chore(core): Migrate runtime lint to Oxlint (no-changelog) (#39844)

This commit is contained in:
Matsu 2026-09-29 16:06:29 +03:00 committed by GitHub
parent 499d64181a
commit fe1bfdfd29
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 206 additions and 52 deletions

View File

@ -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'],
});

View File

@ -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:",

View File

@ -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: {

View File

@ -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',
},
},
],
});

View File

@ -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:",

View File

@ -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: {

View File

@ -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',
},
},
],
});

View File

@ -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:",

View File

@ -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<ReturnType<typeof runWithCredential>>) =>
(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();
});
});

View File

@ -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', () => {

View File

@ -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

View File

@ -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": {