mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
chore: Repair Oxlint migration verification (no-changelog) (#40087)
This commit is contained in:
parent
deaa9b1054
commit
aae8016c84
|
|
@ -33,7 +33,8 @@
|
|||
"format": "biome format --write .",
|
||||
"format:check": "biome ci .",
|
||||
"lint": "node -e \"process.exit(0)\"",
|
||||
"test": "node -e \"process.exit(0)\"",
|
||||
"test": "node ../../../scripts/lint-parity/verify-raw-enum-bridge.mjs",
|
||||
"test:unit": "node ../../../scripts/lint-parity/verify-raw-enum-bridge.mjs",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"watch": "tsc --watch"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -24,6 +24,18 @@ import { baseConfig } from '@n8n/eslint-config/base';
|
|||
export default defineConfig(baseConfig);
|
||||
`;
|
||||
|
||||
const OXLINT_BOUNDARY_CONFIG = `import { backendConfig } from '@n8n/oxlint-config/backend';
|
||||
import { defineConfig } from 'oxlint';
|
||||
|
||||
export default defineConfig({ extends: [backendConfig] });
|
||||
`;
|
||||
|
||||
const OXLINT_NODES_CONFIG = `import { nodesConfig } from '@n8n/oxlint-config/nodes';
|
||||
import { defineConfig } from 'oxlint';
|
||||
|
||||
export default defineConfig({ extends: [nodesConfig] });
|
||||
`;
|
||||
|
||||
describe('EncryptionBoundaryRule', () => {
|
||||
let rootDir: string;
|
||||
let rule: EncryptionBoundaryRule;
|
||||
|
|
@ -49,12 +61,13 @@ describe('EncryptionBoundaryRule', () => {
|
|||
deps: Record<string, string>,
|
||||
section = 'dependencies',
|
||||
config: string | null = BOUNDARY_CONFIG,
|
||||
configFilename = 'eslint.config.mjs',
|
||||
): void {
|
||||
write(
|
||||
`${dir}/package.json`,
|
||||
JSON.stringify({ name: path.basename(dir), [section]: deps }, null, 2),
|
||||
);
|
||||
if (config !== null) write(`${dir}/eslint.config.mjs`, config);
|
||||
if (config !== null) write(`${dir}/${configFilename}`, config);
|
||||
}
|
||||
|
||||
async function analyze(): Promise<string[]> {
|
||||
|
|
@ -76,6 +89,30 @@ describe('EncryptionBoundaryRule', () => {
|
|||
expect(await analyze()).toEqual([]);
|
||||
});
|
||||
|
||||
it('accepts an Oxlint package that extends backendConfig', async () => {
|
||||
writePackage(
|
||||
'packages/a',
|
||||
{ 'n8n-core': 'workspace:*' },
|
||||
'dependencies',
|
||||
OXLINT_BOUNDARY_CONFIG,
|
||||
'oxlint.config.mts',
|
||||
);
|
||||
|
||||
expect(await analyze()).toEqual([]);
|
||||
});
|
||||
|
||||
it('accepts an Oxlint package on nodesConfig', async () => {
|
||||
writePackage(
|
||||
'packages/a',
|
||||
{ 'n8n-core': 'workspace:*' },
|
||||
'dependencies',
|
||||
OXLINT_NODES_CONFIG,
|
||||
'oxlint.config.mts',
|
||||
);
|
||||
|
||||
expect(await analyze()).toEqual([]);
|
||||
});
|
||||
|
||||
it('flags a package on baseConfig only', async () => {
|
||||
writePackage('packages/a', { 'n8n-core': 'workspace:*' }, 'dependencies', BASE_CONFIG);
|
||||
|
||||
|
|
@ -93,14 +130,14 @@ describe('EncryptionBoundaryRule', () => {
|
|||
expect(await analyze()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('flags a package with no ESLint config at all', async () => {
|
||||
it('flags a package with no supported lint config', async () => {
|
||||
writePackage('packages/a', { 'n8n-core': 'workspace:*' }, 'dependencies', null);
|
||||
|
||||
const violations = await analyze();
|
||||
|
||||
expect(violations).toHaveLength(1);
|
||||
expect(violations[0]).toContain('packages/a/package.json:1');
|
||||
expect(violations[0]).toContain('has no ESLint config');
|
||||
expect(violations[0]).toContain('has no supported lint config');
|
||||
});
|
||||
|
||||
it('treats @n8n/db and devDependencies as triggers too', async () => {
|
||||
|
|
|
|||
|
|
@ -21,18 +21,21 @@ const DEFAULT_TRIGGER_DEPENDENCIES = ['n8n-core', '@n8n/db'];
|
|||
/** The rules `encryptionBoundaryConfig` enables, without the plugin prefix. */
|
||||
const DEFAULT_GUARDED_RULES = ['no-encryption-guardrail-disable'];
|
||||
|
||||
const BOUNDARY_CONFIG_PATH = 'packages/@n8n/eslint-config/src/configs/encryption-boundary.ts';
|
||||
const BOUNDARY_CONFIG_PATHS =
|
||||
'packages/@n8n/eslint-config/src/configs/encryption-boundary.ts or packages/@n8n/oxlint-config/src/configs/encryption-boundary.ts';
|
||||
|
||||
const CONFIG_FILENAMES = [
|
||||
'eslint.config.mjs',
|
||||
'eslint.config.js',
|
||||
'eslint.config.cjs',
|
||||
'eslint.config.ts',
|
||||
'oxlint.config.mts',
|
||||
'oxlint.config.ts',
|
||||
];
|
||||
|
||||
/** `backendConfig` composes the boundary, and `nodesConfig` composes `backendConfig`. */
|
||||
const BOUNDARY_IMPORT =
|
||||
/import\s*\{([^}]*)\}\s*from\s+['"]@n8n\/eslint-config\/(?:backend|nodes)['"]/;
|
||||
/import\s*\{([^}]*)\}\s*from\s+['"]@n8n\/(?:eslint|oxlint)-config\/(?:backend|nodes)['"]/;
|
||||
const BOUNDARY_EXPORTS = /^(backendConfig|nodesConfig)(?:\s+as\s+(\w+))?$/;
|
||||
|
||||
/** A guarded rule configured to anything weaker than "error" in an ESLint config. */
|
||||
|
|
@ -86,7 +89,7 @@ export class EncryptionBoundaryRule extends BaseRule<CodeHealthContext> {
|
|||
readonly id = 'encryption-boundary';
|
||||
readonly name = 'Encryption Boundary Coverage';
|
||||
readonly description =
|
||||
'Packages that depend on n8n-core or @n8n/db must compose the encryption-boundary ESLint config, keep its rules at "error", and contain no ESLint directive that silences them.';
|
||||
'Packages that depend on n8n-core or @n8n/db must compose the encryption-boundary lint config, keep its rules at "error", and contain no lint directive that silences them.';
|
||||
readonly severity = 'error' as const;
|
||||
|
||||
async analyze(context: CodeHealthContext): Promise<Violation[]> {
|
||||
|
|
@ -132,8 +135,8 @@ export class EncryptionBoundaryRule extends BaseRule<CodeHealthContext> {
|
|||
packageJsonPath,
|
||||
1,
|
||||
1,
|
||||
`${packageName} depends on ${trigger} but has no ESLint config, so the encryption boundary is not linted there.`,
|
||||
'Add an eslint.config.mjs that extends `backendConfig` from @n8n/eslint-config/backend (or `nodesConfig`).',
|
||||
`${packageName} depends on ${trigger} but has no supported lint config, so the encryption boundary is not linted there.`,
|
||||
'Add an eslint.config.mjs or oxlint.config.mts that extends the matching `backendConfig` (or `nodesConfig`).',
|
||||
),
|
||||
];
|
||||
}
|
||||
|
|
@ -147,8 +150,8 @@ export class EncryptionBoundaryRule extends BaseRule<CodeHealthContext> {
|
|||
configPath,
|
||||
1,
|
||||
1,
|
||||
`${packageName} depends on ${trigger} but its ESLint config does not compose the encryption boundary.`,
|
||||
"Extend `backendConfig` from '@n8n/eslint-config/backend' (or `nodesConfig`) and add it to the exported config.",
|
||||
`${packageName} depends on ${trigger} but its lint config does not compose the encryption boundary.`,
|
||||
'Extend `backendConfig` (or `nodesConfig`) from the matching shared lint package and add it to the exported config.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -161,8 +164,8 @@ export class EncryptionBoundaryRule extends BaseRule<CodeHealthContext> {
|
|||
configPath,
|
||||
index + 1,
|
||||
match.index + 1,
|
||||
`The ESLint config sets \`n8n-local-rules/${match[1]}\` to ${match[2]}; the encryption guardrails must stay at "error".`,
|
||||
`Remove the override. Widen the boundary in ${BOUNDARY_CONFIG_PATH} instead.`,
|
||||
`The lint config sets \`n8n-local-rules/${match[1]}\` to ${match[2]}; the encryption guardrails must stay at "error".`,
|
||||
`Remove the override. Widen the boundary in ${BOUNDARY_CONFIG_PATHS} instead.`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
|
@ -220,7 +223,7 @@ export class EncryptionBoundaryRule extends BaseRule<CodeHealthContext> {
|
|||
lineNumber,
|
||||
column,
|
||||
`An inline \`${form}\` configuration comment reconfigures the encryption guardrail \`${rule}\`.`,
|
||||
`Remove the comment. Widen the boundary in ${BOUNDARY_CONFIG_PATH} instead.`,
|
||||
`Remove the comment. Widen the boundary in ${BOUNDARY_CONFIG_PATHS} instead.`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -251,7 +254,7 @@ export class EncryptionBoundaryRule extends BaseRule<CodeHealthContext> {
|
|||
lineNumber,
|
||||
column,
|
||||
`\`${form}\` names the encryption guardrail \`${rule}\`.`,
|
||||
`Remove the directive. Widen the boundary in ${BOUNDARY_CONFIG_PATH} instead.`,
|
||||
`Remove the directive. Widen the boundary in ${BOUNDARY_CONFIG_PATHS} instead.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ Oxlint version: 1.78.0
|
|||
|
||||
This file is the working backlog for the ESLint to Oxlint migration. It focuses on rules that block an Oxlint-only lint command for many packages. `oxlint-gap.json` is the machine-readable source of truth for shared-layer parity.
|
||||
|
||||
Oxlint configs are the executable policy for migrated packages. ESLint policy twins may remain temporarily for parity review, but migration tooling must also support packages that have removed them.
|
||||
|
||||
## Completion criteria
|
||||
|
||||
A blocker is complete when all of these conditions are true:
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@
|
|||
* the shared layer's blocks mixed in, and the question here is always "what does
|
||||
* THIS package decide", which is a property of the file.
|
||||
*
|
||||
* ponytail: a block assembled at runtime (spread of a computed array, a rule id
|
||||
* held in a variable) is invisible. `verify.mjs` cross-checks the totals against
|
||||
* `--print-config`, which would catch a package hiding decisions that way.
|
||||
* A block assembled at runtime (spread of a computed array, a rule id held in a
|
||||
* variable) is invisible. Use resolved config snapshots when exact parity matters.
|
||||
*/
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
* `--threshold N` (default 10) is the count at which a rule moves into `base`.
|
||||
* `--layer <name>` restricts the report to one layer.
|
||||
*/
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
|
|
@ -49,10 +49,17 @@ const get = (id) => {
|
|||
return stats.get(id);
|
||||
};
|
||||
|
||||
const skipped = [];
|
||||
|
||||
for (const [pkg, layer] of layerOf) {
|
||||
if (layer === 'exempt') continue;
|
||||
if (onlyLayer && layer !== onlyLayer) continue;
|
||||
for (const block of readConfigBlocks(join(root, pkg, 'eslint.config.mjs'))) {
|
||||
const configPath = join(root, pkg, 'eslint.config.mjs');
|
||||
if (!existsSync(configPath)) {
|
||||
skipped.push(pkg);
|
||||
continue;
|
||||
}
|
||||
for (const block of readConfigBlocks(configPath)) {
|
||||
for (const rule of block.rules) {
|
||||
const s = get(rule.id);
|
||||
(rule.severity === 'error' ? s.on : s.down).add(pkg);
|
||||
|
|
@ -102,3 +109,8 @@ if (neverOn.length) {
|
|||
console.log(`\nnever enabled in any package (${neverOn.length}):`);
|
||||
for (const r of neverOn) console.log(` ${r.id} (down ${r.down})`);
|
||||
}
|
||||
|
||||
if (skipped.length > 0) {
|
||||
console.log(`\nskipped ${skipped.length} package(s) without an ESLint policy config:`);
|
||||
for (const pkg of skipped) console.log(` ${pkg}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,11 +54,6 @@
|
|||
"src/configs/activity-log.config.ts"
|
||||
],
|
||||
"packages/@n8n/constants": ["src/api.ts"],
|
||||
"packages/@n8n/crdt": [
|
||||
"src/__tests__/helpers.ts",
|
||||
"src/awareness/awareness.test.ts",
|
||||
"src/awareness/index.ts"
|
||||
],
|
||||
"packages/@n8n/db": [
|
||||
"src/connection/__tests__/backoff.test.ts",
|
||||
"src/connection/backoff.ts",
|
||||
|
|
@ -68,12 +63,10 @@
|
|||
"src/__tests__/memoized.test.ts",
|
||||
"src/auth-handler/auth-handler-metadata.ts"
|
||||
],
|
||||
"packages/@n8n/di": ["src/__tests__/circular-depedency.test.ts", "src/di.ts"],
|
||||
"packages/@n8n/engine": [
|
||||
"src/admittance/__tests__/allow-all-admittance.test.ts",
|
||||
"src/admittance/admittance-rejected.error.ts"
|
||||
],
|
||||
"packages/@n8n/errors": ["src/application.error.ts"],
|
||||
"packages/@n8n/eslint-plugin-community-nodes": [
|
||||
"src/plugin.ts",
|
||||
"src/rules/ai-node-package-json.test.ts"
|
||||
|
|
|
|||
|
|
@ -131,6 +131,12 @@
|
|||
"@n8n/mcp-browser#lint": {
|
||||
"dependsOn": ["^build", "@n8n/oxlint-config#build"]
|
||||
},
|
||||
"@n8n/node-cli#lint": {
|
||||
"dependsOn": ["^build", "@n8n/oxlint-config#build"]
|
||||
},
|
||||
"@n8n/node-engine-compatibility#lint": {
|
||||
"dependsOn": ["^build", "@n8n/oxlint-config#build"]
|
||||
},
|
||||
"@n8n/n8n-benchmark#lint": {
|
||||
"dependsOn": ["^build", "@n8n/oxlint-config#build"]
|
||||
},
|
||||
|
|
@ -149,6 +155,9 @@
|
|||
"@n8n/task-runner#lint": {
|
||||
"dependsOn": ["^build", "@n8n/oxlint-config#build"]
|
||||
},
|
||||
"@n8n/test-impact#lint": {
|
||||
"dependsOn": ["^build", "@n8n/oxlint-config#build"]
|
||||
},
|
||||
"@n8n/workflow-sdk#lint": {
|
||||
"dependsOn": ["^build", "@n8n/oxlint-config#build"]
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user