mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 13:39:44 +02:00
chore: Migrate agent runtime lint to Oxlint (no-changelog) (#39971)
This commit is contained in:
parent
cd9d1ac669
commit
63c01bee24
|
|
@ -14,28 +14,16 @@ export default defineConfig(
|
|||
],
|
||||
},
|
||||
backendConfig,
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/naming-convention': [
|
||||
'error',
|
||||
{
|
||||
selector: 'enumMember',
|
||||
format: ['UPPER_CASE', 'PascalCase'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/**/*.ts'],
|
||||
ignores: ['src/**/__tests__/**/*.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-restricted-imports': [
|
||||
'n8n-local-rules/no-static-runtime-import': [
|
||||
'error',
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: 'ai',
|
||||
allowTypeImports: true,
|
||||
message: AI_SDK_LAZY_IMPORT_MESSAGE,
|
||||
},
|
||||
],
|
||||
|
|
|
|||
45
packages/@n8n/agents/oxlint.config.mts
Normal file
45
packages/@n8n/agents/oxlint.config.mts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { backendConfig } from '@n8n/oxlint-config/backend';
|
||||
import { defineConfig } from 'oxlint';
|
||||
|
||||
const AI_SDK_LAZY_IMPORT_MESSAGE =
|
||||
"Import runtime values from 'ai' through the lazy loader in src/runtime/lazy-ai.ts or a dynamic import at the call site, so @n8n/agents stays light at boot.";
|
||||
|
||||
export default defineConfig({
|
||||
extends: [backendConfig],
|
||||
options: { typeAware: true },
|
||||
ignorePatterns: [
|
||||
'examples/**',
|
||||
'vitest.integration.config.*',
|
||||
'vitest.integration.setup.ts',
|
||||
'src/__tests__/fixtures/**',
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
files: ['src/**/*.ts'],
|
||||
rules: {
|
||||
'n8n-local-rules/no-static-runtime-import': [
|
||||
'error',
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: 'ai',
|
||||
message: AI_SDK_LAZY_IMPORT_MESSAGE,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/**/__tests__/**/*.ts'],
|
||||
rules: { 'n8n-local-rules/no-static-runtime-import': 'off' },
|
||||
},
|
||||
{
|
||||
files: ['src/__tests__/integration/**/*.ts'],
|
||||
rules: {
|
||||
'typescript/require-await': 'off',
|
||||
'n8n-local-rules/no-uncaught-json-parse': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -79,8 +79,8 @@
|
|||
"build:unchecked": "rimraf dist && tsc -p tsconfig.build.json --noCheck",
|
||||
"format": "biome format --write src examples",
|
||||
"format:check": "biome ci src examples",
|
||||
"lint": "eslint . --quiet",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"lint": "oxlint --quiet",
|
||||
"lint:fix": "oxlint --fix",
|
||||
"watch": "tsc -p tsconfig.build.json --watch",
|
||||
"test": "vitest run",
|
||||
"test:unit": "vitest run",
|
||||
|
|
@ -159,6 +159,7 @@
|
|||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@n8n/oxlint-config": "workspace:*",
|
||||
"@n8n/typescript-config": "workspace:*",
|
||||
"@n8n/vitest-config": "workspace:*",
|
||||
"@opentelemetry/api": "catalog:",
|
||||
|
|
@ -172,6 +173,8 @@
|
|||
"dotenv": "catalog:",
|
||||
"cross-env": "catalog:",
|
||||
"nock": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"oxlint-tsgolint": "catalog:",
|
||||
"pg": "catalog:",
|
||||
"tsx": "catalog:",
|
||||
"typescript": "catalog:typescript",
|
||||
|
|
|
|||
|
|
@ -1569,7 +1569,8 @@ describe('AgentRuntime — reasoning-only turn', () => {
|
|||
const error = chunks.find(
|
||||
(chunk): chunk is StreamChunk & { type: 'error' } => chunk.type === 'error',
|
||||
);
|
||||
expect(String((error?.error as Error).message)).toContain('no output');
|
||||
if (!error) throw new Error('Expected an error chunk');
|
||||
expect(String((error.error as Error).message)).toContain('no output');
|
||||
});
|
||||
|
||||
it('fails a reasoning-only length turn without retrying or persisting it', async () => {
|
||||
|
|
|
|||
|
|
@ -76,7 +76,8 @@ describe('buildCallPromptCacheOptions — OpenAI', () => {
|
|||
const result = buildCallPromptCacheOptions({ enabled: true }, 'openai/gpt-5.1', context);
|
||||
|
||||
expect(result?.openai).toMatchObject({ promptCacheRetention: '24h' });
|
||||
expect(typeof (result?.openai as { promptCacheKey: string }).promptCacheKey).toBe('string');
|
||||
if (!result?.openai) throw new Error('Expected OpenAI prompt cache options');
|
||||
expect(typeof (result.openai as { promptCacheKey: string }).promptCacheKey).toBe('string');
|
||||
});
|
||||
|
||||
it('passes through an explicit promptCacheKey and promptCacheRetention unchanged', () => {
|
||||
|
|
@ -120,7 +121,8 @@ describe('createOpenAIPromptCacheKey (via buildCallPromptCacheOptions)', () => {
|
|||
...context,
|
||||
...overrides,
|
||||
});
|
||||
return (result?.openai as { promptCacheKey: string }).promptCacheKey;
|
||||
if (!result?.openai) throw new Error('Expected OpenAI prompt cache options');
|
||||
return (result.openai as { promptCacheKey: string }).promptCacheKey;
|
||||
}
|
||||
|
||||
it('is deterministic for the same agent, model, and instructions', () => {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import { RequireEscapedQueryValuesRule } from './require-escaped-query-values.js
|
|||
import { NoOnLeaderTakeoverRule } from './no-on-leader-takeover.js';
|
||||
import { NoEncryptionGuardrailDisableRule } from './no-encryption-guardrail-disable.js';
|
||||
import { NoRawEnumRule } from './no-raw-enum.js';
|
||||
import { NoStaticRuntimeImportRule } from './no-static-runtime-import.js';
|
||||
|
||||
export const rules = {
|
||||
'no-uncaught-json-parse': NoUncaughtJsonParseRule,
|
||||
|
|
@ -69,4 +70,5 @@ export const rules = {
|
|||
'no-on-leader-takeover': NoOnLeaderTakeoverRule,
|
||||
'no-encryption-guardrail-disable': NoEncryptionGuardrailDisableRule,
|
||||
'no-raw-enum': NoRawEnumRule,
|
||||
'no-static-runtime-import': NoStaticRuntimeImportRule,
|
||||
} satisfies Record<string, AnyRuleModule>;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
import { RuleTester } from '@typescript-eslint/rule-tester';
|
||||
import { NoStaticRuntimeImportRule } from './no-static-runtime-import.js';
|
||||
|
||||
const ruleTester = new RuleTester();
|
||||
const options: [{ paths: Array<{ name: string; message: string }> }] = [
|
||||
{ paths: [{ name: 'heavy-sdk', message: 'Load this module lazily.' }] },
|
||||
];
|
||||
|
||||
ruleTester.run('no-static-runtime-import', NoStaticRuntimeImportRule, {
|
||||
valid: [
|
||||
{ code: "import type { Client } from 'heavy-sdk';", options },
|
||||
{ code: "import { type Client } from 'heavy-sdk';", options },
|
||||
{ code: "const { Client } = await import('heavy-sdk');", options },
|
||||
{ code: "import { Client } from 'other-sdk';", options },
|
||||
{ code: "export type { Client } from 'heavy-sdk';", options },
|
||||
{ code: "export { type Client } from 'heavy-sdk';", options },
|
||||
{ code: "export type * from 'heavy-sdk';", options },
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: "import { Client } from 'heavy-sdk';",
|
||||
options,
|
||||
errors: [{ messageId: 'restrictedImport' }],
|
||||
},
|
||||
{
|
||||
code: "import HeavySdk from 'heavy-sdk';",
|
||||
options,
|
||||
errors: [{ messageId: 'restrictedImport' }],
|
||||
},
|
||||
{
|
||||
code: "import 'heavy-sdk';",
|
||||
options,
|
||||
errors: [{ messageId: 'restrictedImport' }],
|
||||
},
|
||||
{
|
||||
code: "export { Client } from 'heavy-sdk';",
|
||||
options,
|
||||
errors: [{ messageId: 'restrictedImport' }],
|
||||
},
|
||||
{
|
||||
code: "export * from 'heavy-sdk';",
|
||||
options,
|
||||
errors: [{ messageId: 'restrictedImport' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import { ESLintUtils, type TSESTree } from '@typescript-eslint/utils';
|
||||
|
||||
type Options = [{ paths: Array<{ name: string; message: string }> }];
|
||||
type MessageIds = 'restrictedImport';
|
||||
|
||||
export const NoStaticRuntimeImportRule = ESLintUtils.RuleCreator.withoutDocs<Options, MessageIds>({
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description: 'Disallow static runtime imports from configured modules.',
|
||||
},
|
||||
messages: {
|
||||
restrictedImport: '{{ message }}',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['paths'],
|
||||
properties: {
|
||||
paths: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['name', 'message'],
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
message: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
defaultOptions: [{ paths: [] }],
|
||||
create(context, [options]) {
|
||||
const restrictedPaths = new Map(options.paths.map(({ name, message }) => [name, message]));
|
||||
const reportRestrictedExport = (
|
||||
node: TSESTree.ExportAllDeclaration | TSESTree.ExportNamedDeclaration,
|
||||
) => {
|
||||
if (!node.source || node.exportKind === 'type') return;
|
||||
|
||||
const message = restrictedPaths.get(node.source.value);
|
||||
if (!message) return;
|
||||
|
||||
if (node.type === 'ExportNamedDeclaration') {
|
||||
const hasRuntimeSpecifier = node.specifiers.some(
|
||||
(specifier) => specifier.exportKind !== 'type',
|
||||
);
|
||||
if (node.specifiers.length > 0 && !hasRuntimeSpecifier) return;
|
||||
}
|
||||
|
||||
context.report({ node: node.source, messageId: 'restrictedImport', data: { message } });
|
||||
};
|
||||
|
||||
return {
|
||||
ImportDeclaration(node) {
|
||||
const message = restrictedPaths.get(node.source.value);
|
||||
if (!message || node.importKind === 'type') return;
|
||||
|
||||
const hasRuntimeSpecifier = node.specifiers.some(
|
||||
(specifier) => specifier.type !== 'ImportSpecifier' || specifier.importKind !== 'type',
|
||||
);
|
||||
if (node.specifiers.length > 0 && !hasRuntimeSpecifier) return;
|
||||
|
||||
context.report({ node: node.source, messageId: 'restrictedImport', data: { message } });
|
||||
},
|
||||
ExportAllDeclaration: reportRestrictedExport,
|
||||
ExportNamedDeclaration: reportRestrictedExport,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
@ -13,7 +13,6 @@ const restrictedLazyRuntimeImports = [
|
|||
'turndown',
|
||||
].map((name) => ({
|
||||
name,
|
||||
allowTypeImports: true,
|
||||
message: LAZY_RUNTIME_IMPORT_MESSAGE,
|
||||
}));
|
||||
|
||||
|
|
@ -33,25 +32,11 @@ export default defineConfig(
|
|||
'evaluations/cli/pairwise.ts',
|
||||
],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
// Tool names may be kebab-case identifiers (e.g. 'list-workflows'), which
|
||||
// require quotes in object literals. Skip naming checks for those.
|
||||
'@typescript-eslint/naming-convention': [
|
||||
'error',
|
||||
{
|
||||
selector: 'objectLiteralProperty',
|
||||
modifiers: ['requiresQuotes'],
|
||||
format: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/**/*.ts'],
|
||||
ignores: ['src/**/__tests__/**/*.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-restricted-imports': [
|
||||
'n8n-local-rules/no-static-runtime-import': [
|
||||
'error',
|
||||
{ paths: restrictedLazyRuntimeImports },
|
||||
],
|
||||
|
|
@ -93,13 +78,6 @@ export default defineConfig(
|
|||
'@typescript-eslint/no-unsafe-call': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// Debt: the base layer enforces kebab-case filenames and this package has
|
||||
// 6 files that predate it. Rename them, then delete this block.
|
||||
rules: {
|
||||
'unicorn/filename-case': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['evaluations/clients/n8n-client.ts'],
|
||||
// An evaluation harness that talks to a local instance it started
|
||||
|
|
|
|||
82
packages/@n8n/instance-ai/oxlint.config.mts
Normal file
82
packages/@n8n/instance-ai/oxlint.config.mts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { backendConfig } from '@n8n/oxlint-config/backend';
|
||||
import { defineConfig } from 'oxlint';
|
||||
|
||||
const LAZY_RUNTIME_IMPORT_MESSAGE =
|
||||
'Use an existing lazy loader, or add one near first use. Static runtime imports of this dependency undo the idle-memory guardrail.';
|
||||
|
||||
const restrictedLazyRuntimeImports = [
|
||||
'@daytona/sdk',
|
||||
'csv-parse/sync',
|
||||
'linkedom',
|
||||
'pdf-parse',
|
||||
'psl',
|
||||
'turndown',
|
||||
].map((name) => ({
|
||||
name,
|
||||
message: LAZY_RUNTIME_IMPORT_MESSAGE,
|
||||
}));
|
||||
|
||||
export default defineConfig({
|
||||
extends: [backendConfig],
|
||||
options: { typeAware: true },
|
||||
ignorePatterns: [
|
||||
'scripts/**/*.cjs',
|
||||
'skills/**/*.mjs',
|
||||
'.data/**',
|
||||
'evaluations/.data/**',
|
||||
'evaluations/.output/**',
|
||||
'.output/**',
|
||||
'evaluations/cli/pairwise.ts',
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
files: ['src/**/*.ts'],
|
||||
rules: {
|
||||
'n8n-local-rules/no-static-runtime-import': [
|
||||
'error',
|
||||
{ paths: restrictedLazyRuntimeImports },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/**/__tests__/**/*.ts'],
|
||||
rules: { 'n8n-local-rules/no-static-runtime-import': 'off' },
|
||||
},
|
||||
{
|
||||
files: ['src/tools/__tests__/**/*.test.ts'],
|
||||
rules: {
|
||||
'typescript/no-unsafe-assignment': 'off',
|
||||
'typescript/no-unsafe-member-access': 'off',
|
||||
'typescript/no-unsafe-argument': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['evaluations/**/*.ts'],
|
||||
jsPlugins: ['@n8n/oxlint-config/import-x-alias'],
|
||||
rules: {
|
||||
'import-x-alias/no-extraneous-dependencies': ['error', { devDependencies: true }],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['evaluations/computer-use/report-html.ts'],
|
||||
rules: {
|
||||
'typescript/no-unsafe-assignment': 'off',
|
||||
'typescript/no-unsafe-member-access': 'off',
|
||||
'typescript/no-unsafe-argument': 'off',
|
||||
'typescript/no-unsafe-call': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['assets/workflow-diagnostics.mts'],
|
||||
rules: {
|
||||
'import-x-alias/no-extraneous-dependencies': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['evaluations/clients/n8n-client.ts'],
|
||||
rules: {
|
||||
'n8n-local-rules/no-uncentralized-http': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -11,8 +11,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",
|
||||
"eval:instance-ai": "tsx evaluations/cli/index.ts",
|
||||
"eval:agents": "tsx evaluations/cli/index.ts --source langtracer --suite agents --tier agents",
|
||||
"eval:build-mcp-manifest": "tsx evaluations/cli/build-mcp-manifest.ts",
|
||||
|
|
@ -100,6 +100,7 @@
|
|||
"devDependencies": {
|
||||
"@langchain/anthropic": "catalog:",
|
||||
"@langchain/core": "catalog:",
|
||||
"@n8n/oxlint-config": "workspace:*",
|
||||
"@n8n/typescript-config": "workspace:*",
|
||||
"@n8n/vitest-config": "workspace:*",
|
||||
"@types/luxon": "catalog:",
|
||||
|
|
@ -109,6 +110,8 @@
|
|||
"fast-glob": "catalog:",
|
||||
"p-limit": "catalog:p-limit-v7",
|
||||
"playwright-core": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"oxlint-tsgolint": "catalog:",
|
||||
"tsx": "catalog:",
|
||||
"typescript": "catalog:typescript",
|
||||
"undici": "catalog:undici-v7",
|
||||
|
|
|
|||
|
|
@ -6,8 +6,13 @@
|
|||
"compilerOptions": {
|
||||
"target": "es2023",
|
||||
"lib": ["es2023"],
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"paths": {
|
||||
"@/*": ["../src/*"]
|
||||
},
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
|
|
|
|||
|
|
@ -1255,6 +1255,9 @@ importers:
|
|||
specifier: 'catalog:'
|
||||
version: 3.23.3(zod@3.25.76)
|
||||
devDependencies:
|
||||
'@n8n/oxlint-config':
|
||||
specifier: workspace:*
|
||||
version: link:../oxlint-config
|
||||
'@n8n/typescript-config':
|
||||
specifier: workspace:*
|
||||
version: link:../typescript-config
|
||||
|
|
@ -1294,6 +1297,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
|
||||
pg:
|
||||
specifier: 8.21.0
|
||||
version: 8.21.0(pg-native@3.8.0)
|
||||
|
|
@ -3150,6 +3159,9 @@ importers:
|
|||
'@langchain/core':
|
||||
specifier: 'catalog:'
|
||||
version: 1.2.8(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.221.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1))(openai@6.46.0(@aws-sdk/credential-provider-node@3.972.80)(@smithy/signature-v4@5.7.2)(ws@8.21.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))
|
||||
'@n8n/oxlint-config':
|
||||
specifier: workspace:*
|
||||
version: link:../oxlint-config
|
||||
'@n8n/typescript-config':
|
||||
specifier: workspace:*
|
||||
version: link:../typescript-config
|
||||
|
|
@ -3171,6 +3183,12 @@ importers:
|
|||
fast-glob:
|
||||
specifier: 'catalog:'
|
||||
version: 3.3.3
|
||||
oxlint:
|
||||
specifier: 'catalog:'
|
||||
version: 1.78.0(oxlint-tsgolint@0.21.1)
|
||||
oxlint-tsgolint:
|
||||
specifier: 'catalog:'
|
||||
version: 0.21.1
|
||||
p-limit:
|
||||
specifier: catalog:p-limit-v7
|
||||
version: 7.3.1
|
||||
|
|
|
|||
|
|
@ -41,6 +41,13 @@ Possible approaches:
|
|||
|
||||
Audit every package configuration before conversion. Do not copy the full generic naming rule into a new custom implementation.
|
||||
|
||||
Phase 3 decisions:
|
||||
|
||||
- `@n8n/agents`: retire the enum-member casing selector. It enforces style only.
|
||||
- `@n8n/instance-ai`: retire the quoted object-property selector. It only exempts names that require quotes and protects no API contract.
|
||||
- `@n8n/instance-ai`: remove the stale package-wide filename exception. The package no longer contains files that need it.
|
||||
- `@n8n/instance-ai`: override the removed Node 10 module resolution in the scripts tsconfig so tsgolint can keep linting `scripts/**/*.ts`.
|
||||
|
||||
### Testing infrastructure decisions
|
||||
|
||||
- `@n8n/rules-engine`, `@n8n/code-health`, and `@n8n/playwright-janitor`: retire the exemption-only rule-ID selectors. They enforce no positive naming contract.
|
||||
|
|
|
|||
|
|
@ -62,6 +62,9 @@
|
|||
"@n8n/ai-node-sdk#lint": {
|
||||
"dependsOn": ["^build", "@n8n/oxlint-config#build"]
|
||||
},
|
||||
"@n8n/agents#lint": {
|
||||
"dependsOn": ["^build", "@n8n/oxlint-config#build"]
|
||||
},
|
||||
"@n8n/ai-utilities#lint": {
|
||||
"dependsOn": ["^build", "@n8n/oxlint-config#build"]
|
||||
},
|
||||
|
|
@ -113,6 +116,9 @@
|
|||
"@n8n/imap#lint": {
|
||||
"dependsOn": ["^build", "@n8n/oxlint-config#build"]
|
||||
},
|
||||
"@n8n/instance-ai#lint": {
|
||||
"dependsOn": ["^build", "@n8n/oxlint-config#build"]
|
||||
},
|
||||
"@n8n/local-gateway#lint": {
|
||||
"dependsOn": ["^build", "@n8n/oxlint-config#build"]
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user