diff --git a/packages/@n8n/nodes-langchain/package.json b/packages/@n8n/nodes-langchain/package.json index 2fe4e8861e6..4a16ec21657 100644 --- a/packages/@n8n/nodes-langchain/package.json +++ b/packages/@n8n/nodes-langchain/package.json @@ -163,7 +163,7 @@ "@types/cheerio": "^0.22.15", "@types/html-to-text": "^9.0.1", "@types/json-schema": "^7.0.15", - "@types/mime-types": "^2.1.0", + "@types/mime-types": "catalog:", "@types/pg": "^8.11.6", "@types/sanitize-html": "^2.11.0", "@types/temp": "^0.9.1", @@ -228,7 +228,7 @@ "langchain": "0.3.33", "lodash": "catalog:", "mammoth": "1.11.0", - "mime-types": "2.1.35", + "mime-types": "catalog:", "mongodb": "6.11.0", "n8n-nodes-base": "workspace:*", "n8n-workflow": "workspace:*", diff --git a/packages/core/package.json b/packages/core/package.json index 884624eac0f..02ac491455e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -33,7 +33,7 @@ "@types/express": "catalog:", "@types/jsonwebtoken": "catalog:", "@types/lodash": "catalog:", - "@types/mime-types": "^2.1.0", + "@types/mime-types": "catalog:", "@types/proxy-from-env": "^1.0.4", "@types/uuid": "catalog:", "@types/xml2js": "catalog:" @@ -63,7 +63,7 @@ "jsonwebtoken": "catalog:", "lodash": "catalog:", "luxon": "catalog:", - "mime-types": "2.1.35", + "mime-types": "catalog:", "n8n-workflow": "workspace:*", "nanoid": "catalog:", "oauth-1.0a": "2.2.6", diff --git a/packages/nodes-base/nodes/Compression/Compression.node.ts b/packages/nodes-base/nodes/Compression/Compression.node.ts index b7fa5b1a3a5..96bc8f5e9ce 100644 --- a/packages/nodes-base/nodes/Compression/Compression.node.ts +++ b/packages/nodes-base/nodes/Compression/Compression.node.ts @@ -2,6 +2,7 @@ import * as fflate from 'fflate'; import * as mime from 'mime-types'; import { NodeConnectionTypes, + NodeOperationError, type IBinaryKeyData, type IExecuteFunctions, type INodeExecutionData, @@ -276,8 +277,16 @@ export class Compression implements INodeType { for (const [index, binaryPropertyName] of binaryPropertyNames.entries()) { const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName); const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName); + const fileExtension = binaryData.fileExtension?.toLowerCase(); - if (binaryData.fileExtension?.toLowerCase() === 'zip') { + if (!fileExtension) { + throw new NodeOperationError( + this.getNode(), + `File extension not found for binary data ${binaryPropertyName}`, + ); + } + + if (fileExtension === 'zip') { const files = await unzip(binaryDataBuffer); for (const key of Object.keys(files)) { @@ -293,7 +302,7 @@ export class Compression implements INodeType { binaryObject[`${outputPrefix}${zipIndex++}`] = data; } - } else if (['gz', 'gzip'].includes(binaryData.fileExtension?.toLowerCase() as string)) { + } else if (['gz', 'gzip'].includes(fileExtension)) { const file = await gunzip(binaryDataBuffer); const fileName = binaryData.fileName?.split('.')[0]; diff --git a/packages/nodes-base/nodes/Compression/test/node/Decompression.test.ts b/packages/nodes-base/nodes/Compression/test/node/Decompression.test.ts new file mode 100644 index 00000000000..cccffcdeac4 --- /dev/null +++ b/packages/nodes-base/nodes/Compression/test/node/Decompression.test.ts @@ -0,0 +1,575 @@ +import { mockDeep } from 'jest-mock-extended'; +import type { IExecuteFunctions, INode, IBinaryData } from 'n8n-workflow'; +import { NodeOperationError } from 'n8n-workflow'; + +import * as fflate from 'fflate'; + +import { Compression } from '../../Compression.node'; + +jest.mock('fflate'); + +const mockFflate = ( + method: 'unzip' | 'gunzip' | 'zip' | 'gunzip', + data: any, + error: any = null, +) => { + jest.mocked(fflate[method]).mockImplementation((_, callback) => { + callback(error, data); + return () => {}; + }); +}; + +describe('Compression Node - Decompress Operation', () => { + let compression: Compression; + let mockExecuteFunctions: jest.Mocked; + const mockNode: INode = { + id: 'test-node', + name: 'Compression', + type: 'n8n-nodes-base.compression', + typeVersion: 1.1, + position: [0, 0], + parameters: {}, + }; + + beforeEach(() => { + compression = new Compression(); + mockExecuteFunctions = mockDeep(); + jest.clearAllMocks(); + + mockExecuteFunctions.getNode.mockReturnValue(mockNode); + mockExecuteFunctions.getInputData.mockReturnValue([{ json: { test: 'data' } }]); + mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { + const params: Record = { + operation: 'decompress', + binaryPropertyName: 'data', + outputPrefix: 'file_', + }; + return params[paramName]; + }); + mockExecuteFunctions.continueOnFail.mockReturnValue(false); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('Zip Decompression', () => { + it('should decompress a zip file successfully', async () => { + const mockBinaryData: IBinaryData = { + data: 'base64data', + mimeType: 'application/zip', + fileName: 'test.zip', + fileExtension: 'zip', + }; + + const mockZipContents = { + file1_txt: new Uint8Array([72, 101, 108, 108, 111]), + file2_txt: new Uint8Array([87, 111, 114, 108, 100]), + }; + + jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData); + jest + .mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer) + .mockResolvedValue(Buffer.from('mock zip data')); + mockFflate('unzip', mockZipContents); + + jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockImplementation( + async (buffer, fileName) => + ({ + data: buffer.toString('base64'), + mimeType: 'text/plain', + fileName: fileName ?? 'file', + fileExtension: 'txt', + }) as IBinaryData, + ); + + const result = await compression.execute.call(mockExecuteFunctions); + + expect(result).toHaveLength(1); + expect(result[0]).toHaveLength(1); + expect(result[0][0].json).toEqual({ test: 'data' }); + expect(result[0][0].binary).toBeDefined(); + expect(result[0][0].binary?.file_0).toBeDefined(); + expect(result[0][0].binary?.file_1).toBeDefined(); + expect(result[0][0].pairedItem).toEqual({ item: 0 }); + expect(fflate.unzip).toHaveBeenCalledTimes(1); + }); + + it('should skip __MACOSX files when decompressing zip', async () => { + const mockBinaryData: IBinaryData = { + data: 'base64data', + mimeType: 'application/zip', + fileName: 'test.zip', + fileExtension: 'zip', + }; + + const mockZipContents = { + file1_txt: new Uint8Array([72, 101, 108, 108, 111]), + __MACOSX_file1_txt: new Uint8Array([0, 0, 0]), + file2_txt: new Uint8Array([87, 111, 114, 108, 100]), + }; + + jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData); + jest + .mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer) + .mockResolvedValue(Buffer.from('mock zip data')); + mockFflate('unzip', mockZipContents); + + jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockImplementation( + async (buffer, fileName) => + ({ + data: buffer.toString('base64'), + mimeType: 'text/plain', + fileName: fileName ?? 'file', + fileExtension: 'txt', + }) as IBinaryData, + ); + + const result = await compression.execute.call(mockExecuteFunctions); + + expect(result[0][0].binary?.file_0).toBeDefined(); + expect(result[0][0].binary?.file_1).toBeDefined(); + expect(jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData)).toHaveBeenCalledTimes(2); + }); + + it('should process multiple zip files from comma-separated binary properties', async () => { + mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { + const params: Record = { + operation: 'decompress', + binaryPropertyName: 'data1,data2', + outputPrefix: 'file_', + }; + return params[paramName]; + }); + + const mockBinaryData: IBinaryData = { + data: 'base64data', + mimeType: 'application/zip', + fileName: 'test.zip', + fileExtension: 'zip', + }; + + const mockZipContents = { + file_txt: new Uint8Array([72, 101, 108, 108, 111]), + }; + + jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData); + jest + .mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer) + .mockResolvedValue(Buffer.from('mock zip data')); + mockFflate('unzip', mockZipContents); + + jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockImplementation( + async (buffer, fileName) => + ({ + data: buffer.toString('base64'), + mimeType: 'text/plain', + fileName: fileName ?? 'file', + fileExtension: 'txt', + }) as IBinaryData, + ); + + await compression.execute.call(mockExecuteFunctions); + + expect(fflate.unzip).toHaveBeenCalledTimes(2); + expect(jest.mocked(mockExecuteFunctions.helpers.assertBinaryData)).toHaveBeenCalledWith( + 0, + 'data1', + ); + expect(jest.mocked(mockExecuteFunctions.helpers.assertBinaryData)).toHaveBeenCalledWith( + 0, + 'data2', + ); + }); + }); + + describe('Gzip Decompression', () => { + it('should decompress a gzip file successfully', async () => { + const mockBinaryData: IBinaryData = { + data: 'base64data', + mimeType: 'application/gzip', + fileName: 'test.txt.gz', + fileExtension: 'gz', + }; + + const mockGunzipData = new Uint8Array([72, 101, 108, 108, 111]); + + jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData); + jest + .mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer) + .mockResolvedValue(Buffer.from('mock gzip data')); + mockFflate('gunzip', mockGunzipData); + + jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({ + data: 'SGVsbG8=', + mimeType: 'text/plain', + fileName: 'test.txt', + fileExtension: 'txt', + } as IBinaryData); + + const result = await compression.execute.call(mockExecuteFunctions); + + expect(result).toHaveLength(1); + expect(result[0]).toHaveLength(1); + expect(result[0][0].binary?.file_0).toBeDefined(); + expect(result[0][0].binary?.file_0?.fileName).toBe('test.txt'); + expect(result[0][0].binary?.file_0?.fileExtension).toBe('txt'); + expect(result[0][0].binary?.file_0?.mimeType).toBe('text/plain'); + expect(fflate.gunzip).toHaveBeenCalledTimes(1); + }); + + it('should handle gzip file with .gzip extension', async () => { + const mockBinaryData: IBinaryData = { + data: 'base64data', + mimeType: 'application/gzip', + fileName: 'test.txt.gzip', + fileExtension: 'gzip', + }; + + const mockGunzipData = new Uint8Array([72, 101, 108, 108, 111]); + + jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData); + jest + .mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer) + .mockResolvedValue(Buffer.from('mock gzip data')); + mockFflate('gunzip', mockGunzipData); + + jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({ + data: 'SGVsbG8=', + mimeType: 'text/plain', + fileName: 'test.txt', + fileExtension: 'txt', + } as IBinaryData); + + const result = await compression.execute.call(mockExecuteFunctions); + + expect(result[0][0].binary?.file_0).toBeDefined(); + expect(fflate.gunzip).toHaveBeenCalledTimes(1); + }); + + it('should determine mime type and file extension for gzip file', async () => { + const mockBinaryData: IBinaryData = { + data: 'base64data', + mimeType: 'application/gzip', + fileName: 'data.json.gz', + fileExtension: 'gz', + }; + + const mockGunzipData = new Uint8Array([123, 125]); + + jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData); + jest + .mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer) + .mockResolvedValue(Buffer.from('mock gzip data')); + mockFflate('gunzip', mockGunzipData); + + let callCount = 0; + jest + .mocked(mockExecuteFunctions.helpers.prepareBinaryData) + .mockImplementation(async (buffer, fileName, mimeType) => { + callCount++; + if (callCount === 1) { + return { + data: buffer.toString('base64'), + mimeType: 'application/json', + fileName: fileName ?? 'data', + fileExtension: 'json', + } as IBinaryData; + } + return { + data: buffer.toString('base64'), + mimeType: mimeType ?? 'application/json', + fileName: fileName ?? 'data', + fileExtension: 'json', + } as IBinaryData; + }); + + const result = await compression.execute.call(mockExecuteFunctions); + + expect(result[0][0].binary?.file_0?.fileName).toBe('data.json'); + expect(result[0][0].binary?.file_0?.fileExtension).toBe('json'); + expect(result[0][0].binary?.file_0?.mimeType).toBe('application/json'); + }); + + it('should process multiple gzip files', async () => { + mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { + const params: Record = { + operation: 'decompress', + binaryPropertyName: 'data1,data2', + outputPrefix: 'file_', + }; + return params[paramName]; + }); + + const mockBinaryData: IBinaryData = { + data: 'base64data', + mimeType: 'application/gzip', + fileName: 'test.txt.gz', + fileExtension: 'gz', + }; + + const mockGunzipData = new Uint8Array([72, 101, 108, 108, 111]); + + jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData); + jest + .mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer) + .mockResolvedValue(Buffer.from('mock gzip data')); + mockFflate('gunzip', mockGunzipData); + + jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({ + data: 'SGVsbG8=', + mimeType: 'text/plain', + fileName: 'test.txt', + fileExtension: 'txt', + } as IBinaryData); + + const result = await compression.execute.call(mockExecuteFunctions); + + expect(result[0][0].binary?.file_0).toBeDefined(); + expect(result[0][0].binary?.file_1).toBeDefined(); + expect(fflate.gunzip).toHaveBeenCalledTimes(2); + }); + }); + + describe('Error Handling', () => { + it('should throw error when file extension is not found', async () => { + const mockBinaryData: IBinaryData = { + data: 'base64data', + mimeType: 'application/octet-stream', + fileName: undefined, + fileExtension: undefined, + }; + + jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData); + + await expect(compression.execute.call(mockExecuteFunctions)).rejects.toThrow( + NodeOperationError, + ); + }); + + it('should handle decompression errors with continueOnFail', async () => { + const mockBinaryData: IBinaryData = { + data: 'base64data', + mimeType: 'application/zip', + fileName: 'test.zip', + fileExtension: 'zip', + }; + + jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData); + jest + .mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer) + .mockResolvedValue(Buffer.from('invalid zip data')); + mockFflate('unzip', null, new Error('Invalid zip file')); + mockExecuteFunctions.continueOnFail.mockReturnValue(true); + + const result = await compression.execute.call(mockExecuteFunctions); + + expect(result[0]).toHaveLength(1); + expect(result[0][0].json).toHaveProperty('error', 'Invalid zip file'); + expect(result[0][0].pairedItem).toEqual({ item: 0 }); + }); + + it('should throw error when continueOnFail is false', async () => { + const mockBinaryData: IBinaryData = { + data: 'base64data', + mimeType: 'application/zip', + fileName: 'test.zip', + fileExtension: 'zip', + }; + + jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData); + jest + .mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer) + .mockResolvedValue(Buffer.from('invalid zip data')); + mockFflate('unzip', null, new Error('Invalid zip file')); + mockExecuteFunctions.continueOnFail.mockReturnValue(false); + + await expect(compression.execute.call(mockExecuteFunctions)).rejects.toThrow( + 'Invalid zip file', + ); + }); + + it('should handle gunzip errors with continueOnFail', async () => { + const mockBinaryData: IBinaryData = { + data: 'base64data', + mimeType: 'application/gzip', + fileName: 'test.txt.gz', + fileExtension: 'gz', + }; + + jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData); + jest + .mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer) + .mockResolvedValue(Buffer.from('invalid gzip data')); + mockFflate('gunzip', null, new Error('Invalid gzip file')); + mockExecuteFunctions.continueOnFail.mockReturnValue(true); + + const result = await compression.execute.call(mockExecuteFunctions); + + expect(result[0][0].json).toHaveProperty('error', 'Invalid gzip file'); + }); + + it('should throw when fileExtension is missing', async () => { + const mockBinaryData: IBinaryData = { + data: 'base64data', + mimeType: 'application/zip', + fileName: 'test.zip', + }; + + jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData); + jest + .mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer) + .mockResolvedValue(Buffer.from('mock zip data')); + + await expect(compression.execute.call(mockExecuteFunctions)).rejects.toThrow( + NodeOperationError, + ); + }); + }); + + describe('Multiple Items Processing', () => { + it('should process multiple input items', async () => { + mockExecuteFunctions.getInputData.mockReturnValue([ + { json: { item: 1 } }, + { json: { item: 2 } }, + ]); + + const mockBinaryData: IBinaryData = { + data: 'base64data', + mimeType: 'application/gzip', + fileName: 'test.txt.gz', + fileExtension: 'gz', + }; + + const mockGunzipData = new Uint8Array([72, 101, 108, 108, 111]); + + jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData); + jest + .mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer) + .mockResolvedValue(Buffer.from('mock gzip data')); + mockFflate('gunzip', mockGunzipData); + + jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({ + data: 'SGVsbG8=', + mimeType: 'text/plain', + fileName: 'test.txt', + fileExtension: 'txt', + } as IBinaryData); + + const result = await compression.execute.call(mockExecuteFunctions); + + expect(result[0]).toHaveLength(2); + expect(result[0][0].json).toEqual({ item: 1 }); + expect(result[0][1].json).toEqual({ item: 2 }); + expect(result[0][0].pairedItem).toEqual({ item: 0 }); + expect(result[0][1].pairedItem).toEqual({ item: 1 }); + }); + }); + + describe('Edge Cases', () => { + it('should use custom output prefix', async () => { + mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { + const params: Record = { + operation: 'decompress', + binaryPropertyName: 'data', + outputPrefix: 'extracted_', + }; + return params[paramName]; + }); + + const mockBinaryData: IBinaryData = { + data: 'base64data', + mimeType: 'application/zip', + fileName: 'test.zip', + fileExtension: 'zip', + }; + + const mockZipContents = { + file1_txt: new Uint8Array([72, 101, 108, 108, 111]), + }; + + jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData); + jest + .mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer) + .mockResolvedValue(Buffer.from('mock zip data')); + mockFflate('unzip', mockZipContents); + + jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({ + data: 'SGVsbG8=', + mimeType: 'text/plain', + fileName: 'file1.txt', + fileExtension: 'txt', + } as IBinaryData); + + const result = await compression.execute.call(mockExecuteFunctions); + + expect(result[0][0].binary?.extracted_0).toBeDefined(); + }); + + it('should handle empty zip archive', async () => { + const mockBinaryData: IBinaryData = { + data: 'base64data', + mimeType: 'application/zip', + fileName: 'empty.zip', + fileExtension: 'zip', + }; + + jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData); + jest + .mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer) + .mockResolvedValue(Buffer.from('mock zip data')); + mockFflate('unzip', {}); + + const result = await compression.execute.call(mockExecuteFunctions); + + expect(result[0][0].binary).toEqual({}); + expect(result[0][0].json).toEqual({ test: 'data' }); + }); + + it('should trim whitespace from binary property names', async () => { + mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { + const params: Record = { + operation: 'decompress', + binaryPropertyName: ' data1 , data2 ', + outputPrefix: 'file_', + }; + return params[paramName]; + }); + + const mockBinaryData: IBinaryData = { + data: 'base64data', + mimeType: 'application/gzip', + fileName: 'test.txt.gz', + fileExtension: 'gz', + }; + + const mockGunzipData = new Uint8Array([72, 101, 108, 108, 111]); + + jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData); + jest + .mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer) + .mockResolvedValue(Buffer.from('mock gzip data')); + mockFflate('gunzip', mockGunzipData); + + jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({ + data: 'SGVsbG8=', + mimeType: 'text/plain', + fileName: 'test.txt', + fileExtension: 'txt', + } as IBinaryData); + + const result = await compression.execute.call(mockExecuteFunctions); + + expect(jest.mocked(mockExecuteFunctions.helpers.assertBinaryData)).toHaveBeenCalledWith( + 0, + 'data1', + ); + expect(jest.mocked(mockExecuteFunctions.helpers.assertBinaryData)).toHaveBeenCalledWith( + 0, + 'data2', + ); + expect(result[0][0].binary?.file_0).toBeDefined(); + expect(result[0][0].binary?.file_1).toBeDefined(); + }); + }); +}); diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 40ada196e21..cefc60cf340 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -871,7 +871,7 @@ "@types/lodash": "catalog:", "@types/lossless-json": "^1.0.0", "@types/mailparser": "^3.4.4", - "@types/mime-types": "^2.1.0", + "@types/mime-types": "catalog:", "@types/mssql": "^9.1.5", "@types/nodemailer": "^7.0.3", "@types/oracledb": "^6.9.1", @@ -925,6 +925,7 @@ "lossless-json": "1.0.5", "luxon": "catalog:", "mailparser": "3.6.7", + "mime-types": "catalog:", "minifaker": "1.34.1", "moment-timezone": "0.5.48", "mongodb": "6.11.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5af00534465..1fd1d186d36 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,6 +39,9 @@ catalogs: '@types/lodash': specifier: 4.17.17 version: 4.17.17 + '@types/mime-types': + specifier: 3.0.1 + version: 3.0.1 '@types/uuid': specifier: ^10.0.0 version: 10.0.0 @@ -87,6 +90,9 @@ catalogs: luxon: specifier: 3.4.4 version: 3.4.4 + mime-types: + specifier: 3.0.1 + version: 3.0.1 mysql2: specifier: 3.15.0 version: 3.15.0 @@ -1211,8 +1217,8 @@ importers: specifier: 1.11.0 version: 1.11.0 mime-types: - specifier: 2.1.35 - version: 2.1.35 + specifier: 'catalog:' + version: 3.0.1 mongodb: specifier: 6.11.0 version: 6.11.0(@aws-sdk/credential-providers@3.808.0)(gcp-metadata@5.3.0)(socks@2.8.3) @@ -1278,8 +1284,8 @@ importers: specifier: ^7.0.15 version: 7.0.15 '@types/mime-types': - specifier: ^2.1.0 - version: 2.1.1 + specifier: 'catalog:' + version: 3.0.1 '@types/pg': specifier: ^8.11.6 version: 8.11.6 @@ -1926,8 +1932,8 @@ importers: specifier: 'catalog:' version: 3.4.4 mime-types: - specifier: 2.1.35 - version: 2.1.35 + specifier: 'catalog:' + version: 3.0.1 n8n-workflow: specifier: workspace:* version: link:../workflow @@ -1984,8 +1990,8 @@ importers: specifier: 'catalog:' version: 4.17.17 '@types/mime-types': - specifier: ^2.1.0 - version: 2.1.1 + specifier: 'catalog:' + version: 3.0.1 '@types/proxy-from-env': specifier: ^1.0.4 version: 1.0.4 @@ -2999,6 +3005,9 @@ importers: mailparser: specifier: 3.6.7 version: 3.6.7 + mime-types: + specifier: 'catalog:' + version: 3.0.1 minifaker: specifier: 1.34.1 version: 1.34.1(patch_hash=bc707e2c34a2464da2c9c93ead33e80fd9883a00434ef64907ddc45208a08b33) @@ -3148,8 +3157,8 @@ importers: specifier: ^3.4.4 version: 3.4.4 '@types/mime-types': - specifier: ^2.1.0 - version: 2.1.1 + specifier: 'catalog:' + version: 3.0.1 '@types/mssql': specifier: ^9.1.5 version: 9.1.5 @@ -8317,8 +8326,8 @@ packages: '@types/methods@1.1.4': resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} - '@types/mime-types@2.1.1': - resolution: {integrity: sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==} + '@types/mime-types@3.0.1': + resolution: {integrity: sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ==} '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} @@ -24492,7 +24501,7 @@ snapshots: '@types/methods@1.1.4': {} - '@types/mime-types@2.1.1': {} + '@types/mime-types@3.0.1': {} '@types/mime@1.3.5': {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5eadadc39f7..3c07eb454b6 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,24 +5,19 @@ packages: - packages/extensions/** - packages/testing/** -minimumReleaseAge: 2880 # 2 days -minimumReleaseAgeExclude: - - '@n8n/*' - - '@n8n_io/*' - - eslint-plugin-storybook - catalog: - '@n8n/typeorm': 0.3.20-15 - '@n8n_io/ai-assistant-sdk': 1.17.0 - '@langchain/core': 0.3.68 - '@langchain/openai': 0.6.16 '@langchain/anthropic': 0.3.26 '@langchain/community': 0.3.50 + '@langchain/core': 0.3.68 + '@langchain/openai': 0.6.16 + '@n8n/typeorm': 0.3.20-15 + '@n8n_io/ai-assistant-sdk': 1.17.0 '@sentry/node': ^9.42.1 '@types/basic-auth': ^1.1.3 '@types/express': ^5.0.1 '@types/jsonwebtoken': ^9.0.9 '@types/lodash': 4.17.17 + '@types/mime-types': 3.0.1 '@types/uuid': ^10.0.0 '@types/xml2js': ^0.4.14 '@vitest/coverage-v8': 3.2.4 @@ -30,6 +25,7 @@ catalog: basic-auth: 2.0.1 callsites: 3.1.0 chokidar: 4.0.3 + eslint: 9.29.0 fast-glob: 3.2.12 fastest-levenshtein: 1.0.16 flatted: 3.2.7 @@ -37,17 +33,22 @@ catalog: http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 iconv-lite: 0.6.3 - jsonwebtoken: 9.0.2 js-base64: 3.7.2 + jsonwebtoken: 9.0.2 lodash: 4.17.21 luxon: 3.4.4 + mime-types: 3.0.1 + mysql2: 3.15.0 nanoid: 3.3.8 + nodemailer: 7.0.10 picocolors: 1.0.1 reflect-metadata: 0.2.2 rimraf: 6.0.1 + run-script-os: 1.1.6 + simple-git: 3.28.0 tsdown: ^0.15.6 tsx: ^4.19.3 - simple-git: 3.28.0 + typescript: 5.9.2 uuid: 10.0.0 vite: npm:rolldown-vite@latest vite-plugin-dts: ^4.5.4 @@ -57,11 +58,6 @@ catalog: xss: 1.0.15 zod: 3.25.67 zod-to-json-schema: 3.23.3 - typescript: 5.9.2 - eslint: 9.29.0 - mysql2: 3.15.0 - run-script-os: 1.1.6 - nodemailer: 7.0.10 catalogs: frontend: @@ -69,16 +65,23 @@ catalogs: '@testing-library/jest-dom': ^6.6.3 '@testing-library/user-event': ^14.6.1 '@testing-library/vue': ^8.1.0 + '@vitejs/plugin-vue': ^5.2.4 '@vue/test-utils': ^2.4.6 '@vue/tsconfig': ^0.7.0 '@vueuse/core': ^10.11.0 - '@vitejs/plugin-vue': ^5.2.4 + element-plus: 2.4.3 + highlight.js: ^11.8.0 pinia: ^2.2.4 unplugin-icons: ^0.19.0 vue: ^3.5.13 vue-i18n: ^11.1.2 + vue-markdown-render: ^2.2.1 vue-router: ^4.5.0 vue-tsc: ^2.2.8 - vue-markdown-render: ^2.2.1 - highlight.js: ^11.8.0 - element-plus: 2.4.3 + +minimumReleaseAge: 2880 + +minimumReleaseAgeExclude: + - '@n8n/*' + - '@n8n_io/*' + - eslint-plugin-storybook