diff --git a/packages/@n8n/node-engine-compatibility/oxlint.config.mts b/packages/@n8n/node-engine-compatibility/oxlint.config.mts new file mode 100644 index 00000000000..0b93a1b734c --- /dev/null +++ b/packages/@n8n/node-engine-compatibility/oxlint.config.mts @@ -0,0 +1,8 @@ +import { backendConfig } from '@n8n/oxlint-config/backend'; +import { defineConfig } from 'oxlint'; + +export default defineConfig({ + extends: [backendConfig], + options: { typeAware: true }, + ignorePatterns: ['dist/**', 'vitest.integration.config.ts'], +}); diff --git a/packages/@n8n/node-engine-compatibility/package.json b/packages/@n8n/node-engine-compatibility/package.json index 1bd0e0cabb3..49f3da9596d 100644 --- a/packages/@n8n/node-engine-compatibility/package.json +++ b/packages/@n8n/node-engine-compatibility/package.json @@ -9,8 +9,8 @@ "build:unchecked": "tsc -p tsconfig.build.json --noCheck", "format": "biome format --write src", "format:check": "biome ci src", - "lint": "eslint . --quiet", - "lint:fix": "eslint . --fix", + "lint": "oxlint --quiet", + "lint:fix": "oxlint --fix", "test": "vitest run --passWithNoTests", "test:dev": "vitest --watch", "test:integration": "vitest run --config vitest.integration.config.ts", @@ -27,11 +27,15 @@ "n8n-workflow": "workspace:*" }, "devDependencies": { + "@n8n/eslint-config": "workspace:*", + "@n8n/oxlint-config": "workspace:*", "@n8n/typescript-config": "workspace:*", "@n8n/vitest-config": "workspace:*", "@testcontainers/postgresql": "catalog:", "@types/supertest": "^6.0.3", "n8n-nodes-base": "workspace:*", + "oxlint": "catalog:", + "oxlint-tsgolint": "catalog:", "supertest": "^7.1.1", "typescript": "catalog:typescript", "uuid": "catalog:", diff --git a/packages/@n8n/task-runner/eslint.config.mjs b/packages/@n8n/task-runner/eslint.config.mjs index 3934ca643e6..0cd3b58987a 100644 --- a/packages/@n8n/task-runner/eslint.config.mjs +++ b/packages/@n8n/task-runner/eslint.config.mjs @@ -6,10 +6,12 @@ export default defineConfig( { rules: { complexity: 'error', - - '@typescript-eslint/no-require-imports': 'warn', }, }, + { + files: ['src/js-task-runner/js-task-runner.ts'], + rules: { '@typescript-eslint/no-require-imports': 'warn' }, + }, { files: ['**/*.test.ts'], rules: { diff --git a/packages/@n8n/task-runner/oxlint.config.mts b/packages/@n8n/task-runner/oxlint.config.mts new file mode 100644 index 00000000000..e0c689591d6 --- /dev/null +++ b/packages/@n8n/task-runner/oxlint.config.mts @@ -0,0 +1,24 @@ +import { backendConfig } from '@n8n/oxlint-config/backend'; +import { defineConfig } from 'oxlint'; + +export default defineConfig({ + extends: [backendConfig], + options: { typeAware: true }, + rules: { complexity: 'error' }, + overrides: [ + { + files: ['src/js-task-runner/js-task-runner.ts'], + rules: { 'typescript/no-require-imports': 'warn' }, + }, + { + files: ['**/*.test.ts'], + rules: { + 'import/no-duplicates': 'warn', + 'typescript/unbound-method': 'warn', + 'typescript/no-unsafe-argument': 'warn', + 'typescript/no-unsafe-member-access': 'warn', + 'typescript/no-unsafe-assignment': 'warn', + }, + }, + ], +}); diff --git a/packages/@n8n/task-runner/package.json b/packages/@n8n/task-runner/package.json index 9e7d60cc20e..af8b9dd3127 100644 --- a/packages/@n8n/task-runner/package.json +++ b/packages/@n8n/task-runner/package.json @@ -14,8 +14,8 @@ "test": "vitest run", "test:unit": "vitest run", "test:dev": "vitest --silent=false", - "lint": "eslint . --quiet", - "lint:fix": "eslint . --fix", + "lint": "oxlint --quiet", + "lint:fix": "oxlint --fix", "watch": "tsc-watch -p tsconfig.build.json --onCompilationComplete \"tsc-alias -p tsconfig.build.json\"" }, "main": "dist/start.js", @@ -52,12 +52,15 @@ }, "devDependencies": { "@n8n/eslint-config": "workspace:*", + "@n8n/oxlint-config": "workspace:*", "@n8n/typescript-config": "workspace:*", "@n8n/vitest-config": "workspace:*", "@types/lodash": "catalog:", "@types/luxon": "catalog:", "@types/ws": "catalog:", "@vitest/coverage-v8": "catalog:", + "oxlint": "catalog:", + "oxlint-tsgolint": "catalog:", "typescript": "catalog:typescript", "vite": "catalog:", "vitest": "catalog:", diff --git a/packages/core/eslint.config.mjs b/packages/core/eslint.config.mjs index 6d7a5fdf2ad..418631e76a2 100644 --- a/packages/core/eslint.config.mjs +++ b/packages/core/eslint.config.mjs @@ -9,17 +9,42 @@ export default defineConfig( // TODO: Lower the complexity threshold complexity: ['error', 27], 'n8n-local-rules/no-dynamic-regexp': 'error', - - // TODO: Remove these - 'no-prototype-builtins': 'warn', - 'no-ex-assign': 'warn', - 'no-useless-escape': 'warn', - '@typescript-eslint/no-require-imports': 'warn', - '@typescript-eslint/no-base-to-string': 'warn', - '@typescript-eslint/prefer-optional-chain': 'warn', - '@typescript-eslint/no-array-delete': 'warn', }, }, + { + files: [ + 'src/execution-engine/node-execution-context/execute-context.ts', + 'src/execution-engine/node-execution-context/execute-single-context.ts', + 'src/execution-engine/node-execution-context/supply-data-context.ts', + 'src/execution-engine/node-execution-context/utils/get-input-connection-data.ts', + 'src/nodes-loader/directory-loader.ts', + ], + rules: { 'no-prototype-builtins': 'warn' }, + }, + { + files: ['src/execution-engine/node-execution-context/utils/get-input-connection-data.ts'], + rules: { 'no-ex-assign': 'warn' }, + }, + { + files: [ + 'src/execution-engine/node-execution-context/utils/request-helpers/pagination.ts', + 'src/execution-engine/node-execution-context/utils/webhook-helper-functions.ts', + 'src/execution-engine/routing-node.ts', + ], + rules: { '@typescript-eslint/no-base-to-string': 'warn' }, + }, + { + files: ['src/nodes-loader/load-class-in-isolation.ts', 'test/helpers/index.ts'], + rules: { '@typescript-eslint/no-require-imports': 'warn' }, + }, + { + files: ['src/execution-engine/node-execution-context/__tests__/execute-single-context.test.ts'], + rules: { '@typescript-eslint/no-array-delete': 'warn' }, + }, + { + files: ['src/execution-engine/__tests__/routing-node.test.ts'], + rules: { '@typescript-eslint/prefer-optional-chain': 'warn' }, + }, { files: ['**/*.test.ts', '**/test/**/*.ts', '**/__test__/**/*.ts', '**/__tests__/**/*.ts'], rules: { diff --git a/packages/core/oxlint.config.mts b/packages/core/oxlint.config.mts new file mode 100644 index 00000000000..e13c48c6df1 --- /dev/null +++ b/packages/core/oxlint.config.mts @@ -0,0 +1,62 @@ +import { backendConfig } from '@n8n/oxlint-config/backend'; +import { defineConfig } from 'oxlint'; + +export default defineConfig({ + extends: [backendConfig], + options: { typeAware: true }, + ignorePatterns: ['bin/*.js', 'nodes-testing/*.ts', 'nodes-testing/*.cjs', 'coverage/*'], + rules: { + complexity: ['error', 27], + 'n8n-local-rules/no-dynamic-regexp': 'error', + }, + overrides: [ + { + files: [ + 'src/execution-engine/node-execution-context/execute-context.ts', + 'src/execution-engine/node-execution-context/execute-single-context.ts', + 'src/execution-engine/node-execution-context/supply-data-context.ts', + 'src/execution-engine/node-execution-context/utils/get-input-connection-data.ts', + 'src/nodes-loader/directory-loader.ts', + ], + rules: { 'no-prototype-builtins': 'warn' }, + }, + { + files: ['src/execution-engine/node-execution-context/utils/get-input-connection-data.ts'], + rules: { 'no-ex-assign': 'warn' }, + }, + { + files: [ + 'src/execution-engine/node-execution-context/utils/request-helpers/pagination.ts', + 'src/execution-engine/node-execution-context/utils/webhook-helper-functions.ts', + 'src/execution-engine/routing-node.ts', + ], + rules: { 'typescript/no-base-to-string': 'warn' }, + }, + { + files: ['src/nodes-loader/load-class-in-isolation.ts', 'test/helpers/index.ts'], + rules: { 'typescript/no-require-imports': 'warn' }, + }, + { + files: [ + 'src/execution-engine/node-execution-context/__tests__/execute-single-context.test.ts', + ], + rules: { 'typescript/no-array-delete': 'warn' }, + }, + { + files: ['**/*.test.ts', '**/test/**/*.ts', '**/__test__/**/*.ts', '**/__tests__/**/*.ts'], + jsPlugins: ['@n8n/eslint-config/plugin'], + rules: { + 'prefer-const': 'warn', + 'import/no-duplicates': 'warn', + 'typescript/no-unsafe-assignment': 'warn', + 'typescript/no-unsafe-argument': 'warn', + 'typescript/no-unsafe-call': 'warn', + 'typescript/no-unsafe-return': 'warn', + 'typescript/unbound-method': 'warn', + 'no-unused-expressions': 'warn', + 'id-denylist': 'warn', + 'n8n-local-rules/no-dynamic-regexp': 'off', + }, + }, + ], +}); diff --git a/packages/core/package.json b/packages/core/package.json index bdffb10ca34..e0531689a61 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -18,8 +18,8 @@ "dev": "pnpm watch", "format": "biome format --write .", "format:check": "biome ci .", - "lint": "eslint . --quiet", - "lint:fix": "eslint . --fix", + "lint": "oxlint --quiet", + "lint:fix": "oxlint --fix", "watch": "tsc-watch -p tsconfig.build.json --onCompilationComplete \"tsc-alias -p tsconfig.build.json\"", "test": "vitest run", "test:unit": "vitest run", @@ -31,6 +31,8 @@ "bin" ], "devDependencies": { + "@n8n/eslint-config": "workspace:*", + "@n8n/oxlint-config": "workspace:*", "@n8n/playwright-janitor": "workspace:*", "@n8n/typescript-config": "workspace:*", "@n8n/typeorm": "workspace:*", @@ -47,6 +49,8 @@ "@vitest/coverage-v8": "catalog:", "axios": "catalog:", "nock": "catalog:", + "oxlint": "catalog:", + "oxlint-tsgolint": "catalog:", "reflect-metadata": "catalog:", "typescript": "catalog:typescript", "vite": "catalog:", diff --git a/packages/core/src/execution-engine/__tests__/routing-node.test.ts b/packages/core/src/execution-engine/__tests__/routing-node.test.ts index 5f85aa094a6..9fd166f481a 100644 --- a/packages/core/src/execution-engine/__tests__/routing-node.test.ts +++ b/packages/core/src/execution-engine/__tests__/routing-node.test.ts @@ -846,7 +846,7 @@ describe('RoutingNode', () => { // Prototype untouched; the value lands as an own property on the request body. expect(Object.prototype.hasOwnProperty.call(Object.prototype.toString, 'call')).toBe(false); expect(Object.prototype.toString.call([])).toBe('[object Array]'); - expect((result?.options.body as { toString?: { call?: unknown } }).toString?.call).toBe( + expect((result!.options.body as { toString?: { call?: unknown } }).toString?.call).toBe( 'x', ); }); @@ -2749,6 +2749,8 @@ describe('RoutingNode', () => { const routingNode = new RoutingNode(executeFunctions, nodeType, mockCredentials); return await routingNode.runNode(); }; + const getRequestOptions = (result: Awaited>) => + (result![0]![0]!.json as { requestOptions: IHttpRequestOptions }).requestOptions; test("propagates credential allowedDomains when mode is 'domains'", async () => { const result = await runWithCredential({ @@ -2757,8 +2759,7 @@ describe('RoutingNode', () => { allowedDomains: 'api.example.com', }); - const requestOptions = (result?.[0]?.[0]?.json as { requestOptions: IHttpRequestOptions }) - .requestOptions; + const requestOptions = getRequestOptions(result); expect(requestOptions.allowedDomains).toBe('api.example.com'); }); @@ -2768,8 +2769,7 @@ describe('RoutingNode', () => { allowedHttpRequestDomains: 'all', }); - const requestOptions = (result?.[0]?.[0]?.json as { requestOptions: IHttpRequestOptions }) - .requestOptions; + const requestOptions = getRequestOptions(result); expect(requestOptions.allowedDomains).toBeUndefined(); }); @@ -2780,8 +2780,7 @@ describe('RoutingNode', () => { allowedDomains: 'other.example.com', }); - const requestOptions = (result?.[0]?.[0]?.json as { requestOptions: IHttpRequestOptions }) - .requestOptions; + const requestOptions = getRequestOptions(result); expect(requestOptions.allowedDomains).toBe('api.example.com, other.example.com'); }); @@ -2799,8 +2798,7 @@ describe('RoutingNode', () => { { baseURL, routedUrl: 'https://user-chosen.example.net/path' }, ); - const requestOptions = (result?.[0]?.[0]?.json as { requestOptions: IHttpRequestOptions }) - .requestOptions; + const requestOptions = getRequestOptions(result); expect(requestOptions.allowedDomains).toBe('other.example.com'); }); @@ -2810,8 +2808,7 @@ describe('RoutingNode', () => { allowedHttpRequestDomains: 'none', }); - const requestOptions = (result?.[0]?.[0]?.json as { requestOptions: IHttpRequestOptions }) - .requestOptions; + const requestOptions = getRequestOptions(result); expect(requestOptions.allowedDomains).toBeUndefined(); }); @@ -2822,8 +2819,7 @@ describe('RoutingNode', () => { allowedDomains: ' ', }); - const requestOptions = (result?.[0]?.[0]?.json as { requestOptions: IHttpRequestOptions }) - .requestOptions; + const requestOptions = getRequestOptions(result); expect(requestOptions.allowedDomains).toBe('api.example.com'); }); @@ -2833,16 +2829,14 @@ describe('RoutingNode', () => { allowedHttpRequestDomains: 'domains', }); - const requestOptions = (result?.[0]?.[0]?.json as { requestOptions: IHttpRequestOptions }) - .requestOptions; + const requestOptions = getRequestOptions(result); expect(requestOptions.allowedDomains).toBe('api.example.com'); }); test('does not set allowedDomains when restriction field is absent', async () => { const result = await runWithCredential({ apiKey: 'testApiKey' }); - const requestOptions = (result?.[0]?.[0]?.json as { requestOptions: IHttpRequestOptions }) - .requestOptions; + const requestOptions = getRequestOptions(result); expect(requestOptions.allowedDomains).toBeUndefined(); }); @@ -2856,8 +2850,7 @@ describe('RoutingNode', () => { }, ); - const requestOptions = (result?.[0]?.[0]?.json as { requestOptions: IHttpRequestOptions }) - .requestOptions; + const requestOptions = getRequestOptions(result); expect(requestOptions.url).toBe('/tests/project-123'); }); @@ -2880,8 +2873,7 @@ describe('RoutingNode', () => { }, ); - const requestOptions = (result?.[0]?.[0]?.json as { requestOptions: IHttpRequestOptions }) - .requestOptions; + const requestOptions = getRequestOptions(result); expect(requestOptions.allowedDomains).toBe('api.example.com'); }); @@ -2903,8 +2895,7 @@ describe('RoutingNode', () => { { baseURL: parameterizedBaseUrl, nodeParameters: {} }, ); - const requestOptions = (result?.[0]?.[0]?.json as { requestOptions: IHttpRequestOptions }) - .requestOptions; + const requestOptions = getRequestOptions(result); expect(requestOptions.allowedDomains).toBeUndefined(); }); @@ -2918,8 +2909,7 @@ describe('RoutingNode', () => { { baseURL: parameterizedBaseUrl, nodeParameters: {} }, ); - const requestOptions = (result?.[0]?.[0]?.json as { requestOptions: IHttpRequestOptions }) - .requestOptions; + const requestOptions = getRequestOptions(result); expect(requestOptions.allowedDomains).toBe('api.example.com, other.example.com'); }); }); @@ -2939,8 +2929,7 @@ describe('RoutingNode', () => { { propertyBaseURL: 'https://upload.example.com' }, ); - const requestOptions = (result?.[0]?.[0]?.json as { requestOptions: IHttpRequestOptions }) - .requestOptions; + const requestOptions = getRequestOptions(result); expect(requestOptions.allowedDomains).toBe('upload.example.com, api.example.com'); }); @@ -2950,8 +2939,7 @@ describe('RoutingNode', () => { { propertyBaseURL: 'https://upload.example.com' }, ); - const requestOptions = (result?.[0]?.[0]?.json as { requestOptions: IHttpRequestOptions }) - .requestOptions; + const requestOptions = getRequestOptions(result); expect(requestOptions.allowedDomains).toBeUndefined(); }); }); @@ -2975,8 +2963,7 @@ describe('RoutingNode', () => { }, ); - const requestOptions = (result?.[0]?.[0]?.json as { requestOptions: IHttpRequestOptions }) - .requestOptions; + const requestOptions = getRequestOptions(result); expect(requestOptions.allowedDomains).toBe('api.example.com'); }); @@ -2998,8 +2985,7 @@ describe('RoutingNode', () => { { propertyBaseURL: parameterizedPropertyBaseUrl, nodeParameters: {} }, ); - const requestOptions = (result?.[0]?.[0]?.json as { requestOptions: IHttpRequestOptions }) - .requestOptions; + const requestOptions = getRequestOptions(result); expect(requestOptions.allowedDomains).toBeUndefined(); }); }); diff --git a/packages/core/src/execution-engine/node-execution-context/utils/__tests__/get-additional-keys.test.ts b/packages/core/src/execution-engine/node-execution-context/utils/__tests__/get-additional-keys.test.ts index b387ac4fc0b..b9ee6c0a4f8 100644 --- a/packages/core/src/execution-engine/node-execution-context/utils/__tests__/get-additional-keys.test.ts +++ b/packages/core/src/execution-engine/node-execution-context/utils/__tests__/get-additional-keys.test.ts @@ -77,7 +77,7 @@ describe('getAdditionalKeys', () => { const result = getAdditionalKeys(additionalData, 'manual', null, { isCredential: true }); expect(result.$secrets).toBeDefined(); - expect((result.$secrets?.provider1 as IDataObject).secret1).toEqual('secret-value'); + expect((result.$secrets!.provider1 as IDataObject).secret1).toEqual('secret-value'); }); it('should return undefined $secrets by default', () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c0c386f57c..7ccbe2893fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3574,6 +3574,12 @@ importers: specifier: workspace:* version: link:../../workflow devDependencies: + '@n8n/eslint-config': + specifier: workspace:* + version: link:../eslint-config + '@n8n/oxlint-config': + specifier: workspace:* + version: link:../oxlint-config '@n8n/typescript-config': specifier: workspace:* version: link:../typescript-config @@ -3589,6 +3595,12 @@ importers: n8n-nodes-base: specifier: workspace:* version: link:../../nodes-base + oxlint: + specifier: 'catalog:' + version: 1.78.0(oxlint-tsgolint@0.21.1) + oxlint-tsgolint: + specifier: 'catalog:' + version: 0.21.1 supertest: specifier: ^7.1.1 version: 7.1.1(supports-color@8.1.1) @@ -4206,6 +4218,9 @@ importers: '@n8n/eslint-config': specifier: workspace:* version: link:../eslint-config + '@n8n/oxlint-config': + specifier: workspace:* + version: link:../oxlint-config '@n8n/typescript-config': specifier: workspace:* version: link:../typescript-config @@ -4224,6 +4239,12 @@ importers: '@vitest/coverage-v8': specifier: 'catalog:' version: 5.0.1(@vitest/browser@5.0.1)(vitest@5.0.1) + oxlint: + specifier: 'catalog:' + version: 1.78.0(oxlint-tsgolint@0.21.1) + oxlint-tsgolint: + specifier: 'catalog:' + version: 0.21.1 typescript: specifier: catalog:typescript version: 7.0.2 @@ -5301,6 +5322,12 @@ importers: specifier: 'catalog:' version: 11.1.1 devDependencies: + '@n8n/eslint-config': + specifier: workspace:* + version: link:../@n8n/eslint-config + '@n8n/oxlint-config': + specifier: workspace:* + version: link:../@n8n/oxlint-config '@n8n/playwright-janitor': specifier: workspace:* version: link:../testing/janitor @@ -5349,6 +5376,12 @@ importers: nock: specifier: 'catalog:' version: 14.0.14 + oxlint: + specifier: 'catalog:' + version: 1.78.0(oxlint-tsgolint@0.21.1) + oxlint-tsgolint: + specifier: 'catalog:' + version: 0.21.1 reflect-metadata: specifier: 'catalog:' version: 0.2.2 diff --git a/turbo.json b/turbo.json index 095cf4c6b65..e278c292277 100644 --- a/turbo.json +++ b/turbo.json @@ -125,7 +125,10 @@ "@n8n/syslog-client#lint": { "dependsOn": ["^build", "@n8n/oxlint-config#build"] }, - "@n8n/test-impact#lint": { + "@n8n/task-runner#lint": { + "dependsOn": ["^build", "@n8n/oxlint-config#build"] + }, + "n8n-core#lint": { "dependsOn": ["^build", "@n8n/oxlint-config#build"] }, "n8n#lint": {