diff --git a/packages/@n8n/ai-utilities/package.json b/packages/@n8n/ai-utilities/package.json index 1cd79864366..5ecae4c18dc 100644 --- a/packages/@n8n/ai-utilities/package.json +++ b/packages/@n8n/ai-utilities/package.json @@ -69,11 +69,6 @@ "import": "./dist/esm/utils/http-proxy-agent.js", "require": "./dist/cjs/utils/http-proxy-agent.js" }, - "./lazy-import": { - "types": "./dist/esm/utils/lazy-import.d.ts", - "import": "./dist/esm/utils/lazy-import.js", - "require": "./dist/cjs/utils/lazy-import.js" - }, "./web-search": { "types": "./dist/esm/web-search/index.d.ts", "import": "./dist/esm/web-search/index.js", diff --git a/packages/@n8n/ai-utilities/src/index.ts b/packages/@n8n/ai-utilities/src/index.ts index 7ab80c5b4a6..816f06d8e3c 100644 --- a/packages/@n8n/ai-utilities/src/index.ts +++ b/packages/@n8n/ai-utilities/src/index.ts @@ -2,10 +2,6 @@ export { AI_NODE_SDK_VERSION } from './ai-node-sdk-version'; // Utils -// NOTE: `lazyImport` is intentionally NOT re-exported here — import it from the -// lightweight `@n8n/ai-utilities/lazy-import` subpath so callers that use it to -// defer loading provider SDKs don't eagerly pull this barrel (which statically -// imports @langchain/*). export { logWrapper } from './utils/log-wrapper'; export { logAiEvent } from './utils/log-ai-event'; export { parseSSEStream } from './utils/sse'; diff --git a/packages/@n8n/ai-workflow-builder.ee/package.json b/packages/@n8n/ai-workflow-builder.ee/package.json index c590d3babad..dd28bfe36a1 100644 --- a/packages/@n8n/ai-workflow-builder.ee/package.json +++ b/packages/@n8n/ai-workflow-builder.ee/package.json @@ -50,9 +50,9 @@ ], "exports": { ".": { + "types": "./dist/index.d.ts", "require": "./dist/index.js", - "import": "./src/index.ts", - "types": "./dist/index.d.ts" + "import": "./dist/index.js" } }, "dependencies": { diff --git a/packages/@n8n/ai-workflow-builder.ee/src/index.ts b/packages/@n8n/ai-workflow-builder.ee/src/index.ts index c6f56fb87a1..33a630fc573 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/index.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/index.ts @@ -41,3 +41,4 @@ export type { ParseAndValidateResult, ValidationWarning } from './code-builder'; // SSRF guard contract for the web_fetch tool (cli injects its SsrfProtectionService here) export { type SsrfGuard, createPassthroughSsrfGuard } from './tools/utils/ssrf-guard'; +export { type ChatPayload } from './workflow-builder-agent'; diff --git a/packages/@n8n/ai-workflow-builder.ee/src/llm-config.ts b/packages/@n8n/ai-workflow-builder.ee/src/llm-config.ts index 8dd177d7b63..bdbafb2cfdc 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/llm-config.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/llm-config.ts @@ -2,8 +2,8 @@ import type * as LangchainAnthropic from '@langchain/anthropic'; import type { BaseChatModel } from '@langchain/core/language_models/chat_models'; import type * as LangchainOpenAI from '@langchain/openai'; -import { lazyImport } from '@n8n/ai-utilities/lazy-import'; import type { CustomFetch } from '@n8n/backend-network/transport'; +import { lazyImport } from '@n8n/utils/lazy-import'; import { MAX_OUTPUT_TOKENS } from '@/constants'; diff --git a/packages/@n8n/db/package.json b/packages/@n8n/db/package.json index 0662e90172c..9da65e06ffc 100644 --- a/packages/@n8n/db/package.json +++ b/packages/@n8n/db/package.json @@ -61,6 +61,7 @@ "express": "catalog:", "testcontainers": "catalog:", "typescript": "catalog:", + "typescript6": "npm:@typescript/typescript6@6.0.2", "vite": "catalog:", "vitest": "catalog:", "vitest-mock-extended": "catalog:" diff --git a/packages/@n8n/db/vite.config.ts b/packages/@n8n/db/vite.config.ts index 7ceed4b8fe5..0cc5aa8ddbb 100644 --- a/packages/@n8n/db/vite.config.ts +++ b/packages/@n8n/db/vite.config.ts @@ -1,7 +1,9 @@ import { createVitestConfigWithDecorators } from '@n8n/vitest-config/node-decorators'; import fs from 'node:fs'; import path from 'node:path'; -import ts from 'typescript'; +// Uses the legacy JS compiler via the `typescript6` alias: tsgo (typescript 7) +// ships no programmatic API for this LanguageService-based entity transform. +import ts from 'typescript6'; import { mergeConfig, type Plugin } from 'vite'; import { configDefaults } from 'vitest/config'; diff --git a/packages/@n8n/eslint-config/src/configs/base.ts b/packages/@n8n/eslint-config/src/configs/base.ts index 77e47188879..5db8ea8b4e5 100644 --- a/packages/@n8n/eslint-config/src/configs/base.ts +++ b/packages/@n8n/eslint-config/src/configs/base.ts @@ -157,7 +157,7 @@ export const baseConfig = tseslint.config( /** * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-imports.md */ - '@typescript-eslint/consistent-type-imports': 'error', + '@typescript-eslint/consistent-type-imports': ['error', { disallowTypeAnnotations: false }], '@typescript-eslint/consistent-type-exports': 'error', diff --git a/packages/@n8n/instance-ai/package.json b/packages/@n8n/instance-ai/package.json index 85f21a60f8a..b924fdbaae0 100644 --- a/packages/@n8n/instance-ai/package.json +++ b/packages/@n8n/instance-ai/package.json @@ -35,9 +35,9 @@ ], "exports": { ".": { + "types": "./dist/index.d.ts", "require": "./dist/index.js", - "import": "./src/index.ts", - "types": "./dist/index.d.ts" + "import": "./dist/index.js" }, "./parsers": { "require": "./dist/parsers/index.js", diff --git a/packages/@n8n/task-runner/package.json b/packages/@n8n/task-runner/package.json index a010a2bf919..eb2a5e6a5c1 100644 --- a/packages/@n8n/task-runner/package.json +++ b/packages/@n8n/task-runner/package.json @@ -26,14 +26,14 @@ ], "exports": { "./start": { + "types": "./dist/start.d.ts", "require": "./dist/start.js", - "import": "./src/start.ts", - "types": "./dist/start.d.ts" + "import": "./src/start.ts" }, ".": { + "types": "./dist/index.d.ts", "require": "./dist/index.js", - "import": "./src/index.ts", - "types": "./dist/index.d.ts" + "import": "./src/index.ts" } }, "dependencies": { diff --git a/packages/@n8n/ai-utilities/src/utils/lazy-import.ts b/packages/@n8n/utils/src/lazy-import.ts similarity index 100% rename from packages/@n8n/ai-utilities/src/utils/lazy-import.ts rename to packages/@n8n/utils/src/lazy-import.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index 40a9f0ee259..f525317a2f8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -117,7 +117,8 @@ "ts-essentials": "^7.0.3", "tsconfig-paths": "^4.2.0", "@vitest/coverage-v8": "catalog:", - "typescript": "catalog:", + "typescript": "catalog:typescript", + "typescript6": "npm:@typescript/typescript6@6.0.2", "vite": "catalog:", "vitest": "catalog:", "vitest-mock-extended": "catalog:" diff --git a/packages/cli/src/__tests__/credentials-helper.test.ts b/packages/cli/src/__tests__/credentials-helper.test.ts index 2e05ecdd32f..0cd5a3104c9 100644 --- a/packages/cli/src/__tests__/credentials-helper.test.ts +++ b/packages/cli/src/__tests__/credentials-helper.test.ts @@ -1281,7 +1281,7 @@ describe('CredentialsHelper', () => { dynamicCredentialProxy.setResolverProvider(mockCredentialResolutionProvider); const { CredentialResolutionError } = await import( - '@/modules/dynamic-credentials.ee/errors/credential-resolution.error' + '@/modules/dynamic-credentials.ee/errors/credential-resolution.error.js' ); const resolvableCredentialEntity = { @@ -1315,7 +1315,7 @@ describe('CredentialsHelper', () => { dynamicCredentialProxy.setResolverProvider(mockCredentialResolutionProvider); const { CredentialResolutionError } = await import( - '@/modules/dynamic-credentials.ee/errors/credential-resolution.error' + '@/modules/dynamic-credentials.ee/errors/credential-resolution.error.js' ); const resolvableCredentialEntity = { diff --git a/packages/cli/src/__tests__/credentials-overwrites.test.ts b/packages/cli/src/__tests__/credentials-overwrites.test.ts index 03d3f0c9278..ae8c5b8b117 100644 --- a/packages/cli/src/__tests__/credentials-overwrites.test.ts +++ b/packages/cli/src/__tests__/credentials-overwrites.test.ts @@ -317,7 +317,7 @@ describe('CredentialsOverwrites', () => { }); // Mock Publisher service - need to import the class first - const { Publisher } = await import('@/scaling/pubsub/publisher.service'); + const { Publisher } = await import('@/scaling/pubsub/publisher.service.js'); publisherMock = { publishCommand: vi.fn() }; mockInstance(Publisher, publisherMock); @@ -582,7 +582,7 @@ describe('CredentialsOverwrites', () => { }); // Mock Publisher service - const { Publisher } = await import('@/scaling/pubsub/publisher.service'); + const { Publisher } = await import('@/scaling/pubsub/publisher.service.js'); publisherMock = { publishCommand: vi.fn() }; mockInstance(Publisher, publisherMock); @@ -972,7 +972,7 @@ describe('CredentialsOverwrites', () => { }); // Mock Publisher service - const { Publisher } = await import('@/scaling/pubsub/publisher.service'); + const { Publisher } = await import('@/scaling/pubsub/publisher.service.js'); publisherMock = { publishCommand: vi.fn() }; mockInstance(Publisher, publisherMock); diff --git a/packages/cli/src/__tests__/load-nodes-and-credentials.test.ts b/packages/cli/src/__tests__/load-nodes-and-credentials.test.ts index f4acf63b3d7..4359380d6c0 100644 --- a/packages/cli/src/__tests__/load-nodes-and-credentials.test.ts +++ b/packages/cli/src/__tests__/load-nodes-and-credentials.test.ts @@ -584,7 +584,7 @@ describe('LoadNodesAndCredentials', () => { beforeEach(async () => { // Import the mocked functions - const toolGeneration = await import('@/tool-generation'); + const toolGeneration = await import('@/tool-generation/index.js'); createAiTools = toolGeneration.createAiTools as Mock; createHitlTools = toolGeneration.createHitlTools as Mock; diff --git a/packages/cli/src/commands/__tests__/webhook.test.ts b/packages/cli/src/commands/__tests__/webhook.test.ts index 922b7415bb0..0080841d4ba 100644 --- a/packages/cli/src/commands/__tests__/webhook.test.ts +++ b/packages/cli/src/commands/__tests__/webhook.test.ts @@ -71,7 +71,7 @@ describe('Webhook', () => { describe('run', () => { beforeEach(async () => { - const { ScalingService } = await import('@/scaling/scaling.service'); + const { ScalingService } = await import('@/scaling/scaling.service.js'); Container.set(ScalingService, { setupQueue: vi.fn() } as unknown as InstanceType< typeof ScalingService >); diff --git a/packages/cli/src/commands/audit.ts b/packages/cli/src/commands/audit.ts index 742e9574783..a62a5c2092f 100644 --- a/packages/cli/src/commands/audit.ts +++ b/packages/cli/src/commands/audit.ts @@ -71,7 +71,7 @@ export class SecurityAudit extends BaseCommand> { throw new UserError([message, hint].join('. ')); } - const { SecurityAuditService } = await import('@/security-audit/security-audit.service'); + const { SecurityAuditService } = await import('@/security-audit/security-audit.service.js'); const result = await Container.get(SecurityAuditService).run( categories, diff --git a/packages/cli/src/commands/base-command.ts b/packages/cli/src/commands/base-command.ts index dd4901a8d3a..7eb25ff6555 100644 --- a/packages/cli/src/commands/base-command.ts +++ b/packages/cli/src/commands/base-command.ts @@ -141,7 +141,7 @@ export abstract class BaseCommand { this.globalConfig.multiMainSetup.enabled || this.globalConfig.cache.backend === 'redis'; if (useRedisForLocking) { - const { RedisLockService } = await import('@/scaling/redis-lock.service'); + const { RedisLockService } = await import('@/scaling/redis-lock.service.js'); Container.get(LockService).setProvider(Container.get(RedisLockService)); } @@ -194,7 +194,7 @@ export abstract class BaseCommand { ); } - const { TaskRunnerModule } = await import('@/task-runners/task-runner-module'); + const { TaskRunnerModule } = await import('@/task-runners/task-runner-module.js'); await Container.get(TaskRunnerModule).start(); } @@ -227,7 +227,7 @@ export abstract class BaseCommand { const communityPackagesConfig = Container.get(CommunityPackagesConfig); if (communityPackagesConfig.enabled && this.needsCommunityPackages) { const { CommunityPackagesService } = await import( - '@/modules/community-packages/community-packages.service' + '@/modules/community-packages/community-packages.service.js' ); await Container.get(CommunityPackagesService).init(); } @@ -269,7 +269,7 @@ export abstract class BaseCommand { const isS3WriteMode = binaryDataConfig.mode === 's3'; const isAzureWriteMode = binaryDataConfig.mode === 'azure'; - const { DatabaseManager } = await import('@/binary-data/database.manager'); + const { DatabaseManager } = await import('@/binary-data/database.manager.js'); binaryDataService.setManager('database', Container.get(DatabaseManager)); if (isS3WriteMode) { @@ -340,7 +340,7 @@ export abstract class BaseCommand { const objectStoreService = await this.initObjectStoreIfConfigured(); if (objectStoreService) { const { ObjectStoreManager } = await import( - 'n8n-core/dist/binary-data/object-store.manager' + 'n8n-core/dist/binary-data/object-store.manager.js' ); binaryDataService.setManager('s3', new ObjectStoreManager(objectStoreService)); } @@ -354,7 +354,9 @@ export abstract class BaseCommand { try { const azureBlobService = await this.initAzureStoreIfConfigured(); if (azureBlobService) { - const { AzureBlobManager } = await import('n8n-core/dist/binary-data/azure-blob.manager'); + const { AzureBlobManager } = await import( + 'n8n-core/dist/binary-data/azure-blob.manager.js' + ); binaryDataService.setManager('azure', new AzureBlobManager(azureBlobService)); } } catch { @@ -373,12 +375,12 @@ export abstract class BaseCommand { if (Container.get(ObjectStoreConfig).bucket.name === '') return undefined; const { ObjectStoreService } = await import( - 'n8n-core/dist/binary-data/object-store/object-store.service.ee' + 'n8n-core/dist/binary-data/object-store/object-store.service.ee.js' ); const objectStoreService = Container.get(ObjectStoreService); await objectStoreService.init(); - const { S3ByteStore } = await import('@/blob-storage/s3-byte-store.ee'); + const { S3ByteStore } = await import('@/blob-storage/s3-byte-store.ee.js'); Container.get(ExecutionDataJsonStore).registerByteStore( 's3', new S3ByteStore(objectStoreService), @@ -391,12 +393,12 @@ export abstract class BaseCommand { if (Container.get(AzureBlobConfig).containerName === '') return; const { AzureBlobService } = await import( - 'n8n-core/dist/binary-data/azure-blob/azure-blob.service.ee' + 'n8n-core/dist/binary-data/azure-blob/azure-blob.service.ee.js' ); const azureBlobService = Container.get(AzureBlobService); await azureBlobService.init(); - const { AzureByteStore } = await import('@/blob-storage/azure-byte-store.ee'); + const { AzureByteStore } = await import('@/blob-storage/azure-byte-store.ee.js'); Container.get(ExecutionDataJsonStore).registerByteStore( 'az', new AzureByteStore(azureBlobService), diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index 0bc01b51b21..89477d2c72d 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -284,7 +284,7 @@ export class Start extends BaseCommand> { await this.moduleRegistry.initModules(this.instanceSettings.instanceType); // Initialize auth handler registry after modules are loaded - const { AuthHandlerRegistry } = await import('@/auth/auth-handler.registry'); + const { AuthHandlerRegistry } = await import('@/auth/auth-handler.registry.js'); await Container.get(AuthHandlerRegistry).init(); if (this.instanceSettings.isMultiMain) { @@ -297,7 +297,7 @@ export class Start extends BaseCommand> { private async initInstanceSettingsLoader(): Promise { const { InstanceSettingsLoaderService } = await import( - '@/instance-settings-loader/instance-settings-loader.service' + '@/instance-settings-loader/instance-settings-loader.service.js' ); await Container.get(InstanceSettingsLoaderService).init(); } @@ -411,21 +411,21 @@ export class Start extends BaseCommand> { // Start to get active workflows and run their triggers if (this.globalConfig.workflows.useWorkflowPublicationService) { const { PublishedWorkflowEnqueuer } = await import( - '@/workflows/publication/published-workflow-enqueuer' + '@/workflows/publication/published-workflow-enqueuer.js' ); const { WorkflowPublicationOutboxConsumer } = await import( - '@/workflows/publication/workflow-publication-outbox-consumer' + '@/workflows/publication/workflow-publication-outbox-consumer.js' ); const { WorkflowPublicationOutboxCleanupService } = await import( - '@/workflows/publication/workflow-publication-outbox-cleanup.service' + '@/workflows/publication/workflow-publication-outbox-cleanup.service.js' ); const { WorkflowPublicationReconciler } = await import( - '@/workflows/publication/workflow-publication-reconciler.service' + '@/workflows/publication/workflow-publication-reconciler.service.js' ); // Import for its side effect: registering the trigger deactivator's // @OnLeaderStepdown and @OnShutdown handlers. Nothing else loads this module. - await import('@/workflows/publication/published-workflow-trigger-deactivator'); + await import('@/workflows/publication/published-workflow-trigger-deactivator.js'); // Enqueue needs to happen before outbox consumer init, so it can activate // everything on the first drain diff --git a/packages/cli/src/commands/webhook.ts b/packages/cli/src/commands/webhook.ts index a7486e62eea..84dd9c3837b 100644 --- a/packages/cli/src/commands/webhook.ts +++ b/packages/cli/src/commands/webhook.ts @@ -98,7 +98,7 @@ export class Webhook extends BaseCommand { } async run() { - const { ScalingService } = await import('@/scaling/scaling.service'); + const { ScalingService } = await import('@/scaling/scaling.service.js'); await Container.get(ScalingService).setupQueue(); await this.server.start(); this.server.markAsReady(); diff --git a/packages/cli/src/commands/worker.ts b/packages/cli/src/commands/worker.ts index 3c5dd9c92de..664bf98fbbc 100644 --- a/packages/cli/src/commands/worker.ts +++ b/packages/cli/src/commands/worker.ts @@ -178,7 +178,7 @@ export class Worker extends BaseCommand> { } async initScalingService() { - const { ScalingService } = await import('@/scaling/scaling.service'); + const { ScalingService } = await import('@/scaling/scaling.service.js'); this.scalingService = Container.get(ScalingService); await this.scalingService.setupQueue(); @@ -193,7 +193,7 @@ export class Worker extends BaseCommand> { let workerServer: WorkerServer | undefined; if (Object.values(endpointsConfig).some((e) => e)) { - const { WorkerServer } = await import('@/scaling/worker-server'); + const { WorkerServer } = await import('@/scaling/worker-server.js'); workerServer = Container.get(WorkerServer); await workerServer.init(endpointsConfig); } diff --git a/packages/cli/src/credentials-overwrites.ts b/packages/cli/src/credentials-overwrites.ts index f07089eb481..475ddb63b8e 100644 --- a/packages/cli/src/credentials-overwrites.ts +++ b/packages/cli/src/credentials-overwrites.ts @@ -77,7 +77,7 @@ export class CredentialsOverwrites { } private async broadcastReloadOverwriteCredentialsCommand(): Promise { - const { Publisher } = await import('@/scaling/pubsub/publisher.service'); + const { Publisher } = await import('@/scaling/pubsub/publisher.service.js'); await Container.get(Publisher).publishCommand({ command: 'reload-overwrite-credentials' }); } @@ -133,7 +133,7 @@ export class CredentialsOverwrites { private async reloadFrontendService() { // FrontendService has CredentialOverwrites injected via the constructor // to break the circular dependency we need to use the container to get the instance - const { FrontendService } = await import('./services/frontend.service'); + const { FrontendService } = await import('./services/frontend.service.js'); await Container.get(FrontendService)?.generateTypes(); } diff --git a/packages/cli/src/events/relays/telemetry.event-relay.ts b/packages/cli/src/events/relays/telemetry.event-relay.ts index b48284116eb..e4835c9d51f 100644 --- a/packages/cli/src/events/relays/telemetry.event-relay.ts +++ b/packages/cli/src/events/relays/telemetry.event-relay.ts @@ -1555,7 +1555,7 @@ export class TelemetryEventRelay extends EventRelay { } private async getOtelTelemetryInfo() { - const { OtelConfig } = await import('@/modules/otel/otel.config'); + const { OtelConfig } = await import('@/modules/otel/otel.config.js'); const otelConfig = Container.get(OtelConfig); return { diff --git a/packages/cli/src/license.ts b/packages/cli/src/license.ts index e8c52a84348..1f16b437b40 100644 --- a/packages/cli/src/license.ts +++ b/packages/cli/src/license.ts @@ -155,7 +155,7 @@ export class License implements LicenseProvider { private async broadcastReloadLicenseCommand() { if (this.globalConfig.executions.mode === 'queue' && this.instanceSettings.isLeader) { - const { Publisher } = await import('@/scaling/pubsub/publisher.service'); + const { Publisher } = await import('@/scaling/pubsub/publisher.service.js'); await Container.get(Publisher).publishCommand({ command: 'reload-license' }); } } diff --git a/packages/cli/src/load-nodes-and-credentials.ts b/packages/cli/src/load-nodes-and-credentials.ts index 7af84f603bd..16a894dc62d 100644 --- a/packages/cli/src/load-nodes-and-credentials.ts +++ b/packages/cli/src/load-nodes-and-credentials.ts @@ -656,11 +656,11 @@ export class LoadNodesAndCredentials { } async setupHotReload() { - const { default: debounce } = await import('lodash/debounce'); + const { default: debounce } = await import('lodash/debounce.js'); const { subscribe } = await import('@parcel/watcher'); - const { Push } = await import('@/push'); + const { Push } = await import('@/push/index.js'); const push = Container.get(Push); for (const loader of Object.values(this.loaders)) { diff --git a/packages/cli/src/modules/agents/agent-publish.service.ts b/packages/cli/src/modules/agents/agent-publish.service.ts index 17980277b7f..5d312d90907 100644 --- a/packages/cli/src/modules/agents/agent-publish.service.ts +++ b/packages/cli/src/modules/agents/agent-publish.service.ts @@ -108,7 +108,7 @@ export class AgentPublishService { ); } - const { AgentTaskService } = await import('./agent-task.service'); + const { AgentTaskService } = await import('./agent-task.service.js'); await Container.get(AgentTaskService) .requestReconcile(agentId) .catch((error) => @@ -145,7 +145,7 @@ export class AgentPublishService { }); } - const { AgentTaskService } = await import('./agent-task.service'); + const { AgentTaskService } = await import('./agent-task.service.js'); await Container.get(AgentTaskService) .requestReconcile(agentId) .catch((error) => diff --git a/packages/cli/src/modules/agents/agent-runtime-reconstruction.service.ts b/packages/cli/src/modules/agents/agent-runtime-reconstruction.service.ts index a2387458feb..a3fc28e3f21 100644 --- a/packages/cli/src/modules/agents/agent-runtime-reconstruction.service.ts +++ b/packages/cli/src/modules/agents/agent-runtime-reconstruction.service.ts @@ -113,14 +113,14 @@ export interface ReconstructAgentRuntimeParams { async function getChatIntegrationToolServices() { const { IntegrationMessageContextService } = await import( - './integrations/integration-message-context.service' + './integrations/integration-message-context.service.js' ); // eslint-disable-next-line import-x/no-cycle const { ChatIntegrationActionExecutor } = await import( - './integrations/integration-action-executor' + './integrations/integration-action-executor.js' ); const { ChatIntegrationContextQueryExecutor } = await import( - './integrations/integration-context-query-executor' + './integrations/integration-context-query-executor.js' ); return { @@ -131,7 +131,7 @@ async function getChatIntegrationToolServices() { } async function getWorkflowRunner(): Promise { - const { WorkflowRunner } = await import('@/workflow-runner'); + const { WorkflowRunner } = await import('@/workflow-runner.js'); return Container.get(WorkflowRunner); } @@ -467,7 +467,7 @@ export class AgentRuntimeReconstructionService { private makeToolResolver(projectId: string): ToolResolver { return async (ref: AgentJsonToolConfig) => { if (ref.type === 'workflow') { - const { resolveWorkflowTool } = await import('./tools/workflow-tool-factory'); + const { resolveWorkflowTool } = await import('./tools/workflow-tool-factory.js'); return await resolveWorkflowTool(ref, { workflowRepository: this.workflowRepository, workflowRunner: await getWorkflowRunner(), @@ -478,7 +478,7 @@ export class AgentRuntimeReconstructionService { } if (ref.type === 'node') { - const { resolveNodeTool } = await import('./tools/node-tool-factory'); + const { resolveNodeTool } = await import('./tools/node-tool-factory.js'); return await resolveNodeTool(ref, { executor: this.ephemeralNodeExecutor, projectId, @@ -524,7 +524,7 @@ export class AgentRuntimeReconstructionService { (await this.agentFileRepository.hasFilesForAgent(agentId)) ) { const { createKnowledgeRetrievalTools } = await import( - './tools/knowledge/search-knowledge.tool' + './tools/knowledge/search-knowledge.tool.js' ); agent.tool( createKnowledgeRetrievalTools({ diff --git a/packages/cli/src/modules/agents/agent-skills.service.ts b/packages/cli/src/modules/agents/agent-skills.service.ts index d1fb1624fdf..54470f8e72b 100644 --- a/packages/cli/src/modules/agents/agent-skills.service.ts +++ b/packages/cli/src/modules/agents/agent-skills.service.ts @@ -197,7 +197,7 @@ export class AgentSkillsService { } private async clearRuntimes(agentId: string): Promise { - const { AgentRuntimeCacheService } = await import('./agent-runtime-cache.service'); + const { AgentRuntimeCacheService } = await import('./agent-runtime-cache.service.js'); Container.get(AgentRuntimeCacheService).clearRuntimes(agentId); } } diff --git a/packages/cli/src/modules/agents/agents.module.ts b/packages/cli/src/modules/agents/agents.module.ts index 5f8fdfa0cab..0d0cb3ffe92 100644 --- a/packages/cli/src/modules/agents/agents.module.ts +++ b/packages/cli/src/modules/agents/agents.module.ts @@ -8,50 +8,52 @@ import { InstanceSettings } from 'n8n-core'; @BackendModule({ name: 'agents' }) export class AgentsModule implements ModuleInterface { async init() { - await import('./agents-catalog.controller'); - await import('./agent-threads.controller'); - await import('./agents.controller'); - await import('./agents-config.controller'); - await import('./agents-skills.controller'); - await import('./agent-knowledge.controller'); - await import('./agent-publish.controller'); - await import('./agent-chat.controller'); - await import('./agent-integrations.controller'); - await import('./agent-vector-stores.controller'); - await import('./agent-tasks.controller'); - await import('./agent-sandbox.controller'); - await import('./agents-list.controller'); - await import('./builder/agents-builder-settings.controller'); + await import('./agents-catalog.controller.js'); + await import('./agent-threads.controller.js'); + await import('./agents.controller.js'); + await import('./agents-config.controller.js'); + await import('./agents-skills.controller.js'); + await import('./agent-knowledge.controller.js'); + await import('./agent-publish.controller.js'); + await import('./agent-chat.controller.js'); + await import('./agent-integrations.controller.js'); + await import('./agent-vector-stores.controller.js'); + await import('./agent-tasks.controller.js'); + await import('./agent-sandbox.controller.js'); + await import('./agents-list.controller.js'); + await import('./builder/agents-builder-settings.controller.js'); - const { AgentsService } = await import('./agents.service'); + const { AgentsService } = await import('./agents.service.js'); Container.get(AgentsService); const { AgentsBuilderSettingsService } = await import( - './builder/agents-builder-settings.service' + './builder/agents-builder-settings.service.js' ); Container.get(AgentsBuilderSettingsService); - const { AgentExecutionService } = await import('./agent-execution.service'); + const { AgentExecutionService } = await import('./agent-execution.service.js'); Container.get(AgentExecutionService); - const { AgentRuntimeCacheService } = await import('./agent-runtime-cache.service'); + const { AgentRuntimeCacheService } = await import('./agent-runtime-cache.service.js'); Container.get(AgentRuntimeCacheService); - const { AgentHistoryRepository } = await import('./repositories/agent-history.repository'); + const { AgentHistoryRepository } = await import('./repositories/agent-history.repository.js'); Container.get(AgentHistoryRepository); // Register the sandboxed runtime service (lazy — the V8 isolate is only // created on first use, so this import has negligible startup cost). - const { AgentSecureRuntime } = await import('./runtime/agent-secure-runtime'); + const { AgentSecureRuntime } = await import('./runtime/agent-secure-runtime.js'); Container.get(AgentSecureRuntime); // Populate the integration registry with supported chat platforms. // Adding a new platform is adding one subclass + one register() call. - const { ChatIntegrationRegistry } = await import('./integrations/agent-chat-integration'); - const { SlackIntegration } = await import('./integrations/platforms/slack-integration'); - const { TelegramIntegration } = await import('./integrations/platforms/telegram-integration'); - const { LinearIntegration } = await import('./integrations/platforms/linear-integration'); - const { N8nChatIntegration } = await import('./integrations/platforms/n8n-chat-integration'); + const { ChatIntegrationRegistry } = await import('./integrations/agent-chat-integration.js'); + const { SlackIntegration } = await import('./integrations/platforms/slack-integration.js'); + const { TelegramIntegration } = await import( + './integrations/platforms/telegram-integration.js' + ); + const { LinearIntegration } = await import('./integrations/platforms/linear-integration.js'); + const { N8nChatIntegration } = await import('./integrations/platforms/n8n-chat-integration.js'); const registry = Container.get(ChatIntegrationRegistry); registry.register(Container.get(SlackIntegration)); registry.register(Container.get(TelegramIntegration)); @@ -69,8 +71,8 @@ export class AgentsModule implements ModuleInterface { // // Tasks remain leader-only by design — a cron firing on multiple // mains would run the agent twice for the same tick. - const { ChatIntegrationService } = await import('./integrations/chat-integration.service'); - const { AgentTaskService } = await import('./agent-task.service'); + const { ChatIntegrationService } = await import('./integrations/chat-integration.service.js'); + const { AgentTaskService } = await import('./agent-task.service.js'); const chatService = Container.get(ChatIntegrationService); const taskService = Container.get(AgentTaskService); const logger = Container.get(Logger); @@ -93,7 +95,7 @@ export class AgentsModule implements ModuleInterface { async settings() { const config = Container.get(AgentsConfig); - const { isAgentKnowledgeBaseEnabled } = await import('./agent-knowledge-gate'); + const { isAgentKnowledgeBaseEnabled } = await import('./agent-knowledge-gate.js'); return { enabled: true, modules: [...config.modules], @@ -102,33 +104,35 @@ export class AgentsModule implements ModuleInterface { } async entities() { - const { Agent } = await import('./entities/agent.entity'); - const { AgentFile } = await import('./entities/agent-file.entity'); - const { AgentChatSubscription } = await import('./entities/agent-chat-subscription.entity'); - const { AgentCheckpoint } = await import('./entities/agent-checkpoint.entity'); - const { AgentResourceEntity } = await import('./entities/agent-resource.entity'); - const { AgentThreadEntity } = await import('./entities/agent-thread.entity'); - const { AgentMessageEntity } = await import('./entities/agent-message.entity'); - const { AgentExecutionThread } = await import('./entities/agent-execution-thread.entity'); - const { AgentExecution } = await import('./entities/agent-execution.entity'); - const { AgentHistory } = await import('./entities/agent-history.entity'); - const { AgentTask } = await import('./entities/agent-task.entity'); - const { AgentTaskRunLock } = await import('./entities/agent-task-run-lock.entity'); - const { AgentTaskSnapshot } = await import('./entities/agent-task-snapshot.entity'); - const { AgentObservationEntity } = await import('./entities/agent-observation.entity'); + const { Agent } = await import('./entities/agent.entity.js'); + const { AgentFile } = await import('./entities/agent-file.entity.js'); + const { AgentChatSubscription } = await import('./entities/agent-chat-subscription.entity.js'); + const { AgentCheckpoint } = await import('./entities/agent-checkpoint.entity.js'); + const { AgentResourceEntity } = await import('./entities/agent-resource.entity.js'); + const { AgentThreadEntity } = await import('./entities/agent-thread.entity.js'); + const { AgentMessageEntity } = await import('./entities/agent-message.entity.js'); + const { AgentExecutionThread } = await import('./entities/agent-execution-thread.entity.js'); + const { AgentExecution } = await import('./entities/agent-execution.entity.js'); + const { AgentHistory } = await import('./entities/agent-history.entity.js'); + const { AgentTask } = await import('./entities/agent-task.entity.js'); + const { AgentTaskRunLock } = await import('./entities/agent-task-run-lock.entity.js'); + const { AgentTaskSnapshot } = await import('./entities/agent-task-snapshot.entity.js'); + const { AgentObservationEntity } = await import('./entities/agent-observation.entity.js'); const { AgentObservationCursorEntity } = await import( - './entities/agent-observation-cursor.entity' + './entities/agent-observation-cursor.entity.js' ); - const { AgentObservationLockEntity } = await import('./entities/agent-observation-lock.entity'); - const { AgentMemoryEntryEntity } = await import('./entities/agent-memory-entry.entity'); + const { AgentObservationLockEntity } = await import( + './entities/agent-observation-lock.entity.js' + ); + const { AgentMemoryEntryEntity } = await import('./entities/agent-memory-entry.entity.js'); const { AgentMemoryEntryLockEntity } = await import( - './entities/agent-memory-entry-lock.entity' + './entities/agent-memory-entry-lock.entity.js' ); const { AgentMemoryEntrySourceEntity } = await import( - './entities/agent-memory-entry-source.entity' + './entities/agent-memory-entry-source.entity.js' ); const { AgentMemoryEntryCursorEntity } = await import( - './entities/agent-memory-entry-cursor.entity' + './entities/agent-memory-entry-cursor.entity.js' ); return [ @@ -156,7 +160,7 @@ export class AgentsModule implements ModuleInterface { } async context() { - const { AgentsService } = await import('./agents.service'); + const { AgentsService } = await import('./agents.service.js'); return { agentsService: Container.get(AgentsService) }; } diff --git a/packages/cli/src/modules/agents/agents.service.ts b/packages/cli/src/modules/agents/agents.service.ts index b56a7655941..2c3510997e7 100644 --- a/packages/cli/src/modules/agents/agents.service.ts +++ b/packages/cli/src/modules/agents/agents.service.ts @@ -219,7 +219,7 @@ export class AgentsService { this.eventService.emit('agent-deleted', { agentId, projectId }); try { - const { AgentTaskService } = await import('./agent-task.service'); + const { AgentTaskService } = await import('./agent-task.service.js'); await Container.get(AgentTaskService).requestReconcile(agentId); } catch (error) { this.logger.warn('Failed to stop tasks on agent delete', { diff --git a/packages/cli/src/modules/agents/integrations/chat-integration.service.ts b/packages/cli/src/modules/agents/integrations/chat-integration.service.ts index 10f03d83261..7663cfc4e40 100644 --- a/packages/cli/src/modules/agents/integrations/chat-integration.service.ts +++ b/packages/cli/src/modules/agents/integrations/chat-integration.service.ts @@ -89,7 +89,7 @@ interface DisconnectChannelOptions { async function getAgentExecutionOrchestratorService() { // eslint-disable-next-line import-x/no-cycle const { AgentExecutionOrchestratorService } = await import( - '../agent-execution-orchestrator.service' + '../agent-execution-orchestrator.service.js' ); return Container.get(AgentExecutionOrchestratorService); } diff --git a/packages/cli/src/modules/agents/integrations/integrations-sync.ts b/packages/cli/src/modules/agents/integrations/integrations-sync.ts index e295d1b1489..82ba80a1fbc 100644 --- a/packages/cli/src/modules/agents/integrations/integrations-sync.ts +++ b/packages/cli/src/modules/agents/integrations/integrations-sync.ts @@ -23,7 +23,7 @@ export async function syncAgentIntegrations( ): Promise { try { // eslint-disable-next-line import-x/no-cycle - const { ChatIntegrationService } = await import('./chat-integration.service'); + const { ChatIntegrationService } = await import('./chat-integration.service.js'); await Container.get(ChatIntegrationService).syncToConfig(agent, previous, next); } catch (error) { logger.warn('Failed to sync chat integrations', { diff --git a/packages/cli/src/modules/agents/sub-agents/sub-agent-foreground-runner.ts b/packages/cli/src/modules/agents/sub-agents/sub-agent-foreground-runner.ts index 1b4f0f5fa5e..e3aae2d513f 100644 --- a/packages/cli/src/modules/agents/sub-agents/sub-agent-foreground-runner.ts +++ b/packages/cli/src/modules/agents/sub-agents/sub-agent-foreground-runner.ts @@ -240,7 +240,7 @@ export class SubAgentForegroundRunner { async function getReconstructionService() { // eslint-disable-next-line import-x/no-cycle const { AgentRuntimeReconstructionService } = await import( - '../agent-runtime-reconstruction.service' + '../agent-runtime-reconstruction.service.js' ); return Container.get(AgentRuntimeReconstructionService); } diff --git a/packages/cli/src/modules/breaking-changes/breaking-changes.module.ts b/packages/cli/src/modules/breaking-changes/breaking-changes.module.ts index 35ab18d08dd..5dd67126ac7 100644 --- a/packages/cli/src/modules/breaking-changes/breaking-changes.module.ts +++ b/packages/cli/src/modules/breaking-changes/breaking-changes.module.ts @@ -9,12 +9,12 @@ export class BreakingChangesModule implements ModuleInterface { if (!MIGRATION_REPORT_TARGET_VERSION) return; // Import rules so that they are added to the BreakingChangeRuleMetadata registry - await import('./rules'); + await import('./rules/index.js'); // Register rules in the service - const { BreakingChangeService } = await import('./breaking-changes.service'); + const { BreakingChangeService } = await import('./breaking-changes.service.js'); Container.get(BreakingChangeService).registerRules(); - await import('./breaking-changes.controller'); + await import('./breaking-changes.controller.js'); } } diff --git a/packages/cli/src/modules/chat-hub/chat-hub.module.ts b/packages/cli/src/modules/chat-hub/chat-hub.module.ts index 0d817975038..a9939fae329 100644 --- a/packages/cli/src/modules/chat-hub/chat-hub.module.ts +++ b/packages/cli/src/modules/chat-hub/chat-hub.module.ts @@ -7,9 +7,9 @@ import { InstanceSettings } from 'n8n-core'; @BackendModule({ name: 'chat-hub' }) export class ChatHubModule implements ModuleInterface { async init() { - await import('./chat-hub.controller'); - await import('./chat-hub.settings.controller'); - const { ChatHubEventRelay } = await import('./chat-hub-event-relay.service'); + await import('./chat-hub.controller.js'); + await import('./chat-hub.settings.controller.js'); + const { ChatHubEventRelay } = await import('./chat-hub-event-relay.service.js'); Container.get(ChatHubEventRelay); @@ -18,12 +18,12 @@ export class ChatHubModule implements ModuleInterface { const isQueueMode = Container.get(ExecutionsConfig).mode === 'queue'; const isWorker = Container.get(InstanceSettings).isWorker; if (!isQueueMode || isWorker) { - await import('./chat-hub-execution-watcher.service'); + await import('./chat-hub-execution-watcher.service.js'); } } async settings() { - const { ChatHubSettingsService } = await import('./chat-hub.settings.service'); + const { ChatHubSettingsService } = await import('./chat-hub.settings.service.js'); const service = Container.get(ChatHubSettingsService); const [enabled, providers, semanticSearch] = await Promise.all([ service.getEnabled(), @@ -40,10 +40,10 @@ export class ChatHubModule implements ModuleInterface { } async entities() { - const { ChatHubSession } = await import('./chat-hub-session.entity'); - const { ChatHubMessage } = await import('./chat-hub-message.entity'); - const { ChatHubAgent } = await import('./chat-hub-agent.entity'); - const { ChatHubTool } = await import('./chat-hub-tool.entity'); + const { ChatHubSession } = await import('./chat-hub-session.entity.js'); + const { ChatHubMessage } = await import('./chat-hub-message.entity.js'); + const { ChatHubAgent } = await import('./chat-hub-agent.entity.js'); + const { ChatHubTool } = await import('./chat-hub-tool.entity.js'); return [ChatHubSession, ChatHubMessage, ChatHubAgent, ChatHubTool]; } diff --git a/packages/cli/src/modules/community-packages/__tests__/npm-utils.test.ts b/packages/cli/src/modules/community-packages/__tests__/npm-utils.test.ts index 88947e6388e..309f60e8d8c 100644 --- a/packages/cli/src/modules/community-packages/__tests__/npm-utils.test.ts +++ b/packages/cli/src/modules/community-packages/__tests__/npm-utils.test.ts @@ -453,7 +453,7 @@ describe('executeNpmCommand', () => { const importFreshModule = async () => { vi.resetModules(); - return await import('../npm-utils'); + return await import('../npm-utils.js'); }; beforeEach(() => { diff --git a/packages/cli/src/modules/community-packages/community-packages.module.ts b/packages/cli/src/modules/community-packages/community-packages.module.ts index 752bf81673d..a892e3bc198 100644 --- a/packages/cli/src/modules/community-packages/community-packages.module.ts +++ b/packages/cli/src/modules/community-packages/community-packages.module.ts @@ -8,23 +8,23 @@ import path from 'node:path'; @BackendModule({ name: 'community-packages' }) export class CommunityPackagesModule implements ModuleInterface { async init() { - await import('./community-packages.controller'); - await import('./community-node-types.controller'); + await import('./community-packages.controller.js'); + await import('./community-node-types.controller.js'); } async commands() { - await import('./community-node.command'); + await import('./community-node.command.js'); } async entities() { - const { InstalledNodes } = await import('./installed-nodes.entity'); - const { InstalledPackages } = await import('./installed-packages.entity'); + const { InstalledNodes } = await import('./installed-nodes.entity.js'); + const { InstalledPackages } = await import('./installed-packages.entity.js'); return [InstalledNodes, InstalledPackages] as EntityClass[]; } async settings() { - const { CommunityPackagesConfig } = await import('./community-packages.config'); + const { CommunityPackagesConfig } = await import('./community-packages.config.js'); return { communityNodesEnabled: Container.get(CommunityPackagesConfig).enabled, @@ -33,7 +33,7 @@ export class CommunityPackagesModule implements ModuleInterface { } async nodeLoaders() { - const { CommunityPackagesConfig } = await import('./community-packages.config'); + const { CommunityPackagesConfig } = await import('./community-packages.config.js'); if (Container.get(CommunityPackagesConfig).preventLoading) return []; const dir = path.join(Container.get(InstanceSettings).nodesDownloadDir, 'node_modules'); diff --git a/packages/cli/src/modules/data-table/data-table.module.ts b/packages/cli/src/modules/data-table/data-table.module.ts index fb3302e6aae..41159f43127 100644 --- a/packages/cli/src/modules/data-table/data-table.module.ts +++ b/packages/cli/src/modules/data-table/data-table.module.ts @@ -5,15 +5,15 @@ import { Container } from '@n8n/di'; @BackendModule({ name: 'data-table' }) export class DataTableModule implements ModuleInterface { async init() { - await import('./data-table.controller'); - await import('./data-table-aggregate.controller'); - await import('./data-table-uploads.controller'); + await import('./data-table.controller.js'); + await import('./data-table-aggregate.controller.js'); + await import('./data-table-uploads.controller.js'); - const { DataTableService } = await import('./data-table.service'); + const { DataTableService } = await import('./data-table.service.js'); await Container.get(DataTableService).start(); const { OwnershipTransferHandlerRegistry } = await import( - '@/services/ownership-transfer/ownership-transfer-handler.registry' + '@/services/ownership-transfer/ownership-transfer-handler.registry.js' ); Container.get(OwnershipTransferHandlerRegistry).register({ resource: 'data-table', @@ -29,34 +29,34 @@ export class DataTableModule implements ModuleInterface { }, }); - const { DataTableAggregateService } = await import('./data-table-aggregate.service'); + const { DataTableAggregateService } = await import('./data-table-aggregate.service.js'); await Container.get(DataTableAggregateService).start(); - const { DataTableFileCleanupService } = await import('./data-table-file-cleanup.service'); + const { DataTableFileCleanupService } = await import('./data-table-file-cleanup.service.js'); await Container.get(DataTableFileCleanupService).start(); } @OnShutdown() async shutdown() { - const { DataTableService } = await import('./data-table.service'); + const { DataTableService } = await import('./data-table.service.js'); await Container.get(DataTableService).shutdown(); - const { DataTableAggregateService } = await import('./data-table-aggregate.service'); + const { DataTableAggregateService } = await import('./data-table-aggregate.service.js'); await Container.get(DataTableAggregateService).shutdown(); - const { DataTableFileCleanupService } = await import('./data-table-file-cleanup.service'); + const { DataTableFileCleanupService } = await import('./data-table-file-cleanup.service.js'); await Container.get(DataTableFileCleanupService).shutdown(); } async entities() { - const { DataTable } = await import('./data-table.entity'); - const { DataTableColumn } = await import('./data-table-column.entity'); + const { DataTable } = await import('./data-table.entity.js'); + const { DataTableColumn } = await import('./data-table-column.entity.js'); return [DataTable, DataTableColumn]; } async context() { - const { DataTableProxyService } = await import('./data-table-proxy.service'); + const { DataTableProxyService } = await import('./data-table-proxy.service.js'); return { dataTableProxyProvider: Container.get(DataTableProxyService) }; } diff --git a/packages/cli/src/modules/data-table/data-table.service.ts b/packages/cli/src/modules/data-table/data-table.service.ts index afb14682c75..63314b613c7 100644 --- a/packages/cli/src/modules/data-table/data-table.service.ts +++ b/packages/cli/src/modules/data-table/data-table.service.ts @@ -332,7 +332,7 @@ export class DataTableService { return result; } - async upsertRow( + async upsertRow( dataTableId: string, projectId: string, dto: Omit, @@ -433,7 +433,7 @@ export class DataTableService { return { data: transformedData, filter: transformedFilter }; } - async updateRows( + async updateRows( dataTableId: string, projectId: string, dto: Omit, diff --git a/packages/cli/src/modules/dynamic-credentials.ee/__tests__/dynamic-credentials-cors.integration.test.ts b/packages/cli/src/modules/dynamic-credentials.ee/__tests__/dynamic-credentials-cors.integration.test.ts index 87d6e340b3a..7b7ae168493 100644 --- a/packages/cli/src/modules/dynamic-credentials.ee/__tests__/dynamic-credentials-cors.integration.test.ts +++ b/packages/cli/src/modules/dynamic-credentials.ee/__tests__/dynamic-credentials-cors.integration.test.ts @@ -304,7 +304,7 @@ describe('GET /workflows/:workflowId/execution-status - CORS Integration', () => test('should set CORS headers on GET request', async () => { // Mock the workflow status service to return a valid response const { CredentialResolverWorkflowService } = await import( - '../services/credential-resolver-workflow.service' + '../services/credential-resolver-workflow.service.js' ); const workflowService = Container.get(CredentialResolverWorkflowService); vi.spyOn(workflowService, 'getWorkflowStatus').mockResolvedValue([ diff --git a/packages/cli/src/modules/dynamic-credentials.ee/__tests__/dynamic-credentials-rate-limit.integration.test.ts b/packages/cli/src/modules/dynamic-credentials.ee/__tests__/dynamic-credentials-rate-limit.integration.test.ts index 731954af23d..8b796965c19 100644 --- a/packages/cli/src/modules/dynamic-credentials.ee/__tests__/dynamic-credentials-rate-limit.integration.test.ts +++ b/packages/cli/src/modules/dynamic-credentials.ee/__tests__/dynamic-credentials-rate-limit.integration.test.ts @@ -77,7 +77,7 @@ beforeAll(async () => { mockInstance(EnterpriseCredentialsService); const { CredentialResolverWorkflowService } = await import( - '../services/credential-resolver-workflow.service' + '../services/credential-resolver-workflow.service.js' ); workflowService = Container.get(CredentialResolverWorkflowService); }); diff --git a/packages/cli/src/modules/dynamic-credentials.ee/dynamic-credentials.module.ts b/packages/cli/src/modules/dynamic-credentials.ee/dynamic-credentials.module.ts index 16689a3c1d6..409e6041576 100644 --- a/packages/cli/src/modules/dynamic-credentials.ee/dynamic-credentials.module.ts +++ b/packages/cli/src/modules/dynamic-credentials.ee/dynamic-credentials.module.ts @@ -17,10 +17,12 @@ function isExternalResolversEnabled(): boolean { @BackendModule({ name: 'dynamic-credentials', licenseFlag: LICENSE_FEATURES.DYNAMIC_CREDENTIALS }) export class DynamicCredentialsModule implements ModuleInterface { async init() { - await import('./dynamic-credentials.controller'); + await import('./dynamic-credentials.controller.js'); // Import the n8n oauth extractor and seeder - const { N8nOAuthIdentitySeeder } = await import('./context-establishment-hooks/n8n-oauth'); + const { N8nOAuthIdentitySeeder } = await import( + './context-establishment-hooks/n8n-oauth/index.js' + ); Container.get(TriggerAuthIdentitySeederProxy).registerSeeder( Container.get(N8nOAuthIdentitySeeder), @@ -28,12 +30,12 @@ export class DynamicCredentialsModule implements ModuleInterface { // System resolver powers private credentials; OAuth/Slack resolvers and // their management/identity-extractor surfaces are external-only. - await import('./credential-resolvers/n8n-credential-resolver'); + await import('./credential-resolvers/n8n-credential-resolver.js'); if (isExternalResolversEnabled()) { - await import('./credential-resolvers.controller'); - await import('./context-establishment-hooks'); - await import('./credential-resolvers/oauth-credential-resolver'); - await import('./credential-resolvers/slack-credential-resolver'); + await import('./credential-resolvers.controller.js'); + await import('./context-establishment-hooks/index.js'); + await import('./credential-resolvers/oauth-credential-resolver.js'); + await import('./credential-resolvers/slack-credential-resolver.js'); } const { DynamicCredentialResolverRegistry, @@ -41,14 +43,16 @@ export class DynamicCredentialsModule implements ModuleInterface { DynamicCredentialService, N8nResolverSeeder, CredentialConnectionStatusService, - } = await import('./services'); - await import('./workflow-status.controller'); + } = await import('./services/index.js'); + await import('./workflow-status.controller.js'); await Container.get(DynamicCredentialResolverRegistry).init(); await Container.get(N8nResolverSeeder).seed(); // Register the credential resolution provider with CredentialsHelper - const { DynamicCredentialsProxy } = await import('../../credentials/dynamic-credentials-proxy'); + const { DynamicCredentialsProxy } = await import( + '../../credentials/dynamic-credentials-proxy.js' + ); const credentialsProxy = Container.get(DynamicCredentialsProxy); const dynamicCredentialService = Container.get(DynamicCredentialService); const dynamicCredentialStorageService = Container.get(DynamicCredentialStorageService); @@ -58,7 +62,7 @@ export class DynamicCredentialsModule implements ModuleInterface { // Register the per-user connection status provider so the credentials // service can populate `connectedByMe` on responses. const { CredentialConnectionStatusProxy } = await import( - '../../credentials/credential-connection-status-proxy' + '../../credentials/credential-connection-status-proxy.js' ); Container.get(CredentialConnectionStatusProxy).setProvider( Container.get(CredentialConnectionStatusService), @@ -66,10 +70,14 @@ export class DynamicCredentialsModule implements ModuleInterface { } async entities() { - const { DynamicCredentialResolver } = await import('./database/entities/credential-resolver'); - const { DynamicCredentialEntry } = await import('./database/entities/dynamic-credential-entry'); + const { DynamicCredentialResolver } = await import( + './database/entities/credential-resolver.js' + ); + const { DynamicCredentialEntry } = await import( + './database/entities/dynamic-credential-entry.js' + ); const { DynamicCredentialUserEntry } = await import( - './database/entities/dynamic-credential-user-entry' + './database/entities/dynamic-credential-user-entry.js' ); return [DynamicCredentialResolver, DynamicCredentialEntry, DynamicCredentialUserEntry]; @@ -77,7 +85,7 @@ export class DynamicCredentialsModule implements ModuleInterface { async context() { const { CredentialCheckProxyService } = await import( - './services/credential-check-proxy.service' + './services/credential-check-proxy.service.js' ); return { credentialCheckProxy: Container.get(CredentialCheckProxyService) }; } diff --git a/packages/cli/src/modules/encryption-key-manager/encryption-key-manager.module.ts b/packages/cli/src/modules/encryption-key-manager/encryption-key-manager.module.ts index 1b1a80650a6..020cc14897c 100644 --- a/packages/cli/src/modules/encryption-key-manager/encryption-key-manager.module.ts +++ b/packages/cli/src/modules/encryption-key-manager/encryption-key-manager.module.ts @@ -11,11 +11,11 @@ function isKeyRotationApiEnabled(): boolean { export class EncryptionKeyManagerModule implements ModuleInterface { async init() { if (isKeyRotationApiEnabled()) { - await import('./key-manager.service'); + await import('./key-manager.service.js'); if (Container.get(InstanceSettings).instanceType === 'main') { - await import('./encryption-key.controller'); + await import('./encryption-key.controller.js'); } - const { EncryptionBootstrapService } = await import('./encryption-bootstrap.service'); + const { EncryptionBootstrapService } = await import('./encryption-bootstrap.service.js'); await Container.get(EncryptionBootstrapService).run(); } } diff --git a/packages/cli/src/modules/external-secrets.ee/external-secrets.module.ts b/packages/cli/src/modules/external-secrets.ee/external-secrets.module.ts index 43e1073e38b..a2df392de95 100644 --- a/packages/cli/src/modules/external-secrets.ee/external-secrets.module.ts +++ b/packages/cli/src/modules/external-secrets.ee/external-secrets.module.ts @@ -5,15 +5,15 @@ import { Container } from '@n8n/di'; @BackendModule({ name: 'external-secrets', licenseFlag: 'feat:externalSecrets' }) export class ExternalSecretsModule implements ModuleInterface { async init() { - await import('./external-secrets.controller.ee'); - await import('./external-secrets-settings.controller.ee'); + await import('./external-secrets.controller.ee.js'); + await import('./external-secrets-settings.controller.ee.js'); - await import('./secrets-providers-types.controller.ee'); - await import('./secrets-providers-connections.controller.ee'); - await import('./secrets-providers-completions.controller.ee'); - await import('./secrets-providers-project.controller.ee'); + await import('./secrets-providers-types.controller.ee.js'); + await import('./secrets-providers-connections.controller.ee.js'); + await import('./secrets-providers-completions.controller.ee.js'); + await import('./secrets-providers-project.controller.ee.js'); - const { ExternalSecretsManager } = await import('./external-secrets-manager.ee'); + const { ExternalSecretsManager } = await import('./external-secrets-manager.ee.js'); const { ExternalSecretsProxy } = await import('n8n-core'); const externalSecretsManager = Container.get(ExternalSecretsManager); @@ -24,11 +24,11 @@ export class ExternalSecretsModule implements ModuleInterface { } async settings() { - const { ExternalSecretsConfig } = await import('./external-secrets.config'); + const { ExternalSecretsConfig } = await import('./external-secrets.config.js'); const config = Container.get(ExternalSecretsConfig); const { ExternalSecretsSettingsService } = await import( - './external-secrets-settings.service.ee' + './external-secrets-settings.service.ee.js' ); const settingsService = Container.get(ExternalSecretsSettingsService); @@ -44,7 +44,7 @@ export class ExternalSecretsModule implements ModuleInterface { @OnShutdown() async shutdown() { - const { ExternalSecretsManager } = await import('./external-secrets-manager.ee'); + const { ExternalSecretsManager } = await import('./external-secrets-manager.ee.js'); Container.get(ExternalSecretsManager).shutdown(); } diff --git a/packages/cli/src/modules/favorites/favorites.module.ts b/packages/cli/src/modules/favorites/favorites.module.ts index c000540dd76..9bd1522e3ea 100644 --- a/packages/cli/src/modules/favorites/favorites.module.ts +++ b/packages/cli/src/modules/favorites/favorites.module.ts @@ -5,14 +5,14 @@ import { Container } from '@n8n/di'; @BackendModule({ name: 'favorites', instanceTypes: ['main'] }) export class FavoritesModule implements ModuleInterface { async init() { - await import('./favorites.controller'); + await import('./favorites.controller.js'); - const { FavoritesEventRelay } = await import('./favorites.event-relay'); + const { FavoritesEventRelay } = await import('./favorites.event-relay.js'); Container.get(FavoritesEventRelay).init(); } async entities() { - const { UserFavorite } = await import('./database/entities/user-favorite.entity'); + const { UserFavorite } = await import('./database/entities/user-favorite.entity.js'); return [UserFavorite] as never; } } diff --git a/packages/cli/src/modules/insights/insights.module.ts b/packages/cli/src/modules/insights/insights.module.ts index 12d2e4235bf..6f5c2183f78 100644 --- a/packages/cli/src/modules/insights/insights.module.ts +++ b/packages/cli/src/modules/insights/insights.module.ts @@ -9,29 +9,29 @@ import { Container } from '@n8n/di'; @BackendModule({ name: 'insights', instanceTypes: ['main', 'webhook'] }) export class InsightsModule implements ModuleInterface { async init() { - await import('./insights.controller'); + await import('./insights.controller.js'); - const { InsightsService } = await import('./insights.service'); + const { InsightsService } = await import('./insights.service.js'); await Container.get(InsightsService).init(); } async entities() { - const { InsightsByPeriod } = await import('./database/entities/insights-by-period'); - const { InsightsMetadata } = await import('./database/entities/insights-metadata'); - const { InsightsRaw } = await import('./database/entities/insights-raw'); + const { InsightsByPeriod } = await import('./database/entities/insights-by-period.js'); + const { InsightsMetadata } = await import('./database/entities/insights-metadata.js'); + const { InsightsRaw } = await import('./database/entities/insights-raw.js'); return [InsightsByPeriod, InsightsMetadata, InsightsRaw]; } async settings() { - const { InsightsSettings } = await import('./insights.settings'); + const { InsightsSettings } = await import('./insights.settings.js'); return await Container.get(InsightsSettings).settings(); } @OnShutdown() async shutdown() { - const { InsightsService } = await import('./insights.service'); + const { InsightsService } = await import('./insights.service.js'); await Container.get(InsightsService).shutdown(); } diff --git a/packages/cli/src/modules/insights/insights.service.ts b/packages/cli/src/modules/insights/insights.service.ts index ab0840f56da..536cb22b469 100644 --- a/packages/cli/src/modules/insights/insights.service.ts +++ b/packages/cli/src/modules/insights/insights.service.ts @@ -34,7 +34,7 @@ export class InsightsService { return; } - const { InsightsCollectionService } = await import('./insights-collection.service'); + const { InsightsCollectionService } = await import('./insights-collection.service.js'); const collectionService = Container.get(InsightsCollectionService); if (enable) { collectionService.init(); diff --git a/packages/cli/src/modules/instance-ai/__tests__/agent-tree-builder.test.ts b/packages/cli/src/modules/instance-ai/__tests__/agent-tree-builder.test.ts index 355477c55de..c6903eecf26 100644 --- a/packages/cli/src/modules/instance-ai/__tests__/agent-tree-builder.test.ts +++ b/packages/cli/src/modules/instance-ai/__tests__/agent-tree-builder.test.ts @@ -10,7 +10,7 @@ let findAgentNodeInTree: ( beforeAll(async () => { ({ buildAgentTreeFromEvents, findAgentNodeInTree } = (await import( - '../../../../../@n8n/instance-ai/src/utils/agent-tree' + '../../../../../@n8n/instance-ai/src/utils/agent-tree.js' )) as { buildAgentTreeFromEvents: (events: InstanceAiEvent[]) => InstanceAiAgentNode; findAgentNodeInTree: ( diff --git a/packages/cli/src/modules/instance-ai/__tests__/instance-ai.adapter.service.test.ts b/packages/cli/src/modules/instance-ai/__tests__/instance-ai.adapter.service.test.ts index 28f69db1b57..0edcb2f1c69 100644 --- a/packages/cli/src/modules/instance-ai/__tests__/instance-ai.adapter.service.test.ts +++ b/packages/cli/src/modules/instance-ai/__tests__/instance-ai.adapter.service.test.ts @@ -1,7 +1,7 @@ // Mock the barrel import so these adapter tests only exercise local formatting helpers. vi.mock('@n8n/instance-ai', async () => { const { WorkflowSaveConflictError } = await import( - '../../../../../@n8n/instance-ai/src/errors/workflow-save-conflict.error' + '../../../../../@n8n/instance-ai/src/errors/workflow-save-conflict.error.js' ); return { WorkflowSaveConflictError, diff --git a/packages/cli/src/modules/instance-ai/eval/__tests__/api-docs.test.ts b/packages/cli/src/modules/instance-ai/eval/__tests__/api-docs.test.ts index acfaf23f3b5..0d232f3c21e 100644 --- a/packages/cli/src/modules/instance-ai/eval/__tests__/api-docs.test.ts +++ b/packages/cli/src/modules/instance-ai/eval/__tests__/api-docs.test.ts @@ -59,7 +59,7 @@ afterEach(() => { describe('resolveLibraryId (via fetchApiDocs)', () => { it('should return docs when library search and docs fetch both succeed', async () => { - const { fetchApiDocs } = await import('../api-docs'); + const { fetchApiDocs } = await import('../api-docs.js'); // Library search mockFetchResponse(200, [{ id: '/lib/slack-api', trust_score: 90 }]); @@ -73,7 +73,7 @@ describe('resolveLibraryId (via fetchApiDocs)', () => { }); it('should cache library ID — second call does not re-fetch library search', async () => { - const { fetchApiDocs } = await import('../api-docs'); + const { fetchApiDocs } = await import('../api-docs.js'); // First call: library search + docs mockFetchResponse(200, [{ id: '/lib/slack-api', trust_score: 90 }]); @@ -92,7 +92,7 @@ describe('resolveLibraryId (via fetchApiDocs)', () => { }); it('should return fallback when library search returns non-200', async () => { - const { fetchApiDocs } = await import('../api-docs'); + const { fetchApiDocs } = await import('../api-docs.js'); mockFetchResponse(500, 'Internal Server Error'); @@ -103,7 +103,7 @@ describe('resolveLibraryId (via fetchApiDocs)', () => { }); it('should return fallback when library search returns empty results', async () => { - const { fetchApiDocs } = await import('../api-docs'); + const { fetchApiDocs } = await import('../api-docs.js'); mockFetchResponse(200, []); @@ -114,7 +114,7 @@ describe('resolveLibraryId (via fetchApiDocs)', () => { }); it('should log warning on 429 status (quota exceeded)', async () => { - const { fetchApiDocs } = await import('../api-docs'); + const { fetchApiDocs } = await import('../api-docs.js'); mockFetchResponse(429, 'Rate limit exceeded'); @@ -126,7 +126,7 @@ describe('resolveLibraryId (via fetchApiDocs)', () => { }); it('should log warning when response body contains "Quota"', async () => { - const { fetchApiDocs } = await import('../api-docs'); + const { fetchApiDocs } = await import('../api-docs.js'); mockFetchResponse(403, 'Quota limit reached for your plan'); @@ -138,7 +138,7 @@ describe('resolveLibraryId (via fetchApiDocs)', () => { }); it('should log warning only once per session (context7WarningLogged flag)', async () => { - const { fetchApiDocs } = await import('../api-docs'); + const { fetchApiDocs } = await import('../api-docs.js'); // First call — 429, should warn mockFetchResponse(429, 'Rate limit'); @@ -152,7 +152,7 @@ describe('resolveLibraryId (via fetchApiDocs)', () => { }); it('should return fallback on fetch timeout / network error', async () => { - const { fetchApiDocs } = await import('../api-docs'); + const { fetchApiDocs } = await import('../api-docs.js'); mockFetch.mockRejectedValueOnce(new Error('network timeout')); @@ -171,7 +171,7 @@ describe('resolveLibraryId (via fetchApiDocs)', () => { describe('fetchApiDocs', () => { it('should return docs text from Context7 on success', async () => { - const { fetchApiDocs } = await import('../api-docs'); + const { fetchApiDocs } = await import('../api-docs.js'); mockFetchResponse(200, [{ id: '/lib/github-api', trust_score: 80 }]); mockFetchResponse(200, 'GET /repos/{owner}/{repo} — returns a repository'); @@ -182,7 +182,7 @@ describe('fetchApiDocs', () => { }); it('should cache docs per serviceName + endpointQuery', async () => { - const { fetchApiDocs } = await import('../api-docs'); + const { fetchApiDocs } = await import('../api-docs.js'); // First call: library search + docs mockFetchResponse(200, [{ id: '/lib/slack-api', trust_score: 90 }]); @@ -200,7 +200,7 @@ describe('fetchApiDocs', () => { }); it('should return fallback when docs endpoint returns non-200', async () => { - const { fetchApiDocs } = await import('../api-docs'); + const { fetchApiDocs } = await import('../api-docs.js'); mockFetchResponse(200, [{ id: '/lib/slack-api', trust_score: 90 }]); mockFetchResponse(500, 'Server Error'); @@ -211,7 +211,7 @@ describe('fetchApiDocs', () => { }); it('should return fallback when docs response is empty text', async () => { - const { fetchApiDocs } = await import('../api-docs'); + const { fetchApiDocs } = await import('../api-docs.js'); mockFetchResponse(200, [{ id: '/lib/slack-api', trust_score: 90 }]); mockFetchResponse(200, ' '); @@ -223,7 +223,7 @@ describe('fetchApiDocs', () => { it('should pass CONTEXT7_API_KEY as Bearer Authorization header when env var is set', async () => { process.env.CONTEXT7_API_KEY = 'test-api-key-123'; - const { fetchApiDocs } = await import('../api-docs'); + const { fetchApiDocs } = await import('../api-docs.js'); mockFetchResponse(200, [{ id: '/lib/slack-api', trust_score: 90 }]); mockFetchResponse(200, 'docs'); @@ -239,7 +239,7 @@ describe('fetchApiDocs', () => { it('should not send Authorization header when CONTEXT7_API_KEY is not set', async () => { delete process.env.CONTEXT7_API_KEY; - const { fetchApiDocs } = await import('../api-docs'); + const { fetchApiDocs } = await import('../api-docs.js'); mockFetchResponse(200, [{ id: '/lib/slack-api', trust_score: 90 }]); mockFetchResponse(200, 'docs'); diff --git a/packages/cli/src/modules/instance-ai/eval/__tests__/mock-handler.test.ts b/packages/cli/src/modules/instance-ai/eval/__tests__/mock-handler.test.ts index e0545e35a1f..5a8ac931d2e 100644 --- a/packages/cli/src/modules/instance-ai/eval/__tests__/mock-handler.test.ts +++ b/packages/cli/src/modules/instance-ai/eval/__tests__/mock-handler.test.ts @@ -485,7 +485,7 @@ describe('createLlmMockHandler', () => { }); it('should cache node config across calls for the same node name', async () => { - const { extractNodeConfig } = (await import('../node-config')) as unknown as { + const { extractNodeConfig } = (await import('../node-config.js')) as unknown as { extractNodeConfig: Mock; }; extractNodeConfig.mockReturnValue('{"resource":"message"}'); @@ -534,7 +534,7 @@ describe('createLlmMockHandler', () => { }); it('should extract config separately for different node names', async () => { - const { extractNodeConfig } = (await import('../node-config')) as unknown as { + const { extractNodeConfig } = (await import('../node-config.js')) as unknown as { extractNodeConfig: Mock; }; extractNodeConfig.mockReturnValue('{}'); diff --git a/packages/cli/src/modules/instance-ai/instance-ai.adapter.service.ts b/packages/cli/src/modules/instance-ai/instance-ai.adapter.service.ts index 8b5634dd497..13eb88adfc9 100644 --- a/packages/cli/src/modules/instance-ai/instance-ai.adapter.service.ts +++ b/packages/cli/src/modules/instance-ai/instance-ai.adapter.service.ts @@ -1699,7 +1699,7 @@ export class InstanceAiAdapterService { const raw = await credentialsService.decrypt(credential, true); const tokenData = raw.oauthTokenData; if (tokenData && typeof tokenData === 'object') { - const { OauthService } = await import('@/oauth/oauth.service'); + const { OauthService } = await import('@/oauth/oauth.service.js'); const identifier = OauthService.extractAccountIdentifier( tokenData as Record, ); diff --git a/packages/cli/src/modules/instance-ai/instance-ai.module.ts b/packages/cli/src/modules/instance-ai/instance-ai.module.ts index b9edb5ce0d9..61155a76ae0 100644 --- a/packages/cli/src/modules/instance-ai/instance-ai.module.ts +++ b/packages/cli/src/modules/instance-ai/instance-ai.module.ts @@ -16,25 +16,25 @@ export class InstanceAiModule implements ModuleInterface { const logger = Container.get(Logger).scoped('instance-ai'); logger.warn(`${YELLOW}${WARNING_MESSAGE}${CLEAR}`); - const { InstanceAiSettingsService } = await import('./instance-ai-settings.service'); + const { InstanceAiSettingsService } = await import('./instance-ai-settings.service.js'); await Container.get(InstanceAiSettingsService).loadFromDb(); - await import('./instance-ai.controller'); - await import('./mcp/instance-ai-mcp-connection.controller'); + await import('./instance-ai.controller.js'); + await import('./mcp/instance-ai-mcp-connection.controller.js'); // Instantiating the relay registers its `user-deleted` listener, which // cleans up Instance AI data owned by the deleted user. - const { InstanceAiEventRelay } = await import('./instance-ai-event-relay.service'); + const { InstanceAiEventRelay } = await import('./instance-ai-event-relay.service.js'); Container.get(InstanceAiEventRelay); if (process.env.E2E_TESTS === 'true' && process.env.NODE_ENV !== 'production') { - await import('./instance-ai-test.controller'); + await import('./instance-ai-test.controller.js'); } } async settings() { const { GlobalConfig } = await import('@n8n/config'); - const { InstanceAiService } = await import('./instance-ai.service'); - const { InstanceAiSettingsService } = await import('./instance-ai-settings.service'); + const { InstanceAiService } = await import('./instance-ai.service.js'); + const { InstanceAiSettingsService } = await import('./instance-ai-settings.service.js'); const globalConfig = Container.get(GlobalConfig); const service = Container.get(InstanceAiService); const settingsService = Container.get(InstanceAiSettingsService); @@ -56,28 +56,30 @@ export class InstanceAiModule implements ModuleInterface { } async entities() { - const { InstanceAiThread } = await import('./entities/instance-ai-thread.entity'); - const { InstanceAiMessage } = await import('./entities/instance-ai-message.entity'); - const { InstanceAiResource } = await import('./entities/instance-ai-resource.entity'); - const { InstanceAiRunSnapshot } = await import('./entities/instance-ai-run-snapshot.entity'); - const { InstanceAiIterationLog } = await import('./entities/instance-ai-iteration-log.entity'); - const { InstanceAiCheckpoint } = await import('./entities/instance-ai-checkpoint.entity'); - const { InstanceAiPendingConfirmation } = await import( - './entities/instance-ai-pending-confirmation.entity' + const { InstanceAiThread } = await import('./entities/instance-ai-thread.entity.js'); + const { InstanceAiMessage } = await import('./entities/instance-ai-message.entity.js'); + const { InstanceAiResource } = await import('./entities/instance-ai-resource.entity.js'); + const { InstanceAiRunSnapshot } = await import('./entities/instance-ai-run-snapshot.entity.js'); + const { InstanceAiIterationLog } = await import( + './entities/instance-ai-iteration-log.entity.js' ); - const { InstanceAiObservation } = await import('./entities/instance-ai-observation.entity'); + const { InstanceAiCheckpoint } = await import('./entities/instance-ai-checkpoint.entity.js'); + const { InstanceAiPendingConfirmation } = await import( + './entities/instance-ai-pending-confirmation.entity.js' + ); + const { InstanceAiObservation } = await import('./entities/instance-ai-observation.entity.js'); const { InstanceAiObservationCursor } = await import( - './entities/instance-ai-observation-cursor.entity' + './entities/instance-ai-observation-cursor.entity.js' ); const { InstanceAiObservationLock } = await import( - './entities/instance-ai-observation-lock.entity' + './entities/instance-ai-observation-lock.entity.js' ); const { InstanceAiMcpRegistryConnection } = await import( - './entities/instance-ai-mcp-registry-connection.entity' + './entities/instance-ai-mcp-registry-connection.entity.js' ); - const { InstanceAiThreadGrant } = await import('./entities/instance-ai-thread-grant.entity'); + const { InstanceAiThreadGrant } = await import('./entities/instance-ai-thread-grant.entity.js'); const { InstanceAiEventLogEntry } = await import( - './entities/instance-ai-event-log-entry.entity' + './entities/instance-ai-event-log-entry.entity.js' ); return [ @@ -99,7 +101,7 @@ export class InstanceAiModule implements ModuleInterface { @OnShutdown() async shutdown() { - const { InstanceAiService } = await import('./instance-ai.service'); + const { InstanceAiService } = await import('./instance-ai.service.js'); await Container.get(InstanceAiService).shutdown(); } } diff --git a/packages/cli/src/modules/instance-ai/instance-ai.service.ts b/packages/cli/src/modules/instance-ai/instance-ai.service.ts index ee573550773..10c1be6a053 100644 --- a/packages/cli/src/modules/instance-ai/instance-ai.service.ts +++ b/packages/cli/src/modules/instance-ai/instance-ai.service.ts @@ -94,6 +94,7 @@ import { WorkflowLoopStorage, ThreadTaskStorage, } from '@n8n/instance-ai'; +import { lazyImport } from '@n8n/utils/lazy-import'; import { setSchemaBaseDirs } from '@n8n/workflow-sdk'; import { ErrorReporter, InstanceSettings } from 'n8n-core'; import { OperationalError, UnexpectedError, UserError } from 'n8n-workflow'; @@ -102,8 +103,8 @@ import { nanoid } from 'nanoid'; import { N8N_VERSION, WORKFLOW_SDK_VERSION } from '@/constants'; import { EventService } from '@/events/event.service'; import { SourceControlPreferencesService } from '@/modules/source-control.ee/source-control-preferences.service.ee'; -import type { PubSubCommandMap } from '@/scaling/pubsub/pubsub.event-map'; import { Publisher } from '@/scaling/pubsub/publisher.service'; +import type { PubSubCommandMap } from '@/scaling/pubsub/pubsub.event-map'; import { AiService } from '@/services/ai.service'; import { ProxyTokenManager } from '@/services/proxy-token-manager'; import { UrlService } from '@/services/url.service'; @@ -115,6 +116,7 @@ import { EvalThreadCredentialAllowlistService } from './eval/thread-credential-a import { DurableEventLog } from './event-bus/durable-event-log'; import { InProcessEventBus } from './event-bus/in-process-event-bus'; import { InstanceAiCreditService } from './instance-ai-credit.service'; +import { InstanceAiErrorReporterService } from './instance-ai-error-reporter.service'; import { BROWSER_TOOL_CATEGORY, InstanceAiGatewayService } from './instance-ai-gateway.service'; import { InstanceAiMemoryService } from './instance-ai-memory.service'; import { InstanceAiModelService } from './instance-ai-model.service'; @@ -134,7 +136,6 @@ import { } from './internal-messages'; import { INSTANCE_AI_RUN_TIMEOUT_REASON, InstanceAiLivenessService } from './liveness'; import { InstanceAiMcpRegistryService } from './mcp'; -import { InstanceAiErrorReporterService } from './instance-ai-error-reporter.service'; import { buildInstanceAiObservabilityContext, type InstanceAiObservabilityContext, @@ -2924,6 +2925,7 @@ export class InstanceAiService { * `startExecuteRun` so the promise is registered with `inFlightExecutions` * and shutdown can drain it before the DB closes. */ + // eslint-disable-next-line complexity private async executeRun( user: User, threadId: string, @@ -3133,7 +3135,9 @@ export class InstanceAiService { // When trace replay is enabled but LangSmith isn't configured, // create a minimal context that only supports replay/record wrapping. if (!tracing && process.env.E2E_TESTS === 'true') { - const { createTraceReplayOnlyContext } = await import('@n8n/instance-ai'); + const { createTraceReplayOnlyContext } = await lazyImport< + typeof import('@n8n/instance-ai') + >(async () => await import('@n8n/instance-ai')); tracing = createTraceReplayOnlyContext(); } diff --git a/packages/cli/src/modules/instance-ai/trace-replay-state.ts b/packages/cli/src/modules/instance-ai/trace-replay-state.ts index 550a27684f9..cdacb9c95c4 100644 --- a/packages/cli/src/modules/instance-ai/trace-replay-state.ts +++ b/packages/cli/src/modules/instance-ai/trace-replay-state.ts @@ -5,6 +5,7 @@ import type { TraceWriter as TraceWriterType, TraceEvent, } from '@n8n/instance-ai'; +import { lazyImport } from '@n8n/utils/lazy-import'; const TOOL_TRACE_EVENT_KINDS = new Set(['tool-call', 'tool-suspend', 'tool-resume']); @@ -155,7 +156,14 @@ export class TraceReplayState { return; } - const { TraceIndex: TI, IdRemapper: IR, TraceWriter: TW } = await import('@n8n/instance-ai'); + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + type InstanceAiImport = typeof import('@n8n/instance-ai'); + + const { + TraceIndex: TI, + IdRemapper: IR, + TraceWriter: TW, + } = await lazyImport(async () => await import('@n8n/instance-ai')); const slug = this.activeSlug; const events = slug ? this.eventsBySlug.get(slug) : undefined; diff --git a/packages/cli/src/modules/instance-registry/instance-registry.module.ts b/packages/cli/src/modules/instance-registry/instance-registry.module.ts index ace4c1c66e3..701d3fe0c3e 100644 --- a/packages/cli/src/modules/instance-registry/instance-registry.module.ts +++ b/packages/cli/src/modules/instance-registry/instance-registry.module.ts @@ -13,28 +13,28 @@ import { Container } from '@n8n/di'; @BackendModule({ name: 'instance-registry' }) export class InstanceRegistryModule implements ModuleInterface { async init() { - await import('./instance-registry.controller'); + await import('./instance-registry.controller.js'); - const { InstanceRegistryService } = await import('./instance-registry.service'); + const { InstanceRegistryService } = await import('./instance-registry.service.js'); const instanceRegistryService = Container.get(InstanceRegistryService); await instanceRegistryService.init(); const { InstanceRegistryProxyService } = await import( - '@/services/instance-registry-proxy.service' + '@/services/instance-registry-proxy.service.js' ); Container.get(InstanceRegistryProxyService).registerProvider(instanceRegistryService); - const { StaleMemberCleanupService } = await import('./stale-member-cleanup.service'); + const { StaleMemberCleanupService } = await import('./stale-member-cleanup.service.js'); Container.get(StaleMemberCleanupService).init(); - await import('./checks'); - const { CheckService } = await import('./checks/check.service'); + await import('./checks/index.js'); + const { CheckService } = await import('./checks/check.service.js'); Container.get(CheckService).init(); } @OnShutdown() async shutdown() { - const { InstanceRegistryService } = await import('./instance-registry.service'); + const { InstanceRegistryService } = await import('./instance-registry.service.js'); await Container.get(InstanceRegistryService).shutdown(); } } diff --git a/packages/cli/src/modules/instance-registry/instance-registry.service.ts b/packages/cli/src/modules/instance-registry/instance-registry.service.ts index 4d3d7b46564..757a1a26fbd 100644 --- a/packages/cli/src/modules/instance-registry/instance-registry.service.ts +++ b/packages/cli/src/modules/instance-registry/instance-registry.service.ts @@ -110,12 +110,12 @@ export class InstanceRegistryService { const useRedis = this.instanceSettings.isMultiMain || this.executionsConfig.mode === 'queue'; if (useRedis) { - const { RedisInstanceStorage } = await import('./storage/redis-instance-storage'); + const { RedisInstanceStorage } = await import('./storage/redis-instance-storage.js'); const { Container } = await import('@n8n/di'); return Container.get(RedisInstanceStorage); } - const { MemoryInstanceStorage } = await import('./storage/memory-storage'); + const { MemoryInstanceStorage } = await import('./storage/memory-storage.js'); return new MemoryInstanceStorage(); } diff --git a/packages/cli/src/modules/instance-version-history/instance-version-history.module.ts b/packages/cli/src/modules/instance-version-history/instance-version-history.module.ts index d52b72f9e83..99c7f1ad32e 100644 --- a/packages/cli/src/modules/instance-version-history/instance-version-history.module.ts +++ b/packages/cli/src/modules/instance-version-history/instance-version-history.module.ts @@ -5,15 +5,15 @@ import { Container } from '@n8n/di'; @BackendModule({ name: 'instance-version-history', instanceTypes: ['main'] }) export class InstanceVersionHistoryModule implements ModuleInterface { async init() { - await import('./instance-version-history.controller'); + await import('./instance-version-history.controller.js'); - const { InstanceVersionHistoryService } = await import('./instance-version-history.service'); + const { InstanceVersionHistoryService } = await import('./instance-version-history.service.js'); await Container.get(InstanceVersionHistoryService).init(); } async entities() { const { InstanceVersionHistory } = await import( - './database/entities/instance-version-history.entity' + './database/entities/instance-version-history.entity.js' ); return [InstanceVersionHistory]; } diff --git a/packages/cli/src/modules/ldap.ee/ldap.module.ts b/packages/cli/src/modules/ldap.ee/ldap.module.ts index d240cde5173..86f17c405dd 100644 --- a/packages/cli/src/modules/ldap.ee/ldap.module.ts +++ b/packages/cli/src/modules/ldap.ee/ldap.module.ts @@ -4,9 +4,9 @@ import { BackendModule } from '@n8n/decorators'; @BackendModule({ name: 'ldap', licenseFlag: 'feat:ldap', instanceTypes: ['main'] }) export class LdapModule implements ModuleInterface { async init() { - await import('./ldap.controller.ee'); + await import('./ldap.controller.ee.js'); // Import LdapService to trigger @PasswordAuthHandler() decorator registration - await import('./ldap.service.ee'); + await import('./ldap.service.ee.js'); } } diff --git a/packages/cli/src/modules/ldap.ee/ldap.service.ee.ts b/packages/cli/src/modules/ldap.ee/ldap.service.ee.ts index 19e2b25649f..27e355962ae 100644 --- a/packages/cli/src/modules/ldap.ee/ldap.service.ee.ts +++ b/packages/cli/src/modules/ldap.ee/ldap.service.ee.ts @@ -7,6 +7,7 @@ import type { RunningMode, SyncStatus } from '@n8n/db'; import type { IPasswordAuthHandler } from '@n8n/decorators'; import { AuthHandler } from '@n8n/decorators'; import { Constructable, Container } from '@n8n/di'; +import { lazyImport } from '@n8n/utils/lazy-import'; import type { Entry as LdapUser, ClientOptions, Client } from 'ldapts'; import { Cipher } from 'n8n-core'; import { jsonParse, UnexpectedError } from 'n8n-workflow'; @@ -51,7 +52,6 @@ export class LdapService implements IPasswordAuthHandler { readonly metadata = { name: 'ldap', type: 'password' as const }; private client: Client | undefined; - // eslint-disable-next-line @typescript-eslint/consistent-type-imports private ldapts: typeof import('ldapts'); private syncTimer: NodeJS.Timeout | undefined = undefined; @@ -184,7 +184,7 @@ export class LdapService implements IPasswordAuthHandler { } if (this.client === undefined) { if (!this.ldapts) { - this.ldapts = await import('ldapts'); + this.ldapts = await lazyImport(async () => await import('ldapts')); } const url = formatUrl( diff --git a/packages/cli/src/modules/log-streaming.ee/log-streaming.module.ts b/packages/cli/src/modules/log-streaming.ee/log-streaming.module.ts index cf200d5d02d..61069f45c1d 100644 --- a/packages/cli/src/modules/log-streaming.ee/log-streaming.module.ts +++ b/packages/cli/src/modules/log-streaming.ee/log-streaming.module.ts @@ -17,16 +17,18 @@ import { Container } from '@n8n/di'; }) export class LogStreamingModule implements ModuleInterface { async init() { - await import('./log-streaming.controller'); + await import('./log-streaming.controller.js'); - const { LogStreamingDestinationService } = await import('./log-streaming-destination.service'); + const { LogStreamingDestinationService } = await import( + './log-streaming-destination.service.js' + ); const destinationService = Container.get(LogStreamingDestinationService); await destinationService.loadDestinationsFromDb(); await destinationService.initialize(); } async entities() { - const { EventDestinations } = await import('./database/entities/event-destination.entity'); + const { EventDestinations } = await import('./database/entities/event-destination.entity.js'); return [EventDestinations]; } } diff --git a/packages/cli/src/modules/mcp-registry/mcp-registry.module.ts b/packages/cli/src/modules/mcp-registry/mcp-registry.module.ts index d7fec38ebc3..e9cacd42157 100644 --- a/packages/cli/src/modules/mcp-registry/mcp-registry.module.ts +++ b/packages/cli/src/modules/mcp-registry/mcp-registry.module.ts @@ -8,23 +8,23 @@ import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials'; @BackendModule({ name: 'mcp-registry' }) export class McpRegistryModule implements ModuleInterface { async init() { - const { McpRegistryService } = await import('./registry/mcp-registry.service'); + const { McpRegistryService } = await import('./registry/mcp-registry.service.js'); await Container.get(McpRegistryService).init(); - await import('./mcp-registry.controller'); + await import('./mcp-registry.controller.js'); if (process.env.E2E_TESTS === 'true' && process.env.NODE_ENV !== 'production') { - await import('./mcp-registry-test.controller'); + await import('./mcp-registry-test.controller.js'); } } async entities() { - const { McpRegistryServerEntity } = await import('./registry/mcp-registry-server.entity'); + const { McpRegistryServerEntity } = await import('./registry/mcp-registry-server.entity.js'); return [McpRegistryServerEntity]; } async nodeLoaders() { - const { McpRegistryNodeLoader } = await import('./mcp-registry-node-loader'); + const { McpRegistryNodeLoader } = await import('./mcp-registry-node-loader.js'); return [ new McpRegistryNodeLoader(Container.get(LoadNodesAndCredentials), Container.get(Logger)), diff --git a/packages/cli/src/modules/mcp/__tests__/mcp.controller.test.ts b/packages/cli/src/modules/mcp/__tests__/mcp.controller.test.ts index cfcc88c374a..6c77b3483bc 100644 --- a/packages/cli/src/modules/mcp/__tests__/mcp.controller.test.ts +++ b/packages/cli/src/modules/mcp/__tests__/mcp.controller.test.ts @@ -84,7 +84,7 @@ describe('McpController', () => { Container.set(ApiKeyRepository, mock()); // Imported here (not statically) so the Container.set above runs first. - ({ McpController } = await import('../mcp.controller')); + ({ McpController } = await import('../mcp.controller.js')); controller = Container.get(McpController); }); diff --git a/packages/cli/src/modules/mcp/mcp.module.ts b/packages/cli/src/modules/mcp/mcp.module.ts index b030fcac0d8..c4694339232 100644 --- a/packages/cli/src/modules/mcp/mcp.module.ts +++ b/packages/cli/src/modules/mcp/mcp.module.ts @@ -12,14 +12,14 @@ import { Container } from '@n8n/di'; @BackendModule({ name: 'mcp', instanceTypes: ['main'] }) export class McpModule implements ModuleInterface { async init() { - await import('./mcp.controller'); - await import('./mcp.settings.controller'); + await import('./mcp.controller.js'); + await import('./mcp.settings.controller.js'); // Register the instance MCP server as a protected resource of the shared // OAuth server, so its tokens are minted and verified with the right // audiences and its discovery metadata is served. - const { ProtectedResourceRegistry } = await import('@/services/protected-resource.registry'); - const { McpProtectedResource } = await import('./mcp-protected-resource'); + const { ProtectedResourceRegistry } = await import('@/services/protected-resource.registry.js'); + const { McpProtectedResource } = await import('./mcp-protected-resource.js'); Container.get(ProtectedResourceRegistry).register(Container.get(McpProtectedResource)); } @@ -30,8 +30,8 @@ export class McpModule implements ModuleInterface { * `{ mcp: { mcpAccessEnabled: boolean, mcpManagedByEnv: boolean, serverUrl: string } }`. */ async settings() { - const { McpSettingsService } = await import('./mcp.settings.service'); - const { McpProtectedResource } = await import('./mcp-protected-resource'); + const { McpSettingsService } = await import('./mcp.settings.service.js'); + const { McpProtectedResource } = await import('./mcp-protected-resource.js'); const mcpAccessEnabled = await Container.get(McpSettingsService).getEnabled(); const { mcpManagedByEnv } = Container.get(InstanceSettingsLoaderConfig); // Canonical resource URL, so the UI shows the URL clients must actually diff --git a/packages/cli/src/modules/mcp/mcp.service.ts b/packages/cli/src/modules/mcp/mcp.service.ts index 4fad05a1f91..72b58ad15c3 100644 --- a/packages/cli/src/modules/mcp/mcp.service.ts +++ b/packages/cli/src/modules/mcp/mcp.service.ts @@ -16,19 +16,20 @@ import { WORKFLOW_PREVIEW_APP_URI, type McpAppTelemetryConfig, } from '@n8n/mcp-apps/server'; +import { lazyImport } from '@n8n/utils/lazy-import'; import { createDeferredPromise, type IDeferredPromise } from '@n8n/utils/promise/deferred-promise'; import { InstanceSettings } from 'n8n-core'; import { ManualExecutionCancelledError, type IRun } from 'n8n-workflow'; -import { - createAddDataTableColumnTool, - createAddDataTableRowsTool, - createCreateDataTableTool, - createDeleteDataTableColumnTool, - createRenameDataTableColumnTool, - createRenameDataTableTool, - createSearchDataTablesTool, -} from './tools/data-table'; +import { ActiveExecutions } from '@/active-executions'; +import { CollaborationService } from '@/collaboration/collaboration.service'; +import { N8N_VERSION } from '@/constants'; +import { CredentialsService } from '@/credentials/credentials.service'; +import { ExecutionService } from '@/executions/execution.service'; +import { SubworkflowPolicyChecker } from '@/executions/pre-execution-checks/subworkflow-policy-checker'; +import { DataTableProxyService } from '@/modules/data-table/data-table-proxy.service'; +import { NodeCatalogService } from '@/node-catalog'; + import { createExecuteWorkflowTool } from './tools/execute-workflow.tool'; import { createGetExecutionTool } from './tools/get-execution.tool'; import { createSearchExecutionsTool } from './tools/search-executions.tool'; @@ -56,13 +57,7 @@ import { createSearchWorkflowNodesTool } from './tools/workflow-builder/search-w import { getSdkReferenceContent } from './tools/workflow-builder/sdk-reference-content'; import { createValidateNodeTool } from './tools/workflow-builder/validate-node.tool'; import { createValidateWorkflowCodeTool } from './tools/workflow-builder/validate-workflow-code.tool'; -import { NodeCatalogService } from '@/node-catalog'; -import { ActiveExecutions } from '@/active-executions'; -import { CollaborationService } from '@/collaboration/collaboration.service'; -import { N8N_VERSION } from '@/constants'; -import { CredentialsService } from '@/credentials/credentials.service'; -import { DataTableProxyService } from '@/modules/data-table/data-table-proxy.service'; import { NodeTypes } from '@/node-types'; import { PostHogClient } from '@/posthog'; import { NodeResourceExplorerService } from '@/services/node-resource-explorer.service'; @@ -77,13 +72,21 @@ import { WorkflowFinderService } from '@/workflows/workflow-finder.service'; import { WorkflowHistoryService } from '@/workflows/workflow-history/workflow-history.service'; import { WorkflowPublishedDataService } from '@/workflows/workflow-published-data.service'; import { WorkflowService } from '@/workflows/workflow.service'; -import { SubworkflowPolicyChecker } from '@/executions/pre-execution-checks/subworkflow-policy-checker'; + import { MCP_PREVIEW_RENDER_REQUESTED_EVENT } from './mcp.constants'; import { getAllowedToolNames } from './mcp-scopes'; import type { McpAppsTelemetryVariant, McpClientInfo, RegisterToolFn } from './mcp.types'; +import { + createAddDataTableColumnTool, + createAddDataTableRowsTool, + createCreateDataTableTool, + createDeleteDataTableColumnTool, + createRenameDataTableColumnTool, + createRenameDataTableTool, + createSearchDataTablesTool, +} from './tools/data-table'; import { createPrepareTestPinDataTool } from './tools/prepare-workflow-pin-data.tool'; import { createTestWorkflowTool } from './tools/test-workflow.tool'; -import { ExecutionService } from '@/executions/execution.service'; /** * Pending MCP execution response, used for queue mode support. @@ -217,7 +220,10 @@ export class McpService { clientInfo?: McpClientInfo, grantedScopes?: string[], ) { - const { McpServer } = await import('@modelcontextprotocol/sdk/server/mcp.js'); + const { McpServer } = await lazyImport< + typeof import('@modelcontextprotocol/sdk/server/mcp.js') + >(async () => await import('@modelcontextprotocol/sdk/server/mcp.js')); + const builderEnabled = this.globalConfig.endpoints.mcpBuilderEnabled; const allowedToolNames = getAllowedToolNames(grantedScopes); // The builder walkthrough is only useful when the grant can actually diff --git a/packages/cli/src/modules/mcp/mcp.utils.ts b/packages/cli/src/modules/mcp/mcp.utils.ts index fa1081c02df..247b9c796c9 100644 --- a/packages/cli/src/modules/mcp/mcp.utils.ts +++ b/packages/cli/src/modules/mcp/mcp.utils.ts @@ -1,9 +1,8 @@ import type { AuthenticatedRequest } from '@n8n/db'; +import { isRecord } from '@n8n/utils/is-record'; import type { Request } from 'express'; import type { INode } from 'n8n-workflow'; -import { isRecord } from '@n8n/utils/is-record'; - import { SUPPORTED_MCP_TRIGGERS, SUPPORTED_PRODUCTION_MCP_TRIGGERS } from './mcp.constants'; import { isJSONRPCRequest } from './mcp.typeguards'; import type { McpClientInfo } from './mcp.types'; @@ -44,7 +43,7 @@ export const getToolArguments = (body: unknown): Record => { if (!isJSONRPCRequest(body)) return {}; if (!body.params) return {}; - const { arguments: args } = body.params; + const args = body.params.arguments; if (isRecord(args)) { return args; } diff --git a/packages/cli/src/modules/n8n-packages/n8n-packages.module.ts b/packages/cli/src/modules/n8n-packages/n8n-packages.module.ts index fb2a592dbb6..4a2778ab4b3 100644 --- a/packages/cli/src/modules/n8n-packages/n8n-packages.module.ts +++ b/packages/cli/src/modules/n8n-packages/n8n-packages.module.ts @@ -6,6 +6,6 @@ import { BackendModule } from '@n8n/decorators'; }) export class N8nPackagesModule implements ModuleInterface { async init() { - await import('./n8n-packages.service'); + await import('./n8n-packages.service.js'); } } diff --git a/packages/cli/src/modules/oauth-jwe/oauth-jwe.module.ts b/packages/cli/src/modules/oauth-jwe/oauth-jwe.module.ts index e02b6702201..5cbf887adcb 100644 --- a/packages/cli/src/modules/oauth-jwe/oauth-jwe.module.ts +++ b/packages/cli/src/modules/oauth-jwe/oauth-jwe.module.ts @@ -14,7 +14,7 @@ export class OAuthJweModule implements ModuleInterface { async init() { if (!isFeatureFlagEnabled()) return; - const { OAuthJweDecryptService } = await import('./oauth-jwe-decrypt.service'); + const { OAuthJweDecryptService } = await import('./oauth-jwe-decrypt.service.js'); Container.get(OAuthJweServiceProxy).setHandler(Container.get(OAuthJweDecryptService)); // Eager key bootstrap and the JWKS controller belong on main only. @@ -22,9 +22,9 @@ export class OAuthJweModule implements ModuleInterface { // the cache is cold and main hasn't generated yet, the partial unique // index on `(type, algorithm)` serializes any concurrent generation. if (Container.get(InstanceSettings).instanceType === 'main') { - const { OAuthJweKeyService } = await import('./oauth-jwe-key.service'); + const { OAuthJweKeyService } = await import('./oauth-jwe-key.service.js'); await Container.get(OAuthJweKeyService).initialize(); - await import('./oauth-jwe.controller'); + await import('./oauth-jwe.controller.js'); } } diff --git a/packages/cli/src/modules/oauth-server/__tests__/oauth-consent.controller.api.test.ts b/packages/cli/src/modules/oauth-server/__tests__/oauth-consent.controller.api.test.ts index 06387d483ce..3257ba80d10 100644 --- a/packages/cli/src/modules/oauth-server/__tests__/oauth-consent.controller.api.test.ts +++ b/packages/cli/src/modules/oauth-server/__tests__/oauth-consent.controller.api.test.ts @@ -405,7 +405,7 @@ describe('POST /rest/consent/approve', () => { expect(response.statusCode).toBe(200); const { UserConsentRepository } = await import( - '../database/repositories/oauth-user-consent.repository' + '../database/repositories/oauth-user-consent.repository.js' ); const userConsentRepository = Container.get(UserConsentRepository); const consent = await userConsentRepository.findOne({ @@ -428,7 +428,7 @@ describe('POST /rest/consent/approve', () => { expect(response.statusCode).toBe(200); const { UserConsentRepository } = await import( - '../database/repositories/oauth-user-consent.repository' + '../database/repositories/oauth-user-consent.repository.js' ); const userConsentRepository = Container.get(UserConsentRepository); const consent = await userConsentRepository.findOne({ diff --git a/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.api.test.ts b/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.api.test.ts index 79403f01a0a..7952ecce39b 100644 --- a/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.api.test.ts +++ b/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.api.test.ts @@ -298,7 +298,7 @@ describe('POST /mcp-oauth/register', () => { }); test('should reject with descriptive server_error on the post-insert rollback (race path)', async () => { - const { OAuthServerService } = await import('../oauth-server.service'); + const { OAuthServerService } = await import('../oauth-server.service.js'); const globalConfig = Container.get(GlobalConfig); const originalLimit = globalConfig.endpoints.mcpMaxRegisteredClients; globalConfig.endpoints.mcpMaxRegisteredClients = 1; @@ -805,7 +805,7 @@ describe('IP rate limit configuration', () => { let OAuthController: typeof OAuthControllerClass; beforeAll(async () => { - ({ OAuthController } = await import('../oauth.controller')); + ({ OAuthController } = await import('../oauth.controller.js')); }); test('applies the configured limits to the shared OAuth endpoints', () => { diff --git a/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.test.ts b/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.test.ts index 82fe2b2532d..b7c833f3d93 100644 --- a/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.test.ts +++ b/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.test.ts @@ -1,4 +1,4 @@ -import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients'; +import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients.js'; import { mockInstance } from '@n8n/backend-test-utils'; import type { Request, Response } from 'express'; import { mock } from 'vitest-mock-extended'; @@ -22,7 +22,7 @@ beforeAll(async () => { // The SDK's `clientRegistrationHandler` validates `clientsStore.registerClient` // when the module builds its routers, so the mock needs a real store. mockInstance(OAuthServerService, { clientsStore: mock() }); - ({ OAuthController } = await import('../oauth.controller')); + ({ OAuthController } = await import('../oauth.controller.js')); }); const urlService = mock(); diff --git a/packages/cli/src/modules/oauth-server/oauth-server.module.ts b/packages/cli/src/modules/oauth-server/oauth-server.module.ts index 40cd7195140..d4ecb01f0ed 100644 --- a/packages/cli/src/modules/oauth-server/oauth-server.module.ts +++ b/packages/cli/src/modules/oauth-server/oauth-server.module.ts @@ -21,29 +21,29 @@ export class OAuthServerModule implements ModuleInterface { async init() { // Only import controllers in the main process, since the webhook/worker processes don't run an HTTP server and don't need them. if (Container.get(InstanceSettings).instanceType === 'main') { - await import('./oauth.controller'); - await import('./oauth-consent.controller'); - await import('./oauth-clients.controller'); + await import('./oauth.controller.js'); + await import('./oauth-consent.controller.js'); + await import('./oauth-clients.controller.js'); } // Register the token service as the OAuth token verifier provider, so // protected-resource modules verify bearer tokens through the core // `OAuthTokenVerifierProxy` instead of importing this module. const { OAuthTokenVerifierProxy } = await import( - '@/services/oauth-token-verifier-proxy.service' + '@/services/oauth-token-verifier-proxy.service.js' ); - const { OAuthTokenService } = await import('./oauth-token.service'); + const { OAuthTokenService } = await import('./oauth-token.service.js'); Container.get(OAuthTokenVerifierProxy).registerProvider(Container.get(OAuthTokenService)); const { WorkflowMcpTriggerResourceResolver } = await import( - './protected-resource-resolvers/workflow-mcp-trigger-resource.resolver' + './protected-resource-resolvers/workflow-mcp-trigger-resource.resolver.js' ); Container.get(ProtectedResourceRegistry).registerResolver( Container.get(WorkflowMcpTriggerResourceResolver), ); const { WorkflowMcpTestTriggerResourceResolver } = await import( - './protected-resource-resolvers/workflow-mcp-test-trigger-resource.resolver' + './protected-resource-resolvers/workflow-mcp-test-trigger-resource.resolver.js' ); Container.get(ProtectedResourceRegistry).registerResolver( Container.get(WorkflowMcpTestTriggerResourceResolver), @@ -51,13 +51,13 @@ export class OAuthServerModule implements ModuleInterface { } async entities() { - const { OAuthClient } = await import('./database/entities/oauth-client.entity'); + const { OAuthClient } = await import('./database/entities/oauth-client.entity.js'); const { AuthorizationCode } = await import( - './database/entities/oauth-authorization-code.entity' + './database/entities/oauth-authorization-code.entity.js' ); - const { AccessToken } = await import('./database/entities/oauth-access-token.entity'); - const { RefreshToken } = await import('./database/entities/oauth-refresh-token.entity'); - const { UserConsent } = await import('./database/entities/oauth-user-consent.entity'); + const { AccessToken } = await import('./database/entities/oauth-access-token.entity.js'); + const { RefreshToken } = await import('./database/entities/oauth-refresh-token.entity.js'); + const { UserConsent } = await import('./database/entities/oauth-user-consent.entity.js'); return [OAuthClient, AuthorizationCode, AccessToken, RefreshToken, UserConsent] as never; } diff --git a/packages/cli/src/modules/oauth-server/oauth-server.service.ts b/packages/cli/src/modules/oauth-server/oauth-server.service.ts index f5b9dc826e5..08ab863a2fa 100644 --- a/packages/cli/src/modules/oauth-server/oauth-server.service.ts +++ b/packages/cli/src/modules/oauth-server/oauth-server.service.ts @@ -1,4 +1,4 @@ -import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients'; +import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients.js'; import { InvalidGrantError, InvalidTargetError, @@ -6,18 +6,20 @@ import { import type { AuthorizationParams, OAuthServerProvider, -} from '@modelcontextprotocol/sdk/server/auth/provider'; -import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types'; +} from '@modelcontextprotocol/sdk/server/auth/provider.js'; +import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js'; import type { OAuthClientInformationFull, OAuthTokens, OAuthTokenRevocationRequest, -} from '@modelcontextprotocol/sdk/shared/auth'; +} from '@modelcontextprotocol/sdk/shared/auth.js'; import { Logger } from '@n8n/backend-common'; import { GlobalConfig } from '@n8n/config'; import { Service } from '@n8n/di'; import type { Response } from 'express'; +import { ProtectedResourceRegistry } from '@/services/protected-resource.registry'; + import { OAuthClient } from './database/entities/oauth-client.entity'; import { OAuthClientRepository } from './database/repositories/oauth-client.repository'; import { UserConsentRepository } from './database/repositories/oauth-user-consent.repository'; @@ -25,7 +27,6 @@ import { OAuthAuthorizationCodeService } from './oauth-authorization-code.servic import { OAuthSessionService } from './oauth-session.service'; import { OAuthTokenService } from './oauth-token.service'; import { OAuthClientLimitReachedError } from './oauth.errors'; -import { ProtectedResourceRegistry } from '@/services/protected-resource.registry'; /** Maximum number of redirect URIs per client */ const MAX_REDIRECT_URIS = 10; diff --git a/packages/cli/src/modules/oauth-server/oauth-token.service.ts b/packages/cli/src/modules/oauth-server/oauth-token.service.ts index feaaeec0eda..6775e9d1a1d 100644 --- a/packages/cli/src/modules/oauth-server/oauth-token.service.ts +++ b/packages/cli/src/modules/oauth-server/oauth-token.service.ts @@ -1,6 +1,6 @@ import { InvalidGrantError } from '@modelcontextprotocol/sdk/server/auth/errors.js'; -import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types'; -import { OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth'; +import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js'; +import { OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js'; import { Logger } from '@n8n/backend-common'; import { Time } from '@n8n/constants'; import { UserRepository, withTransaction } from '@n8n/db'; @@ -10,19 +10,19 @@ import { ensureError } from '@n8n/utils/errors/ensure-error'; import { UnexpectedError } from 'n8n-workflow'; import { randomBytes, randomUUID } from 'node:crypto'; -import { AccessToken } from './database/entities/oauth-access-token.entity'; -import { RefreshToken } from './database/entities/oauth-refresh-token.entity'; -import { AccessTokenRepository } from './database/repositories/oauth-access-token.repository'; -import { RefreshTokenRepository } from './database/repositories/oauth-refresh-token.repository'; -import type { ProtectedResource } from '@/services/protected-resource.registry'; -import { ProtectedResourceRegistry } from '@/services/protected-resource.registry'; -import { AccessTokenNotFoundError, JWTVerificationError } from './oauth.errors'; - import { JwtService } from '@/services/jwt.service'; import type { OAuthTokenVerifier, UserWithContext, } from '@/services/oauth-token-verifier-proxy.service'; +import type { ProtectedResource } from '@/services/protected-resource.registry'; +import { ProtectedResourceRegistry } from '@/services/protected-resource.registry'; + +import { AccessToken } from './database/entities/oauth-access-token.entity'; +import { RefreshToken } from './database/entities/oauth-refresh-token.entity'; +import { AccessTokenRepository } from './database/repositories/oauth-access-token.repository'; +import { RefreshTokenRepository } from './database/repositories/oauth-refresh-token.repository'; +import { AccessTokenNotFoundError, JWTVerificationError } from './oauth.errors'; /** * Manages the OAuth 2.1 token lifecycle for the shared OAuth server. diff --git a/packages/cli/src/modules/otel/otel.module.ts b/packages/cli/src/modules/otel/otel.module.ts index 1e61bf503cd..dd133343f4d 100644 --- a/packages/cli/src/modules/otel/otel.module.ts +++ b/packages/cli/src/modules/otel/otel.module.ts @@ -9,24 +9,24 @@ import { InstanceSettings } from 'n8n-core'; }) export class OtelModule implements ModuleInterface { async init() { - await import('./otel-lifecycle-handler'); + await import('./otel-lifecycle-handler.js'); - const { OtelService } = await import('./otel.service'); + const { OtelService } = await import('./otel.service.js'); await Container.get(OtelService).init(); if (Container.get(InstanceSettings).instanceType === 'main') { - await import('./otel-settings.controller'); + await import('./otel-settings.controller.js'); } } async settings() { - const { OtelSettingsService } = await import('./otel-settings.service'); + const { OtelSettingsService } = await import('./otel-settings.service.js'); const { enabled } = Container.get(OtelSettingsService).getSettings(); return { enabled }; } async context(): Promise { - const { ExecutionLevelTracer } = await import('./execution-level-tracer'); + const { ExecutionLevelTracer } = await import('./execution-level-tracer.js'); const tracer = Container.get(ExecutionLevelTracer); return { @@ -36,7 +36,7 @@ export class OtelModule implements ModuleInterface { @OnShutdown() async shutdown() { - const { OtelService } = await import('./otel.service'); + const { OtelService } = await import('./otel.service.js'); await Container.get(OtelService).shutdown(); } } diff --git a/packages/cli/src/modules/provisioning.ee/provisioning.module.ts b/packages/cli/src/modules/provisioning.ee/provisioning.module.ts index 1245457a5dc..14e51c430a3 100644 --- a/packages/cli/src/modules/provisioning.ee/provisioning.module.ts +++ b/packages/cli/src/modules/provisioning.ee/provisioning.module.ts @@ -9,14 +9,16 @@ import { Container } from '@n8n/di'; }) export class ProvisioningModule implements ModuleInterface { async init() { - await import('./provisioning.controller.ee'); - await import('./role-mapping-rule.controller.ee'); + await import('./provisioning.controller.ee.js'); + await import('./role-mapping-rule.controller.ee.js'); // Register the role-deletion checker so the core `RoleService` blocks // deleting a role still targeted by a mapping rule, without importing // this module. - const { RoleDeletionCheckProxy } = await import('@/services/role-deletion-check-proxy.service'); - const { ProvisioningRoleDeletionChecker } = await import('./role-deletion-checker.ee'); + const { RoleDeletionCheckProxy } = await import( + '@/services/role-deletion-check-proxy.service.js' + ); + const { ProvisioningRoleDeletionChecker } = await import('./role-deletion-checker.ee.js'); Container.get(RoleDeletionCheckProxy).registerProvider( Container.get(ProvisioningRoleDeletionChecker), ); diff --git a/packages/cli/src/modules/quick-connect/quick-connect.module.ts b/packages/cli/src/modules/quick-connect/quick-connect.module.ts index 9716cd95bcc..96c29c7ff1a 100644 --- a/packages/cli/src/modules/quick-connect/quick-connect.module.ts +++ b/packages/cli/src/modules/quick-connect/quick-connect.module.ts @@ -6,7 +6,7 @@ import { Container } from '@n8n/di'; export class QuickConnectModule implements ModuleInterface { async init() { await this.registerHandlers(); - await import('./quick-connect.controller'); + await import('./quick-connect.controller.js'); } /** @@ -16,7 +16,7 @@ export class QuickConnectModule implements ModuleInterface { * Note: backendFlowConfig is intentionally excluded from the response to keep it server-side only. */ async settings() { - const { QuickConnectConfig } = await import('./quick-connect.config'); + const { QuickConnectConfig } = await import('./quick-connect.config.js'); const { options } = Container.get(QuickConnectConfig); // Strip backendFlowConfig before sending options to frontend return { @@ -27,7 +27,7 @@ export class QuickConnectModule implements ModuleInterface { } private async registerHandlers() { - const { QuickConnectService } = await import('./quick-connect.service'); + const { QuickConnectService } = await import('./quick-connect.service.js'); const quickConnectService = Container.get(QuickConnectService); await quickConnectService.registerHandlers(); } diff --git a/packages/cli/src/modules/quick-connect/quick-connect.service.ts b/packages/cli/src/modules/quick-connect/quick-connect.service.ts index b90b11d19e6..71ae0aeee5c 100644 --- a/packages/cli/src/modules/quick-connect/quick-connect.service.ts +++ b/packages/cli/src/modules/quick-connect/quick-connect.service.ts @@ -12,7 +12,7 @@ import { QuickConnectConfig } from './quick-connect.config'; import { QuickConnectError } from './quick-connect.errors'; const backendHandlers = { - firecrawl: async () => (await import('./handlers/firecrawl.handler')).FirecrawlHandler, + firecrawl: async () => (await import('./handlers/firecrawl.handler.js')).FirecrawlHandler, }; @Service() diff --git a/packages/cli/src/modules/redaction/redaction.module.ts b/packages/cli/src/modules/redaction/redaction.module.ts index ad3ee979540..012e3df97b8 100644 --- a/packages/cli/src/modules/redaction/redaction.module.ts +++ b/packages/cli/src/modules/redaction/redaction.module.ts @@ -7,16 +7,18 @@ import { ExecutionRedactionServiceProxy } from '@/executions/execution-redaction @BackendModule({ name: 'redaction', instanceTypes: ['main'] }) export class RedactionModule implements ModuleInterface { async init() { - await import('./redaction-context-hook'); + await import('./redaction-context-hook.js'); // Importing the service here registers its @OnPubSubEvent handler with the // pubsub metadata before PubSubRegistry.init() wires up the listeners. // The decorator runs at class-evaluation (import) time, so the import // side-effect alone is sufficient — the registry instantiates the handler // lazily on event receipt, so we must not eagerly resolve it here. - await import('./instance-redaction-enforcement.service'); + await import('./instance-redaction-enforcement.service.js'); - const { ExecutionRedactionService } = await import('./executions/execution-redaction.service'); + const { ExecutionRedactionService } = await import( + './executions/execution-redaction.service.js' + ); const executionRedactionService = Container.get(ExecutionRedactionService); await executionRedactionService.init(); diff --git a/packages/cli/src/modules/runtime-credentials/runtime-credentials.module.ts b/packages/cli/src/modules/runtime-credentials/runtime-credentials.module.ts index c744e068d5f..dd8eb2754a7 100644 --- a/packages/cli/src/modules/runtime-credentials/runtime-credentials.module.ts +++ b/packages/cli/src/modules/runtime-credentials/runtime-credentials.module.ts @@ -13,13 +13,13 @@ export class RuntimeCredentialsModule implements ModuleInterface { async init() { if (!isFeatureFlagEnabled()) return; - const { RuntimeCredentialsService } = await import('./runtime-credentials.service'); + const { RuntimeCredentialsService } = await import('./runtime-credentials.service.js'); Container.get(RuntimeCredentialsService).init(); - await import('./runtime-credentials-context-hook'); - await import('./runtime-credentials.config'); + await import('./runtime-credentials-context-hook.js'); + await import('./runtime-credentials.config.js'); const { RuntimeCredentialsAccessService } = await import( - './runtime-credentials-access.service' + './runtime-credentials-access.service.js' ); Container.get(RuntimeCredentialProxyService).registerProvider( Container.get(RuntimeCredentialsAccessService), diff --git a/packages/cli/src/modules/source-control.ee/__tests__/source-control-helper.ee.test.ts b/packages/cli/src/modules/source-control.ee/__tests__/source-control-helper.ee.test.ts index c5ffef8feee..d9114f1c53c 100644 --- a/packages/cli/src/modules/source-control.ee/__tests__/source-control-helper.ee.test.ts +++ b/packages/cli/src/modules/source-control.ee/__tests__/source-control-helper.ee.test.ts @@ -525,7 +525,7 @@ describe('Source Control Helper', () => { const filePath = 'invalid/path/tags-and-mappings.json'; // Import the function after resetting modules const { readTagAndMappingsFromSourceControlFile } = await import( - '@/modules/source-control.ee/source-control-helper.ee' + '@/modules/source-control.ee/source-control-helper.ee.js' ); const result = await readTagAndMappingsFromSourceControlFile(filePath); expect(result).toEqual({ @@ -546,7 +546,7 @@ describe('Source Control Helper', () => { const filePath = 'invalid/path/folders.json'; // Import the function after resetting modules const { readFoldersFromSourceControlFile } = await import( - '@/modules/source-control.ee/source-control-helper.ee' + '@/modules/source-control.ee/source-control-helper.ee.js' ); const result = await readFoldersFromSourceControlFile(filePath); expect(result).toEqual({ @@ -566,7 +566,7 @@ describe('Source Control Helper', () => { const filePath = 'invalid/path/data_tables.json'; // Import the function after resetting modules const { readDataTablesFromSourceControlFile } = await import( - '@/modules/source-control.ee/source-control-helper.ee' + '@/modules/source-control.ee/source-control-helper.ee.js' ); const result = await readDataTablesFromSourceControlFile(filePath); expect(result).toEqual([]); @@ -594,7 +594,7 @@ describe('Source Control Helper', () => { // Import the function after mocking const { readDataTablesFromSourceControlFile } = await import( - '@/modules/source-control.ee/source-control-helper.ee' + '@/modules/source-control.ee/source-control-helper.ee.js' ); const result = await readDataTablesFromSourceControlFile('valid/path/data_tables.json'); diff --git a/packages/cli/src/modules/source-control.ee/source-control-helper.ee.ts b/packages/cli/src/modules/source-control.ee/source-control-helper.ee.ts index be4624887eb..ce526aba5ba 100644 --- a/packages/cli/src/modules/source-control.ee/source-control-helper.ee.ts +++ b/packages/cli/src/modules/source-control.ee/source-control-helper.ee.ts @@ -283,7 +283,10 @@ export function isSourceControlLicensed() { } export async function generateSshKeyPair(keyType: KeyPairType) { - const sshpk = await import('sshpk'); + // sshpk is CommonJS (`export =`): under nodenext, a native dynamic import only + // hoists some named exports onto the namespace (parsePrivateKey is missed), so + // read the real module.exports off `.default`. + const { default: sshpk } = await import('sshpk'); const keyPair: KeyPair = { publicKey: '', privateKey: '', diff --git a/packages/cli/src/modules/source-control.ee/source-control-preferences.service.ee.ts b/packages/cli/src/modules/source-control.ee/source-control-preferences.service.ee.ts index a72ab346b0b..8eafab4ab22 100644 --- a/packages/cli/src/modules/source-control.ee/source-control-preferences.service.ee.ts +++ b/packages/cli/src/modules/source-control.ee/source-control-preferences.service.ee.ts @@ -323,7 +323,7 @@ export class SourceControlPreferencesService { */ private async broadcastReloadSourceControlConfiguration(): Promise { if (this.instanceSettings.isMultiMain) { - const { Publisher } = await import('@/scaling/pubsub/publisher.service'); + const { Publisher } = await import('@/scaling/pubsub/publisher.service.js'); await Container.get(Publisher).publishCommand({ command: 'reload-source-control-config' }); this.logger.debug('Broadcasting source control configuration reload to other main instances'); } diff --git a/packages/cli/src/modules/source-control.ee/source-control.module.ts b/packages/cli/src/modules/source-control.ee/source-control.module.ts index 80b165720b4..61abe1d5a01 100644 --- a/packages/cli/src/modules/source-control.ee/source-control.module.ts +++ b/packages/cli/src/modules/source-control.ee/source-control.module.ts @@ -9,9 +9,9 @@ import { Container } from '@n8n/di'; }) export class SourceControlModule implements ModuleInterface { async init() { - await import('./source-control.controller.ee'); + await import('./source-control.controller.ee.js'); - const { SourceControlService } = await import('./source-control.service.ee'); + const { SourceControlService } = await import('./source-control.service.ee.js'); await Container.get(SourceControlService).start(); } } diff --git a/packages/cli/src/modules/sso-oidc/oidc.service.ee.ts b/packages/cli/src/modules/sso-oidc/oidc.service.ee.ts index 4668a1743dc..048fbd50038 100644 --- a/packages/cli/src/modules/sso-oidc/oidc.service.ee.ts +++ b/packages/cli/src/modules/sso-oidc/oidc.service.ee.ts @@ -526,7 +526,7 @@ export class OidcService { private async broadcastReloadOIDCConfigurationCommand(): Promise { if (this.instanceSettings.isMultiMain) { - const { Publisher } = await import('@/scaling/pubsub/publisher.service'); + const { Publisher } = await import('@/scaling/pubsub/publisher.service.js'); await Container.get(Publisher).publishCommand({ command: 'reload-oidc-config' }); } } diff --git a/packages/cli/src/modules/sso-oidc/sso-oidc.module.ts b/packages/cli/src/modules/sso-oidc/sso-oidc.module.ts index 2875dfba935..be84c5374da 100644 --- a/packages/cli/src/modules/sso-oidc/sso-oidc.module.ts +++ b/packages/cli/src/modules/sso-oidc/sso-oidc.module.ts @@ -5,9 +5,9 @@ import { Container } from '@n8n/di'; @BackendModule({ name: 'sso-oidc', licenseFlag: 'feat:oidc', instanceTypes: ['main'] }) export class OidcModule implements ModuleInterface { async init() { - await import('./oidc.controller.ee'); + await import('./oidc.controller.ee.js'); - const { OidcService } = await import('./oidc.service.ee'); + const { OidcService } = await import('./oidc.service.ee.js'); await Container.get(OidcService).init(); } } diff --git a/packages/cli/src/modules/sso-saml/saml-validator.ts b/packages/cli/src/modules/sso-saml/saml-validator.ts index da130a87a16..4065e5da3c8 100644 --- a/packages/cli/src/modules/sso-saml/saml-validator.ts +++ b/packages/cli/src/modules/sso-saml/saml-validator.ts @@ -57,24 +57,24 @@ export class SamlValidator { // dynamically load schema files private async loadSchemas(): Promise { - this.xmlProtocol = (await import('./schema/saml-schema-protocol-2.0.xsd')).xmlFileInfo; - this.xmlMetadata = (await import('./schema/saml-schema-metadata-2.0.xsd')).xmlFileInfo; + this.xmlProtocol = (await import('./schema/saml-schema-protocol-2.0.xsd.js')).xmlFileInfo; + this.xmlMetadata = (await import('./schema/saml-schema-metadata-2.0.xsd.js')).xmlFileInfo; this.preload = ( await Promise.all([ // SAML - import('./schema/saml-schema-assertion-2.0.xsd'), - import('./schema/xmldsig-core-schema.xsd'), - import('./schema/xenc-schema.xsd'), - import('./schema/xml.xsd'), + import('./schema/saml-schema-assertion-2.0.xsd.js'), + import('./schema/xmldsig-core-schema.xsd.js'), + import('./schema/xenc-schema.xsd.js'), + import('./schema/xml.xsd.js'), // WS-Federation - import('./schema/ws-federation.xsd'), - import('./schema/oasis-200401-wss-wssecurity-secext-1.0.xsd'), - import('./schema/oasis-200401-wss-wssecurity-utility-1.0.xsd'), - import('./schema/ws-addr.xsd'), - import('./schema/metadata-exchange.xsd'), - import('./schema/ws-securitypolicy-1.2.xsd'), - import('./schema/ws-authorization.xsd'), + import('./schema/ws-federation.xsd.js'), + import('./schema/oasis-200401-wss-wssecurity-secext-1.0.xsd.js'), + import('./schema/oasis-200401-wss-wssecurity-utility-1.0.xsd.js'), + import('./schema/ws-addr.xsd.js'), + import('./schema/metadata-exchange.xsd.js'), + import('./schema/ws-securitypolicy-1.2.xsd.js'), + import('./schema/ws-authorization.xsd.js'), ]) ).map((m) => m.xmlFileInfo); } diff --git a/packages/cli/src/modules/sso-saml/saml.service.ee.ts b/packages/cli/src/modules/sso-saml/saml.service.ee.ts index 061298754e2..3e75433d225 100644 --- a/packages/cli/src/modules/sso-saml/saml.service.ee.ts +++ b/packages/cli/src/modules/sso-saml/saml.service.ee.ts @@ -459,7 +459,7 @@ export class SamlService { private async broadcastReloadSAMLConfigurationCommand(): Promise { if (this.instanceSettings.isMultiMain) { - const { Publisher } = await import('@/scaling/pubsub/publisher.service'); + const { Publisher } = await import('@/scaling/pubsub/publisher.service.js'); await Container.get(Publisher).publishCommand({ command: 'reload-saml-config' }); } } diff --git a/packages/cli/src/modules/sso-saml/sso-saml.module.ts b/packages/cli/src/modules/sso-saml/sso-saml.module.ts index 0f6b9f8dc39..2eeef4bc18d 100644 --- a/packages/cli/src/modules/sso-saml/sso-saml.module.ts +++ b/packages/cli/src/modules/sso-saml/sso-saml.module.ts @@ -5,9 +5,9 @@ import { Container } from '@n8n/di'; @BackendModule({ name: 'sso-saml', licenseFlag: 'feat:saml', instanceTypes: ['main'] }) export class SamlModule implements ModuleInterface { async init() { - await import('./saml.controller.ee'); + await import('./saml.controller.ee.js'); - const { SamlService } = await import('./saml.service.ee'); + const { SamlService } = await import('./saml.service.ee.js'); await Container.get(SamlService).init(); } } diff --git a/packages/cli/src/modules/token-exchange/controllers/__tests__/embed-auth.controller.test.ts b/packages/cli/src/modules/token-exchange/controllers/__tests__/embed-auth.controller.test.ts index d1caa37dbac..a0d91f4eb00 100644 --- a/packages/cli/src/modules/token-exchange/controllers/__tests__/embed-auth.controller.test.ts +++ b/packages/cli/src/modules/token-exchange/controllers/__tests__/embed-auth.controller.test.ts @@ -188,8 +188,8 @@ describe('EmbedAuthController', () => { const req = mock({ browserId: 'browser-id-789', ip: '10.0.0.1' }); const res = mock(); const query = new EmbedLoginQueryDto({ token: 'bad-token' }); - const { TokenExchangeAuthError } = await import('../../token-exchange.errors'); - const { TokenExchangeFailureReason } = await import('../../token-exchange.types'); + const { TokenExchangeAuthError } = await import('../../token-exchange.errors.js'); + const { TokenExchangeFailureReason } = await import('../../token-exchange.types.js'); tokenExchangeService.embedLogin.mockRejectedValue( new TokenExchangeAuthError( TokenExchangeFailureReason.InvalidSignature, @@ -213,7 +213,7 @@ describe('EmbedAuthController', () => { const req = mock({ browserId: 'browser-id-789', ip: '10.0.0.1' }); const res = mock(); const query = new EmbedLoginQueryDto({ token: 'bad-token' }); - const { TokenExchangeFailureReason } = await import('../../token-exchange.types'); + const { TokenExchangeFailureReason } = await import('../../token-exchange.types.js'); tokenExchangeService.embedLogin.mockRejectedValue(new Error('Some unexpected error')); await expect(controller.getLogin(req, res, query)).rejects.toThrow('Some unexpected error'); diff --git a/packages/cli/src/modules/token-exchange/token-exchange.module.ts b/packages/cli/src/modules/token-exchange/token-exchange.module.ts index 61a54c88699..f388226043d 100644 --- a/packages/cli/src/modules/token-exchange/token-exchange.module.ts +++ b/packages/cli/src/modules/token-exchange/token-exchange.module.ts @@ -14,11 +14,11 @@ function isFeatureFlagEnabled(): boolean { }) export class TokenExchangeModule implements ModuleInterface { async entities() { - const { TokenExchangeJti } = await import('./database/entities/token-exchange-jti.entity'); + const { TokenExchangeJti } = await import('./database/entities/token-exchange-jti.entity.js'); const { TrustedKeySourceEntity } = await import( - './database/entities/trusted-key-source.entity' + './database/entities/trusted-key-source.entity.js' ); - const { TrustedKeyEntity } = await import('./database/entities/trusted-key.entity'); + const { TrustedKeyEntity } = await import('./database/entities/trusted-key.entity.js'); return [TokenExchangeJti, TrustedKeySourceEntity, TrustedKeyEntity] as never; } @@ -27,19 +27,19 @@ export class TokenExchangeModule implements ModuleInterface { return; } - const { TrustedKeyService } = await import('./services/trusted-key.service'); + const { TrustedKeyService } = await import('./services/trusted-key.service.js'); await Container.get(TrustedKeyService).initialize(); - await import('./controllers/token-exchange.controller'); - await import('./controllers/embed-auth.controller'); + await import('./controllers/token-exchange.controller.js'); + await import('./controllers/embed-auth.controller.js'); - const { JtiCleanupService } = await import('./services/jti-cleanup.service'); + const { JtiCleanupService } = await import('./services/jti-cleanup.service.js'); Container.get(JtiCleanupService).init(); // Register the scoped JWT auth strategy into the public API auth chain. // ScopedJwtStrategy runs after ApiKeyAuthStrategy (which abstains for token-exchange JWTs). - const { ScopedJwtStrategy } = await import('./services/scoped-jwt.strategy'); - const { AuthStrategyRegistry } = await import('@/services/auth-strategy.registry'); + const { ScopedJwtStrategy } = await import('./services/scoped-jwt.strategy.js'); + const { AuthStrategyRegistry } = await import('@/services/auth-strategy.registry.js'); Container.get(AuthStrategyRegistry).register(Container.get(ScopedJwtStrategy)); } } diff --git a/packages/cli/src/modules/workflow-builder/workflow-builder.module.ts b/packages/cli/src/modules/workflow-builder/workflow-builder.module.ts index 8808db9bd9a..b657fe253ae 100644 --- a/packages/cli/src/modules/workflow-builder/workflow-builder.module.ts +++ b/packages/cli/src/modules/workflow-builder/workflow-builder.module.ts @@ -4,7 +4,7 @@ import { BackendModule } from '@n8n/decorators'; @BackendModule({ name: 'workflow-builder', instanceTypes: ['main'] }) export class WorkflowBuilderModule implements ModuleInterface { async entities() { - const { WorkflowBuilderSession } = await import('./workflow-builder-session.entity'); + const { WorkflowBuilderSession } = await import('./workflow-builder-session.entity.js'); return [WorkflowBuilderSession]; } } diff --git a/packages/cli/src/permissions.ee/check-access.ts b/packages/cli/src/permissions.ee/check-access.ts index 59590dfb53d..0368e5c5468 100644 --- a/packages/cli/src/permissions.ee/check-access.ts +++ b/packages/cli/src/permissions.ee/check-access.ts @@ -127,7 +127,7 @@ export async function userHasScopes( throw new NotFoundError(`Data table with ID "${dataTableId}" not found.`); } - const { DataTableRepository } = await import('@/modules/data-table/data-table.repository'); + const { DataTableRepository } = await import('@/modules/data-table/data-table.repository.js'); const dataTable = await Container.get(DataTableRepository).findOne({ where: { id: dataTableId }, relations: ['project'], diff --git a/packages/cli/src/public-api/v1/handlers/audit/audit.handler.ts b/packages/cli/src/public-api/v1/handlers/audit/audit.handler.ts index 0e456027a0e..228b9815caf 100644 --- a/packages/cli/src/public-api/v1/handlers/audit/audit.handler.ts +++ b/packages/cli/src/public-api/v1/handlers/audit/audit.handler.ts @@ -13,7 +13,7 @@ const auditHandlers: AuditHandlers = { generateAudit: [ apiKeyHasScopeWithGlobalScopeFallback({ scope: 'securityAudit:generate' }), async (req, res) => { - const { SecurityAuditService } = await import('@/security-audit/security-audit.service'); + const { SecurityAuditService } = await import('@/security-audit/security-audit.service.js'); const result = await Container.get(SecurityAuditService).run( req.body?.additionalOptions?.categories, req.body?.additionalOptions?.daysAbandonedWorkflow, diff --git a/packages/cli/src/public-api/v1/handlers/community-packages/__tests__/community-packages.handler.test.ts b/packages/cli/src/public-api/v1/handlers/community-packages/__tests__/community-packages.handler.test.ts index 1a382590d20..09981f20390 100644 --- a/packages/cli/src/public-api/v1/handlers/community-packages/__tests__/community-packages.handler.test.ts +++ b/packages/cli/src/public-api/v1/handlers/community-packages/__tests__/community-packages.handler.test.ts @@ -23,7 +23,7 @@ vi.spyOn(middlewares, 'publicApiScope').mockReturnValue(mockMiddleware); let handler: Record unknown>>; beforeAll(async () => { - handler = (await import('../community-packages.handler')) as unknown as typeof handler; + handler = (await import('../community-packages.handler.js')) as unknown as typeof handler; }); describe('CommunityPackages Handler', () => { diff --git a/packages/cli/src/public-api/v1/handlers/data-tables/__tests__/data-tables.handler.test.ts b/packages/cli/src/public-api/v1/handlers/data-tables/__tests__/data-tables.handler.test.ts index 15030ac737c..5d635e809ce 100644 --- a/packages/cli/src/public-api/v1/handlers/data-tables/__tests__/data-tables.handler.test.ts +++ b/packages/cli/src/public-api/v1/handlers/data-tables/__tests__/data-tables.handler.test.ts @@ -26,8 +26,8 @@ let mainHandler: Record unknown>>; let handler: Record unknown>>; beforeAll(async () => { - mainHandler = (await import('../data-tables.handler')) as unknown as typeof mainHandler; - handler = (await import('../data-tables.rows.handler')) as unknown as typeof handler; + mainHandler = (await import('../data-tables.handler.js')) as unknown as typeof mainHandler; + handler = (await import('../data-tables.rows.handler.js')) as unknown as typeof handler; }); describe('DataTable Handler', () => { diff --git a/packages/cli/src/public-api/v1/handlers/discover/__tests__/discover.handler.test.ts b/packages/cli/src/public-api/v1/handlers/discover/__tests__/discover.handler.test.ts index 4ae1323970f..481dffa2b84 100644 --- a/packages/cli/src/public-api/v1/handlers/discover/__tests__/discover.handler.test.ts +++ b/packages/cli/src/public-api/v1/handlers/discover/__tests__/discover.handler.test.ts @@ -14,7 +14,7 @@ import * as discoverService from '../discover.service'; let handler: Record unknown>>; beforeAll(async () => { - handler = (await import('../discover.handler')) as unknown as typeof handler; + handler = (await import('../discover.handler.js')) as unknown as typeof handler; }); describe('Discover Handler', () => { diff --git a/packages/cli/src/public-api/v1/handlers/discover/__tests__/discover.service.test.ts b/packages/cli/src/public-api/v1/handlers/discover/__tests__/discover.service.test.ts index bf894ed3382..cbb7dedb9be 100644 --- a/packages/cli/src/public-api/v1/handlers/discover/__tests__/discover.service.test.ts +++ b/packages/cli/src/public-api/v1/handlers/discover/__tests__/discover.service.test.ts @@ -32,11 +32,11 @@ vi.spyOn(middlewares, 'isLicensed').mockReturnValue(createMockMiddleware as any) // `discover.service` builds its endpoint registry at module-evaluation time by // reading middleware metadata, so it must be imported *after* the spies above // are installed. A static import is hoisted above them, so load it dynamically. -let buildDiscoverResponse: typeof import('../discover.service').buildDiscoverResponse; -let _resetCache: typeof import('../discover.service')._resetCache; +let buildDiscoverResponse: typeof import('../discover.service.js').buildDiscoverResponse; +let _resetCache: typeof import('../discover.service.js')._resetCache; beforeAll(async () => { - ({ buildDiscoverResponse, _resetCache } = await import('../discover.service')); + ({ buildDiscoverResponse, _resetCache } = await import('../discover.service.js')); // Warm the registry once. The first call cold-loads and transforms all // handler modules (and their transitive graph) through Vite, which can // approach the default 5s test timeout. Paying it here in the hook keeps diff --git a/packages/cli/src/public-api/v1/handlers/discover/discover.service.ts b/packages/cli/src/public-api/v1/handlers/discover/discover.service.ts index 75a1a10b7ec..1a42080dc3d 100644 --- a/packages/cli/src/public-api/v1/handlers/discover/discover.service.ts +++ b/packages/cli/src/public-api/v1/handlers/discover/discover.service.ts @@ -118,7 +118,10 @@ async function _parseEndpointsFromSpec(): Promise { let handlerModule = handlerCache.get(handlerPath); if (!handlerModule) { try { - const fullHandlerPath = path.join(publicApiRoot, handlerPath); + // The `.js` extension is required: under NodeNext, `await import()` is emitted + // as a native dynamic import, which (unlike `require`) does no extension guessing. + // The spec's handler paths are extensionless, so append it here. + const fullHandlerPath = path.join(publicApiRoot, `${handlerPath}.js`); const imported: unknown = await import(fullHandlerPath); if (!isRecord(imported)) continue; // Handlers use `export = xHandlers`, which surfaces as `.default` diff --git a/packages/cli/src/public-api/v1/handlers/n8n-packages/__tests__/n8n-packages.handler.test.ts b/packages/cli/src/public-api/v1/handlers/n8n-packages/__tests__/n8n-packages.handler.test.ts index 211c95b0f44..5f47e332ea3 100644 --- a/packages/cli/src/public-api/v1/handlers/n8n-packages/__tests__/n8n-packages.handler.test.ts +++ b/packages/cli/src/public-api/v1/handlers/n8n-packages/__tests__/n8n-packages.handler.test.ts @@ -34,7 +34,7 @@ let exportPackage: (...args: unknown[]) => unknown; let importPackage: (...args: unknown[]) => unknown; beforeAll(async () => { - handler = (await import('../n8n-packages.handler')) as unknown as typeof handler; + handler = (await import('../n8n-packages.handler.js')) as unknown as typeof handler; exportPackage = handler.exportPackage[1]; importPackage = handler.importPackage[1]; }); diff --git a/packages/cli/src/scaling/scaling.service.ts b/packages/cli/src/scaling/scaling.service.ts index 793f4c1a7f4..ae6c4224dd7 100644 --- a/packages/cli/src/scaling/scaling.service.ts +++ b/packages/cli/src/scaling/scaling.service.ts @@ -55,7 +55,7 @@ export class ScalingService { async setupQueue() { const { default: BullQueue } = await import('bull'); - const { RedisClientService } = await import('@/services/redis-client.service'); + const { RedisClientService } = await import('@/services/redis-client.service.js'); if (this.queue) return; @@ -79,7 +79,7 @@ export class ScalingService { const { McpServer, QueuedExecutionStrategy, RedisSessionStore } = await import( '@n8n/n8n-nodes-langchain/mcp/core' ); - const { Publisher } = await import('@/scaling/pubsub/publisher.service'); + const { Publisher } = await import('@/scaling/pubsub/publisher.service.js'); const publisher = Container.get(Publisher); @@ -471,7 +471,7 @@ export class ScalingService { storedAt: executionData.storedAt, }; - const { McpService } = await import('@/modules/mcp/mcp.service'); + const { McpService } = await import('@/modules/mcp/mcp.service.js'); const mcpService = Container.get(McpService); mcpService.handleWorkerResponse(executionId, runData); } else { diff --git a/packages/cli/src/server.ts b/packages/cli/src/server.ts index f3643004d7e..ecbe9580cbc 100644 --- a/packages/cli/src/server.ts +++ b/packages/cli/src/server.ts @@ -101,10 +101,10 @@ export class Server extends AbstractServer { async start() { if (!this.globalConfig.endpoints.disableUi) { - const { FrontendService } = await import('@/services/frontend.service'); + const { FrontendService } = await import('@/services/frontend.service.js'); this.frontendService = Container.get(FrontendService); - await import('@/controllers/module-settings.controller'); - await import('@/controllers/third-party-licenses.controller'); + await import('@/controllers/module-settings.controller.js'); + await import('@/controllers/third-party-licenses.controller.js'); } this.presetCredentialsLoaded = false; @@ -125,29 +125,29 @@ export class Server extends AbstractServer { private async registerAdditionalControllers() { if (!inProduction && this.instanceSettings.isMultiMain) { - await import('@/controllers/debug.controller'); + await import('@/controllers/debug.controller.js'); } if (inE2ETests) { - await import('@/controllers/e2e.controller'); + await import('@/controllers/e2e.controller.js'); } if (isMfaFeatureEnabled()) { await Container.get(MfaService).init(); - await import('@/controllers/mfa.controller'); + await import('@/controllers/mfa.controller.js'); } if (!this.globalConfig.endpoints.disableUi) { - await import('@/controllers/cta.controller'); + await import('@/controllers/cta.controller.js'); } if (!this.globalConfig.tags.disabled) { - await import('@/controllers/tags.controller'); + await import('@/controllers/tags.controller.js'); } if (this.globalConfig.diagnostics.enabled) { - await import('@/controllers/telemetry.controller'); - await import('@/controllers/posthog.controller'); + await import('@/controllers/telemetry.controller.js'); + await import('@/controllers/posthog.controller.js'); } // ---------------------------------------- @@ -155,7 +155,7 @@ export class Server extends AbstractServer { // ---------------------------------------- try { - await import('@/environments.ee/variables/variables.controller.ee'); + await import('@/environments.ee/variables/variables.controller.ee.js'); } catch (error) { this.logger.warn(`Variables initialization failed: ${(error as Error).message}`); } @@ -163,7 +163,7 @@ export class Server extends AbstractServer { async configure(): Promise { if (this.globalConfig.endpoints.metrics.enable) { - const { PrometheusMetricsService } = await import('@/metrics/prometheus'); + const { PrometheusMetricsService } = await import('@/metrics/prometheus/index.js'); Container.get(PrometheusMetricsService).init(this.app); } @@ -216,7 +216,7 @@ export class Server extends AbstractServer { push.setupPushHandler(restEndpoint, app); if (push.isBidirectional) { - const { CollaborationService } = await import('@/collaboration/collaboration.service'); + const { CollaborationService } = await import('@/collaboration/collaboration.service.js'); const collaborationService = Container.get(CollaborationService); collaborationService.init(); @@ -227,7 +227,7 @@ export class Server extends AbstractServer { } if (this.globalConfig.executions.mode === 'queue') { - const { ScalingService } = await import('@/scaling/scaling.service'); + const { ScalingService } = await import('@/scaling/scaling.service.js'); await Container.get(ScalingService).setupQueue(); } @@ -503,7 +503,7 @@ export class Server extends AbstractServer { private async initializeWorkflowIndexing() { const { WorkflowIndexService } = await import( - '@/modules/workflow-index/workflow-index.service' + '@/modules/workflow-index/workflow-index.service.js' ); Container.get(WorkflowIndexService).init(); } diff --git a/packages/cli/src/services/ai-workflow-builder.service.ts b/packages/cli/src/services/ai-workflow-builder.service.ts index 74c0e7630a9..16f175b919c 100644 --- a/packages/cli/src/services/ai-workflow-builder.service.ts +++ b/packages/cli/src/services/ai-workflow-builder.service.ts @@ -1,6 +1,9 @@ -import { AiWorkflowBuilderService, createPassthroughSsrfGuard } from '@n8n/ai-workflow-builder'; import type { ResourceLocatorCallbackFactory } from '@n8n/ai-workflow-builder'; -import { ChatPayload } from '@n8n/ai-workflow-builder/dist/workflow-builder-agent'; +import { + AiWorkflowBuilderService, + createPassthroughSsrfGuard, + ChatPayload, +} from '@n8n/ai-workflow-builder'; import { Logger } from '@n8n/backend-common'; import { OutboundHttp, SsrfProtectionService } from '@n8n/backend-network'; import { GlobalConfig, SsrfProtectionConfig } from '@n8n/config'; diff --git a/packages/cli/src/services/cache/cache.service.ts b/packages/cli/src/services/cache/cache.service.ts index 6ad6a40229d..780e8acf2e6 100644 --- a/packages/cli/src/services/cache/cache.service.ts +++ b/packages/cli/src/services/cache/cache.service.ts @@ -36,7 +36,7 @@ export class CacheService extends TypedEmitter { const useRedis = backend === 'redis' || (backend === 'auto' && mode === 'queue'); if (useRedis) { - const { RedisClientService } = await import('../redis-client.service'); + const { RedisClientService } = await import('../redis-client.service.js'); const redisClientService = Container.get(RedisClientService); const prefixBase = this.globalConfig.redis.prefix; @@ -52,7 +52,7 @@ export class CacheService extends TypedEmitter { extraOptions: { keyPrefix: prefix }, }); - const { redisStoreUsingClient } = await import('@/services/cache/redis.cache-manager'); + const { redisStoreUsingClient } = await import('@/services/cache/redis.cache-manager.js'); const redisStore = redisStoreUsingClient(redisClient, { ttl: this.globalConfig.cache.redis.ttl, }); diff --git a/packages/cli/src/services/frontend.service.ts b/packages/cli/src/services/frontend.service.ts index 954ab095ba9..44a671654f4 100644 --- a/packages/cli/src/services/frontend.service.ts +++ b/packages/cli/src/services/frontend.service.ts @@ -146,7 +146,7 @@ export class FrontendService { void this.generateTypes(); // @TODO: Move to community-packages module if (Container.get(CommunityPackagesConfig).enabled) { - void import('@/modules/community-packages/community-packages.service').then( + void import('@/modules/community-packages/community-packages.service.js').then( ({ CommunityPackagesService }) => { this.communityPackagesService = Container.get(CommunityPackagesService); }, diff --git a/packages/cli/src/services/project.service.ee.ts b/packages/cli/src/services/project.service.ee.ts index 832a930039a..2431aece893 100644 --- a/packages/cli/src/services/project.service.ee.ts +++ b/packages/cli/src/services/project.service.ee.ts @@ -84,49 +84,49 @@ export class ProjectService { ) {} private get workflowService() { - return import('@/workflows/workflow.service').then(({ WorkflowService }) => + return import('@/workflows/workflow.service.js').then(({ WorkflowService }) => Container.get(WorkflowService), ); } private get credentialsService() { - return import('@/credentials/credentials.service').then(({ CredentialsService }) => + return import('@/credentials/credentials.service.js').then(({ CredentialsService }) => Container.get(CredentialsService), ); } private get folderService() { - return import('@/services/folder.service').then(({ FolderService }) => + return import('@/services/folder.service.js').then(({ FolderService }) => Container.get(FolderService), ); } private get dataTableService() { - return import('@/modules/data-table/data-table.service').then(({ DataTableService }) => + return import('@/modules/data-table/data-table.service.js').then(({ DataTableService }) => Container.get(DataTableService), ); } private get secretsProvidersConnectionsService() { - return import('@/modules/external-secrets.ee/secrets-providers-connections.service.ee').then( + return import('@/modules/external-secrets.ee/secrets-providers-connections.service.ee.js').then( ({ SecretsProvidersConnectionsService }) => Container.get(SecretsProvidersConnectionsService), ); } private get agentRepository() { - return import('@/modules/agents/repositories/agent.repository').then(({ AgentRepository }) => + return import('@/modules/agents/repositories/agent.repository.js').then(({ AgentRepository }) => Container.get(AgentRepository), ); } private get agentKnowledgeService() { - return import('@/modules/agents/agent-knowledge.service').then(({ AgentKnowledgeService }) => + return import('@/modules/agents/agent-knowledge.service.js').then(({ AgentKnowledgeService }) => Container.get(AgentKnowledgeService), ); } private get connectionStatusProxy() { - return import('@/credentials/credential-connection-status-proxy').then( + return import('@/credentials/credential-connection-status-proxy.js').then( ({ CredentialConnectionStatusProxy }) => Container.get(CredentialConnectionStatusProxy), ); } diff --git a/packages/cli/src/task-runners/task-runner-module.ts b/packages/cli/src/task-runners/task-runner-module.ts index 7db8a74dd8a..ffb829f5631 100644 --- a/packages/cli/src/task-runners/task-runner-module.ts +++ b/packages/cli/src/task-runners/task-runner-module.ts @@ -92,9 +92,9 @@ export class TaskRunnerModule { } private async loadTaskRequester() { - const { TaskRequester } = await import('@/task-runners/task-managers/task-requester'); + const { TaskRequester } = await import('@/task-runners/task-managers/task-requester.js'); const { LocalTaskRequester } = await import( - '@/task-runners/task-managers/local-task-requester' + '@/task-runners/task-managers/local-task-requester.js' ); this.taskRequester = Container.get(LocalTaskRequester); Container.set(TaskRequester, this.taskRequester); @@ -103,7 +103,7 @@ export class TaskRunnerModule { private async loadTaskBroker() { // These are imported dynamically because we need to set the task manager // instance before importing them - const { TaskBrokerServer } = await import('@/task-runners/task-broker/task-broker-server'); + const { TaskBrokerServer } = await import('@/task-runners/task-broker/task-broker-server.js'); this.taskBrokerHttpServer = Container.get(TaskBrokerServer); this.taskBrokerWsServer = Container.get(TaskBrokerWsServer); @@ -114,13 +114,13 @@ export class TaskRunnerModule { a.ok(this.taskBrokerWsServer, 'Task Runner WS Server not loaded'); const { InternalTaskRunnerDisconnectAnalyzer } = await import( - '@/task-runners/internal-task-runner-disconnect-analyzer' + '@/task-runners/internal-task-runner-disconnect-analyzer.js' ); this.taskBrokerWsServer.setDisconnectAnalyzer( Container.get(InternalTaskRunnerDisconnectAnalyzer), ); - const { JsTaskRunnerProcess } = await import('@/task-runners/task-runner-process-js'); + const { JsTaskRunnerProcess } = await import('@/task-runners/task-runner-process-js.js'); this.jsRunnerProcess = Container.get(JsTaskRunnerProcess); this.jsRunnerProcessRestartLoopDetector = new TaskRunnerProcessRestartLoopDetector( this.jsRunnerProcess, @@ -132,7 +132,7 @@ export class TaskRunnerModule { await this.jsRunnerProcess.start(); - const { PyTaskRunnerProcess } = await import('@/task-runners/task-runner-process-py'); + const { PyTaskRunnerProcess } = await import('@/task-runners/task-runner-process-py.js'); const failureReason = await PyTaskRunnerProcess.checkRequirements(); if (failureReason) { diff --git a/packages/cli/src/webhooks/webhook-helpers.ts b/packages/cli/src/webhooks/webhook-helpers.ts index 88688221a76..6beec152c48 100644 --- a/packages/cli/src/webhooks/webhook-helpers.ts +++ b/packages/cli/src/webhooks/webhook-helpers.ts @@ -778,7 +778,7 @@ export async function executeWebhook( const mcpListToolsRelayValue = firstItem && 'json' in firstItem ? firstItem.json?.mcpListToolsRelay : null; if (isMcpListToolsRelay(mcpListToolsRelayValue)) { - const { Publisher } = await import('@/scaling/pubsub/publisher.service'); + const { Publisher } = await import('@/scaling/pubsub/publisher.service.js'); const publisher = Container.get(Publisher); await publisher.publishMcpRelay({ sessionId: mcpListToolsRelayValue.sessionId, diff --git a/packages/cli/src/webhooks/webhook-server.ts b/packages/cli/src/webhooks/webhook-server.ts index 644450514a5..0e09840b1d9 100644 --- a/packages/cli/src/webhooks/webhook-server.ts +++ b/packages/cli/src/webhooks/webhook-server.ts @@ -8,7 +8,7 @@ export class WebhookServer extends AbstractServer { /** Mounts `/metrics` so dedicated webhook procs are scrapeable. */ async configure(): Promise { if (this.globalConfig.endpoints.metrics.enable) { - const { PrometheusMetricsService } = await import('@/metrics/prometheus'); + const { PrometheusMetricsService } = await import('@/metrics/prometheus/index.js'); Container.get(PrometheusMetricsService).init(this.app); } } diff --git a/packages/cli/src/workflow-execute-additional-data.ts b/packages/cli/src/workflow-execute-additional-data.ts index 05ad80754aa..c4bc8fb069d 100644 --- a/packages/cli/src/workflow-execute-additional-data.ts +++ b/packages/cli/src/workflow-execute-additional-data.ts @@ -363,7 +363,7 @@ export async function executeAgent( // `webhooks/*`, `scaling/job-processor`). Resolve it from the workflow's // owning project so the agent runs under the correct project scope. if (!projectId && additionalData.workflowId) { - const { OwnershipService } = await import('@/services/ownership.service'); + const { OwnershipService } = await import('@/services/ownership.service.js'); const ownershipService = Container.get(OwnershipService); const project = await ownershipService.getWorkflowProjectCached(additionalData.workflowId); projectId = project.id; @@ -376,7 +376,7 @@ export async function executeAgent( } const { AgentExecutionOrchestratorService } = await import( - '@/modules/agents/agent-execution-orchestrator.service' + '@/modules/agents/agent-execution-orchestrator.service.js' ); const agentExecutionOrchestratorService = Container.get(AgentExecutionOrchestratorService); @@ -425,7 +425,7 @@ export async function executeAgent( } async function listAgents(userId: string): Promise> { - const { AgentsService } = await import('@/modules/agents/agents.service'); + const { AgentsService } = await import('@/modules/agents/agents.service.js'); const agentsService = Container.get(AgentsService); // Only published agents are runnable from a published workflow. // But unpublished agents may be called from manual workflow executions (e.g. during development), so they are included in the list as well. diff --git a/packages/cli/src/workflow-runner.ts b/packages/cli/src/workflow-runner.ts index 1e72b2fee1a..cc201dc954c 100644 --- a/packages/cli/src/workflow-runner.ts +++ b/packages/cli/src/workflow-runner.ts @@ -519,7 +519,7 @@ export class WorkflowRunner { }; if (!this.scalingService) { - const { ScalingService } = await import('@/scaling/scaling.service'); + const { ScalingService } = await import('@/scaling/scaling.service.js'); this.scalingService = Container.get(ScalingService); await this.scalingService.setupQueue(); } diff --git a/packages/cli/src/workflows/publication/workflow-publication-notifier.ts b/packages/cli/src/workflows/publication/workflow-publication-notifier.ts index 90d9f3c3eab..a1e6f1842e4 100644 --- a/packages/cli/src/workflows/publication/workflow-publication-notifier.ts +++ b/packages/cli/src/workflows/publication/workflow-publication-notifier.ts @@ -45,7 +45,7 @@ export class WorkflowPublicationNotifier { */ private async wakeLocalConsumer(): Promise { const { WorkflowPublicationOutboxConsumer } = await import( - './workflow-publication-outbox-consumer' + './workflow-publication-outbox-consumer.js' ); await Container.get(WorkflowPublicationOutboxConsumer).wakeUp(); } diff --git a/packages/cli/src/workflows/workflow.service.ts b/packages/cli/src/workflows/workflow.service.ts index af13ee38ffd..131bfa59b3c 100644 --- a/packages/cli/src/workflows/workflow.service.ts +++ b/packages/cli/src/workflows/workflow.service.ts @@ -232,7 +232,7 @@ export class WorkflowService { T extends ListQueryDb.Workflow.Plain | ListQueryDb.Workflow.WithSharing, >(workflows: T[]): Promise> { // Use lazy import to avoid circular dependency - const { EnterpriseWorkflowService } = await import('./workflow.service.ee'); + const { EnterpriseWorkflowService } = await import('./workflow.service.ee.js'); const enterpriseWorkflowService = Container.get(EnterpriseWorkflowService); const workflowIds = workflows.map((w) => w.id); @@ -392,7 +392,7 @@ export class WorkflowService { // Loaded lazily to avoid a circular import (workflow.service.ee pulls in // folder/project services which import this module). if (this.licenseState.isSharingLicensed()) { - const { EnterpriseWorkflowService } = await import('./workflow.service.ee'); + const { EnterpriseWorkflowService } = await import('./workflow.service.ee.js'); await Container.get(EnterpriseWorkflowService).preventTampering( workflowUpdateData, workflowId, diff --git a/packages/cli/test/integration/database/repositories/credentials.repository.test.ts b/packages/cli/test/integration/database/repositories/credentials.repository.test.ts index ec24cdedcfb..2a794048326 100644 --- a/packages/cli/test/integration/database/repositories/credentials.repository.test.ts +++ b/packages/cli/test/integration/database/repositories/credentials.repository.test.ts @@ -59,9 +59,9 @@ describe('CredentialsRepository', () => { it('should fetch credentials using subquery for standard user with roles', async () => { // ARRANGE - const { createMember } = await import('../../shared/db/users'); + const { createMember } = await import('../../shared/db/users.js'); const { createTeamProject, linkUserToProject } = await import('@n8n/backend-test-utils'); - const { createCredentials } = await import('../../shared/db/credentials'); + const { createCredentials } = await import('../../shared/db/credentials.js'); const member = await createMember(); const teamProject = await createTeamProject('test-project'); @@ -97,9 +97,9 @@ describe('CredentialsRepository', () => { it('should handle personal project filtering correctly', async () => { // ARRANGE - const { createOwner } = await import('../../shared/db/users'); + const { createOwner } = await import('../../shared/db/users.js'); const { getPersonalProject } = await import('@n8n/backend-test-utils'); - const { createCredentials } = await import('../../shared/db/credentials'); + const { createCredentials } = await import('../../shared/db/credentials.js'); const owner = await createOwner(); const personalProject = await getPersonalProject(owner); @@ -125,9 +125,9 @@ describe('CredentialsRepository', () => { it('should handle onlySharedWithMe filter correctly', async () => { // ARRANGE - const { createMember } = await import('../../shared/db/users'); + const { createMember } = await import('../../shared/db/users.js'); const { getPersonalProject } = await import('@n8n/backend-test-utils'); - const { createCredentials } = await import('../../shared/db/credentials'); + const { createCredentials } = await import('../../shared/db/credentials.js'); const member = await createMember(); const memberPersonalProject = await getPersonalProject(member); @@ -158,9 +158,9 @@ describe('CredentialsRepository', () => { it('should apply name filter correctly with subquery approach', async () => { // ARRANGE - const { createOwner } = await import('../../shared/db/users'); + const { createOwner } = await import('../../shared/db/users.js'); const { getPersonalProject } = await import('@n8n/backend-test-utils'); - const { createCredentials } = await import('../../shared/db/credentials'); + const { createCredentials } = await import('../../shared/db/credentials.js'); const owner = await createOwner(); const personalProject = await getPersonalProject(owner); @@ -190,9 +190,9 @@ describe('CredentialsRepository', () => { it('should apply type filter correctly with subquery approach', async () => { // ARRANGE - const { createOwner } = await import('../../shared/db/users'); + const { createOwner } = await import('../../shared/db/users.js'); const { getPersonalProject } = await import('@n8n/backend-test-utils'); - const { createCredentials } = await import('../../shared/db/credentials'); + const { createCredentials } = await import('../../shared/db/credentials.js'); const owner = await createOwner(); const personalProject = await getPersonalProject(owner); @@ -222,9 +222,9 @@ describe('CredentialsRepository', () => { it('should handle pagination correctly with subquery approach', async () => { // ARRANGE - const { createOwner } = await import('../../shared/db/users'); + const { createOwner } = await import('../../shared/db/users.js'); const { getPersonalProject } = await import('@n8n/backend-test-utils'); - const { createCredentials } = await import('../../shared/db/credentials'); + const { createCredentials } = await import('../../shared/db/credentials.js'); const owner = await createOwner(); const personalProject = await getPersonalProject(owner); @@ -276,9 +276,9 @@ describe('CredentialsRepository', () => { it('should correctly filter credentials by project when credentials belong to multiple projects', async () => { // ARRANGE - const { createMember } = await import('../../shared/db/users'); + const { createMember } = await import('../../shared/db/users.js'); const { createTeamProject, linkUserToProject } = await import('@n8n/backend-test-utils'); - const { createCredentials } = await import('../../shared/db/credentials'); + const { createCredentials } = await import('../../shared/db/credentials.js'); const member = await createMember(); const projectA = await createTeamProject('Project A'); @@ -346,9 +346,9 @@ describe('CredentialsRepository', () => { it('should correctly isolate credentials by user - each user sees only their credentials', async () => { // ARRANGE - const { createMember } = await import('../../shared/db/users'); + const { createMember } = await import('../../shared/db/users.js'); const { createTeamProject, linkUserToProject } = await import('@n8n/backend-test-utils'); - const { createCredentials } = await import('../../shared/db/credentials'); + const { createCredentials } = await import('../../shared/db/credentials.js'); const userA = await createMember(); const userB = await createMember(); @@ -422,11 +422,13 @@ describe('CredentialsRepository', () => { it('should return identical results for standard user with both approaches', async () => { // ARRANGE - const { createMember } = await import('../../shared/db/users'); + const { createMember } = await import('../../shared/db/users.js'); const { createTeamProject, linkUserToProject } = await import('@n8n/backend-test-utils'); - const { createCredentials } = await import('../../shared/db/credentials'); - const { CredentialsFinderService } = await import('@/credentials/credentials-finder.service'); - const { RoleService } = await import('@/services/role.service'); + const { createCredentials } = await import('../../shared/db/credentials.js'); + const { CredentialsFinderService } = await import( + '@/credentials/credentials-finder.service.js' + ); + const { RoleService } = await import('@/services/role.service.js'); const member = await createMember(); const teamProject = await createTeamProject('test-project'); @@ -469,10 +471,12 @@ describe('CredentialsRepository', () => { it('should return identical results for personal project with both approaches', async () => { // ARRANGE - const { createOwner } = await import('../../shared/db/users'); + const { createOwner } = await import('../../shared/db/users.js'); const { getPersonalProject } = await import('@n8n/backend-test-utils'); - const { createCredentials } = await import('../../shared/db/credentials'); - const { CredentialsFinderService } = await import('@/credentials/credentials-finder.service'); + const { createCredentials } = await import('../../shared/db/credentials.js'); + const { CredentialsFinderService } = await import( + '@/credentials/credentials-finder.service.js' + ); const owner = await createOwner(); const personalProject = await getPersonalProject(owner); @@ -512,11 +516,13 @@ describe('CredentialsRepository', () => { it('should return identical results with filters and pagination', async () => { // ARRANGE - const { createMember } = await import('../../shared/db/users'); + const { createMember } = await import('../../shared/db/users.js'); const { createTeamProject, linkUserToProject } = await import('@n8n/backend-test-utils'); - const { createCredentials } = await import('../../shared/db/credentials'); - const { CredentialsFinderService } = await import('@/credentials/credentials-finder.service'); - const { RoleService } = await import('@/services/role.service'); + const { createCredentials } = await import('../../shared/db/credentials.js'); + const { CredentialsFinderService } = await import( + '@/credentials/credentials-finder.service.js' + ); + const { RoleService } = await import('@/services/role.service.js'); const member = await createMember(); const teamProject = await createTeamProject('test-project'); @@ -576,11 +582,13 @@ describe('CredentialsRepository', () => { it('should correctly filter credentials by project - old vs new comparison', async () => { // ARRANGE - const { createMember } = await import('../../shared/db/users'); + const { createMember } = await import('../../shared/db/users.js'); const { createTeamProject, linkUserToProject } = await import('@n8n/backend-test-utils'); - const { createCredentials } = await import('../../shared/db/credentials'); - const { CredentialsFinderService } = await import('@/credentials/credentials-finder.service'); - const { RoleService } = await import('@/services/role.service'); + const { createCredentials } = await import('../../shared/db/credentials.js'); + const { CredentialsFinderService } = await import( + '@/credentials/credentials-finder.service.js' + ); + const { RoleService } = await import('@/services/role.service.js'); const member = await createMember(); @@ -661,11 +669,13 @@ describe('CredentialsRepository', () => { it('should correctly isolate credentials by user - old vs new comparison', async () => { // ARRANGE - const { createMember } = await import('../../shared/db/users'); + const { createMember } = await import('../../shared/db/users.js'); const { createTeamProject, linkUserToProject } = await import('@n8n/backend-test-utils'); - const { createCredentials } = await import('../../shared/db/credentials'); - const { CredentialsFinderService } = await import('@/credentials/credentials-finder.service'); - const { RoleService } = await import('@/services/role.service'); + const { createCredentials } = await import('../../shared/db/credentials.js'); + const { CredentialsFinderService } = await import( + '@/credentials/credentials-finder.service.js' + ); + const { RoleService } = await import('@/services/role.service.js'); // Create two separate users const userA = await createMember(); diff --git a/packages/cli/test/integration/database/repositories/workflow.repository.test.ts b/packages/cli/test/integration/database/repositories/workflow.repository.test.ts index e685d205c44..98659b25cb2 100644 --- a/packages/cli/test/integration/database/repositories/workflow.repository.test.ts +++ b/packages/cli/test/integration/database/repositories/workflow.repository.test.ts @@ -753,7 +753,7 @@ describe('WorkflowRepository', () => { it('should fetch workflows using subquery for standard user with roles', async () => { // ARRANGE - const { createMember } = await import('../../shared/db/users'); + const { createMember } = await import('../../shared/db/users.js'); const { createTeamProject, linkUserToProject } = await import('@n8n/backend-test-utils'); const member = await createMember(); @@ -790,7 +790,7 @@ describe('WorkflowRepository', () => { it('should handle personal project filtering correctly', async () => { // ARRANGE - const { createOwner } = await import('../../shared/db/users'); + const { createOwner } = await import('../../shared/db/users.js'); const { getPersonalProject } = await import('@n8n/backend-test-utils'); const owner = await createOwner(); @@ -817,7 +817,7 @@ describe('WorkflowRepository', () => { it('should handle onlySharedWithMe filter correctly', async () => { // ARRANGE - const { createMember } = await import('../../shared/db/users'); + const { createMember } = await import('../../shared/db/users.js'); const { getPersonalProject } = await import('@n8n/backend-test-utils'); const member = await createMember(); @@ -841,7 +841,7 @@ describe('WorkflowRepository', () => { it('should apply filters correctly with subquery approach', async () => { // ARRANGE - const { createOwner } = await import('../../shared/db/users'); + const { createOwner } = await import('../../shared/db/users.js'); const { getPersonalProject } = await import('@n8n/backend-test-utils'); const owner = await createOwner(); @@ -872,7 +872,7 @@ describe('WorkflowRepository', () => { it('should handle pagination correctly with subquery approach', async () => { // ARRANGE - const { createOwner } = await import('../../shared/db/users'); + const { createOwner } = await import('../../shared/db/users.js'); const { getPersonalProject } = await import('@n8n/backend-test-utils'); const owner = await createOwner(); @@ -942,9 +942,9 @@ describe('WorkflowRepository', () => { it('should fetch both workflows and folders using subquery approach', async () => { // ARRANGE - const { createOwner } = await import('../../shared/db/users'); + const { createOwner } = await import('../../shared/db/users.js'); const { getPersonalProject } = await import('@n8n/backend-test-utils'); - const { createFolder } = await import('../../shared/db/folders'); + const { createFolder } = await import('../../shared/db/folders.js'); const owner = await createOwner(); const personalProject = await getPersonalProject(owner); @@ -972,9 +972,9 @@ describe('WorkflowRepository', () => { it('should handle complex filtering in union query with subquery', async () => { // ARRANGE - const { createOwner } = await import('../../shared/db/users'); + const { createOwner } = await import('../../shared/db/users.js'); const { getPersonalProject } = await import('@n8n/backend-test-utils'); - const { createFolder } = await import('../../shared/db/folders'); + const { createFolder } = await import('../../shared/db/folders.js'); const owner = await createOwner(); const personalProject = await getPersonalProject(owner); @@ -1023,10 +1023,10 @@ describe('WorkflowRepository', () => { it('should return identical results for standard user with both approaches', async () => { // ARRANGE - const { createMember } = await import('../../shared/db/users'); + const { createMember } = await import('../../shared/db/users.js'); const { createTeamProject, linkUserToProject } = await import('@n8n/backend-test-utils'); - const { WorkflowSharingService } = await import('@/workflows/workflow-sharing.service'); - const { RoleService } = await import('@/services/role.service'); + const { WorkflowSharingService } = await import('@/workflows/workflow-sharing.service.js'); + const { RoleService } = await import('@/services/role.service.js'); const member = await createMember(); const teamProject = await createTeamProject('test-project'); @@ -1068,9 +1068,9 @@ describe('WorkflowRepository', () => { it('should return identical results for personal project with both approaches', async () => { // ARRANGE - const { createOwner } = await import('../../shared/db/users'); + const { createOwner } = await import('../../shared/db/users.js'); const { getPersonalProject } = await import('@n8n/backend-test-utils'); - const { WorkflowSharingService } = await import('@/workflows/workflow-sharing.service'); + const { WorkflowSharingService } = await import('@/workflows/workflow-sharing.service.js'); const owner = await createOwner(); const personalProject = await getPersonalProject(owner); @@ -1109,9 +1109,9 @@ describe('WorkflowRepository', () => { it('should return identical results with filters and pagination', async () => { // ARRANGE - const { createOwner } = await import('../../shared/db/users'); + const { createOwner } = await import('../../shared/db/users.js'); const { getPersonalProject } = await import('@n8n/backend-test-utils'); - const { WorkflowSharingService } = await import('@/workflows/workflow-sharing.service'); + const { WorkflowSharingService } = await import('@/workflows/workflow-sharing.service.js'); const owner = await createOwner(); const personalProject = await getPersonalProject(owner); @@ -1161,10 +1161,10 @@ describe('WorkflowRepository', () => { it('should correctly filter workflows by project when workflows belong to multiple projects', async () => { // ARRANGE - const { createMember } = await import('../../shared/db/users'); + const { createMember } = await import('../../shared/db/users.js'); const { createTeamProject, linkUserToProject } = await import('@n8n/backend-test-utils'); - const { WorkflowSharingService } = await import('@/workflows/workflow-sharing.service'); - const { RoleService } = await import('@/services/role.service'); + const { WorkflowSharingService } = await import('@/workflows/workflow-sharing.service.js'); + const { RoleService } = await import('@/services/role.service.js'); const member = await createMember(); @@ -1243,10 +1243,10 @@ describe('WorkflowRepository', () => { it('should correctly isolate workflows by user - each user sees only their workflows', async () => { // ARRANGE - const { createMember } = await import('../../shared/db/users'); + const { createMember } = await import('../../shared/db/users.js'); const { createTeamProject, linkUserToProject } = await import('@n8n/backend-test-utils'); - const { WorkflowSharingService } = await import('@/workflows/workflow-sharing.service'); - const { RoleService } = await import('@/services/role.service'); + const { WorkflowSharingService } = await import('@/workflows/workflow-sharing.service.js'); + const { RoleService } = await import('@/services/role.service.js'); // Create two separate users const userA = await createMember(); diff --git a/packages/cli/test/integration/external-secrets/secret-providers-connections.api.test.ts b/packages/cli/test/integration/external-secrets/secret-providers-connections.api.test.ts index d814fca1eb1..653623d9cc2 100644 --- a/packages/cli/test/integration/external-secrets/secret-providers-connections.api.test.ts +++ b/packages/cli/test/integration/external-secrets/secret-providers-connections.api.test.ts @@ -755,18 +755,18 @@ describe('Secret Providers Connections API', () => { describe('Reload connection secrets', () => { beforeAll(async () => { - const { DummyProvider } = await import('../../shared/external-secrets/utils'); + const { DummyProvider } = await import('../../shared/external-secrets/utils.js'); mockProvidersInstance.setProviders({ awsSecretsManager: DummyProvider }); }); afterEach(async () => { - const { DummyProvider } = await import('../../shared/external-secrets/utils'); + const { DummyProvider } = await import('../../shared/external-secrets/utils.js'); mockProvidersInstance.setProviders({ awsSecretsManager: DummyProvider }); }); test('should successfully reload connection secrets', async () => { const { ExternalSecretsManager } = await import( - '@/modules/external-secrets.ee/external-secrets-manager.ee' + '@/modules/external-secrets.ee/external-secrets-manager.ee.js' ); await ownerAgent @@ -811,7 +811,7 @@ describe('Secret Providers Connections API', () => { { role: 'member', allowed: false }, ])('should allow=$allowed for $role to reload connection', async ({ role, allowed }) => { const { ExternalSecretsManager } = await import( - '@/modules/external-secrets.ee/external-secrets-manager.ee' + '@/modules/external-secrets.ee/external-secrets-manager.ee.js' ); const providerKey = `reloadAccess${role.charAt(0).toUpperCase() + role.slice(1)}`; @@ -842,12 +842,12 @@ describe('Secret Providers Connections API', () => { describe('Test connection', () => { beforeAll(async () => { - const { DummyProvider } = await import('../../shared/external-secrets/utils'); + const { DummyProvider } = await import('../../shared/external-secrets/utils.js'); mockProvidersInstance.setProviders({ awsSecretsManager: DummyProvider }); }); afterEach(async () => { - const { DummyProvider } = await import('../../shared/external-secrets/utils'); + const { DummyProvider } = await import('../../shared/external-secrets/utils.js'); mockProvidersInstance.setProviders({ awsSecretsManager: DummyProvider }); }); @@ -870,7 +870,7 @@ describe('Secret Providers Connections API', () => { }); test('should return failure when provider test fails', async () => { - const { TestFailProvider } = await import('../../shared/external-secrets/utils'); + const { TestFailProvider } = await import('../../shared/external-secrets/utils.js'); mockProvidersInstance.setProviders({ awsSecretsManager: TestFailProvider }); @@ -892,7 +892,7 @@ describe('Secret Providers Connections API', () => { }); test('should return error when connection fails', async () => { - const { FailedProvider } = await import('../../shared/external-secrets/utils'); + const { FailedProvider } = await import('../../shared/external-secrets/utils.js'); mockProvidersInstance.setProviders({ awsSecretsManager: FailedProvider }); diff --git a/packages/cli/test/integration/external-secrets/secret-providers-project.api.test.ts b/packages/cli/test/integration/external-secrets/secret-providers-project.api.test.ts index 02f2b8e20d0..5f7570d0c23 100644 --- a/packages/cli/test/integration/external-secrets/secret-providers-project.api.test.ts +++ b/packages/cli/test/integration/external-secrets/secret-providers-project.api.test.ts @@ -641,7 +641,7 @@ describe('Secret Providers Project API', () => { beforeAll(async () => { agents = { owner: ownerAgent, admin: adminAgent, member: memberAgent }; - const { DummyProvider } = await import('../../shared/external-secrets/utils'); + const { DummyProvider } = await import('../../shared/external-secrets/utils.js'); mockProvidersInstance.setProviders({ dummy: DummyProvider, awsSecretsManager: DummyProvider, @@ -664,7 +664,7 @@ describe('Secret Providers Project API', () => { await createProviderConnection('test-conn', [teamProject1.id]); const { ExternalSecretsManager } = await import( - '@/modules/external-secrets.ee/external-secrets-manager.ee' + '@/modules/external-secrets.ee/external-secrets-manager.ee.js' ); await Container.get(ExternalSecretsManager).reloadAllProviders(); @@ -708,7 +708,7 @@ describe('Secret Providers Project API', () => { await createProviderConnection(`test-auth-${role}`, [teamProject1.id]); const { ExternalSecretsManager } = await import( - '@/modules/external-secrets.ee/external-secrets-manager.ee' + '@/modules/external-secrets.ee/external-secrets-manager.ee.js' ); await Container.get(ExternalSecretsManager).reloadAllProviders(); diff --git a/packages/cli/test/integration/public-api/endpoints-with-scopes-enabled.test.ts b/packages/cli/test/integration/public-api/endpoints-with-scopes-enabled.test.ts index e5da4efe5a8..811bf60ae48 100644 --- a/packages/cli/test/integration/public-api/endpoints-with-scopes-enabled.test.ts +++ b/packages/cli/test/integration/public-api/endpoints-with-scopes-enabled.test.ts @@ -78,9 +78,9 @@ describe('Public API endpoints with API key scopes', () => { // N8N_ENV_FEAT_TOKEN_EXCHANGE env flag. We register it directly here to test // the auth layer in isolation without triggering the full module boot. const { ScopedJwtStrategy } = await import( - '@/modules/token-exchange/services/scoped-jwt.strategy' + '@/modules/token-exchange/services/scoped-jwt.strategy.js' ); - const { AuthStrategyRegistry } = await import('@/services/auth-strategy.registry'); + const { AuthStrategyRegistry } = await import('@/services/auth-strategy.registry.js'); Container.get(AuthStrategyRegistry).register(Container.get(ScopedJwtStrategy)); }); diff --git a/packages/cli/test/integration/shared/db/credentials.ts b/packages/cli/test/integration/shared/db/credentials.ts index a7f6c9ce7dd..2cf3a0d0341 100644 --- a/packages/cli/test/integration/shared/db/credentials.ts +++ b/packages/cli/test/integration/shared/db/credentials.ts @@ -12,7 +12,7 @@ import type { CredentialSharingRole } from '@n8n/permissions'; export async function encryptCredentialData( credential: CredentialsEntity, ): Promise { - const { createCredentialsFromCredentialsEntity } = await import('@/credentials-helper'); + const { createCredentialsFromCredentialsEntity } = await import('@/credentials-helper.js'); const coreCredential = createCredentialsFromCredentialsEntity(credential, true); // @ts-ignore @@ -22,7 +22,7 @@ export async function encryptCredentialData( } export async function decryptCredentialData(credential: ICredentialsDb): Promise { - const { createCredentialsFromCredentialsEntity } = await import('@/credentials-helper'); + const { createCredentialsFromCredentialsEntity } = await import('@/credentials-helper.js'); const coreCredential = createCredentialsFromCredentialsEntity(credential); return await coreCredential.getData(); diff --git a/packages/cli/test/integration/shared/utils/index.ts b/packages/cli/test/integration/shared/utils/index.ts index 89a3d4db4c4..58ea68f38c4 100644 --- a/packages/cli/test/integration/shared/utils/index.ts +++ b/packages/cli/test/integration/shared/utils/index.ts @@ -49,7 +49,7 @@ export async function initActiveWorkflowManager() { mockInstance(Push); mockInstance(ExecutionService); - const { ActiveWorkflowManager } = await import('@/active-workflow-manager'); + const { ActiveWorkflowManager } = await import('@/active-workflow-manager.js'); const activeWorkflowManager = Container.get(ActiveWorkflowManager); await activeWorkflowManager.init(); return activeWorkflowManager; diff --git a/packages/cli/test/integration/shared/utils/test-server.ts b/packages/cli/test/integration/shared/utils/test-server.ts index 5d055199403..39559e7c13a 100644 --- a/packages/cli/test/integration/shared/utils/test-server.ts +++ b/packages/cli/test/integration/shared/utils/test-server.ts @@ -160,7 +160,7 @@ export const setupTestServer = ({ const enablePublicAPI = endpointGroups?.includes('publicApi'); if (enablePublicAPI) { - const { loadPublicApiVersions } = await import('@/public-api'); + const { loadPublicApiVersions } = await import('@/public-api/index.js'); const { apiRouters } = await loadPublicApiVersions(PUBLIC_API_REST_PATH_SEGMENT); app.use(...apiRouters); } @@ -180,190 +180,190 @@ export const setupTestServer = ({ for (const group of endpointGroups) { switch (group) { case 'annotationTags': - await import('@/controllers/annotation-tags.controller.ee'); + await import('@/controllers/annotation-tags.controller.ee.js'); break; case 'credentials': - await import('@/credentials/credentials.controller'); + await import('@/credentials/credentials.controller.js'); break; case 'workflows': - await import('@/workflows/workflows.controller'); + await import('@/workflows/workflows.controller.js'); break; case 'workflowDependencies': - await import('@/modules/workflow-index/workflow-dependency.controller'); + await import('@/modules/workflow-index/workflow-dependency.controller.js'); break; case 'executions': - await import('@/executions/executions.controller'); + await import('@/executions/executions.controller.js'); break; case 'variables': - await import('@/environments.ee/variables/variables.controller.ee'); + await import('@/environments.ee/variables/variables.controller.ee.js'); break; case 'license': - await import('@/license/license.controller'); + await import('@/license/license.controller.js'); break; case 'metrics': { // CacheService must be initialized before PrometheusMetricsService // because cache-metrics.service calls isRedis() during init, which // reads this.cache.kind — only set after CacheService.init() resolves. - const { CacheService } = await import('@/services/cache/cache.service'); + const { CacheService } = await import('@/services/cache/cache.service.js'); await Container.get(CacheService).init(); - const { PrometheusMetricsService } = await import('@/metrics/prometheus'); + const { PrometheusMetricsService } = await import('@/metrics/prometheus/index.js'); Container.get(PrometheusMetricsService).init(app); break; } case 'eventBus': - await import('@/modules/log-streaming.ee/log-streaming.controller'); + await import('@/modules/log-streaming.ee/log-streaming.controller.js'); break; case 'auth': - await import('@/controllers/auth.controller'); + await import('@/controllers/auth.controller.js'); break; case 'oauth1': - await import('@/controllers/oauth/oauth1-credential.controller'); + await import('@/controllers/oauth/oauth1-credential.controller.js'); break; case 'oauth2': - await import('@/controllers/oauth/oauth2-credential.controller'); + await import('@/controllers/oauth/oauth2-credential.controller.js'); break; case 'mfa': - await import('@/controllers/mfa.controller'); + await import('@/controllers/mfa.controller.js'); break; case 'ldap': { - const { LdapService } = await import('@/modules/ldap.ee/ldap.service.ee'); - await import('@/modules/ldap.ee/ldap.controller.ee'); + const { LdapService } = await import('@/modules/ldap.ee/ldap.service.ee.js'); + await import('@/modules/ldap.ee/ldap.controller.ee.js'); testServer.license.enable('feat:ldap'); await Container.get(LdapService).init(); break; } case 'saml': { - const { SamlService } = await import('@/modules/sso-saml/saml.service.ee'); + const { SamlService } = await import('@/modules/sso-saml/saml.service.ee.js'); await Container.get(SamlService).init(); - await import('@/modules/sso-saml/saml.controller.ee'); - const { setSamlLoginEnabled } = await import('@/modules/sso-saml/saml-helpers'); + await import('@/modules/sso-saml/saml.controller.ee.js'); + const { setSamlLoginEnabled } = await import('@/modules/sso-saml/saml-helpers.js'); await setSamlLoginEnabled(true); break; } case 'sourceControl': - await import('@/modules/source-control.ee/source-control.controller.ee'); + await import('@/modules/source-control.ee/source-control.controller.ee.js'); break; case 'community-packages': - await import('@/modules/community-packages/community-packages.controller'); + await import('@/modules/community-packages/community-packages.controller.js'); break; case 'me': - await import('@/controllers/me.controller'); + await import('@/controllers/me.controller.js'); break; case 'passwordReset': - await import('@/controllers/password-reset.controller'); + await import('@/controllers/password-reset.controller.js'); break; case 'owner': - await import('@/controllers/owner.controller'); + await import('@/controllers/owner.controller.js'); break; case 'users': - await import('@/controllers/users.controller'); + await import('@/controllers/users.controller.js'); break; case 'invitations': - await import('@/controllers/invitation.controller'); + await import('@/controllers/invitation.controller.js'); break; case 'tags': - await import('@/controllers/tags.controller'); + await import('@/controllers/tags.controller.js'); break; case 'workflowHistory': - await import('@/workflows/workflow-history/workflow-history.controller'); + await import('@/workflows/workflow-history/workflow-history.controller.js'); break; case 'binaryData': - await import('@/controllers/binary-data.controller'); + await import('@/controllers/binary-data.controller.js'); break; case 'debug': - await import('@/controllers/debug.controller'); + await import('@/controllers/debug.controller.js'); break; case 'project': - await import('@/controllers/project.controller'); + await import('@/controllers/project.controller.js'); break; case 'role': - await import('@/controllers/role.controller'); + await import('@/controllers/role.controller.js'); break; case 'roleMappingRule': - await import('@/modules/provisioning.ee/role-mapping-rule.controller.ee'); + await import('@/modules/provisioning.ee/role-mapping-rule.controller.ee.js'); break; case 'dynamic-node-parameters': - await import('@/controllers/dynamic-node-parameters.controller'); + await import('@/controllers/dynamic-node-parameters.controller.js'); break; case 'apiKeys': - await import('@/controllers/api-keys.controller'); + await import('@/controllers/api-keys.controller.js'); break; case 'evaluation': - await import('@/evaluation.ee/test-runs.controller.ee'); + await import('@/evaluation.ee/test-runs.controller.ee.js'); break; case 'ai': - await import('@/controllers/ai.controller'); + await import('@/controllers/ai.controller.js'); break; case 'folder': - await import('@/controllers/folder.controller'); + await import('@/controllers/folder.controller.js'); break; case 'externalSecrets': - await import('@/modules/external-secrets.ee/external-secrets.module'); + await import('@/modules/external-secrets.ee/external-secrets.module.js'); break; case 'insights': - await import('@/modules/insights/insights.module'); + await import('@/modules/insights/insights.module.js'); break; case 'data-table': - await import('@/modules/data-table/data-table.module'); + await import('@/modules/data-table/data-table.module.js'); break; case 'mcp': - await import('@/modules/mcp/mcp.module'); + await import('@/modules/mcp/mcp.module.js'); break; case 'module-settings': - await import('@/controllers/module-settings.controller'); + await import('@/controllers/module-settings.controller.js'); break; case 'security-settings': - await import('@/controllers/security-settings.controller'); + await import('@/controllers/security-settings.controller.js'); break; case 'third-party-licenses': - await import('@/controllers/third-party-licenses.controller'); + await import('@/controllers/third-party-licenses.controller.js'); break; case 'encryption-keys': - await import('@/modules/encryption-key-manager/encryption-key.controller'); + await import('@/modules/encryption-key-manager/encryption-key.controller.js'); break; case 'test-webhooks': - await import('@/webhooks/test-webhooks.controller'); + await import('@/webhooks/test-webhooks.controller.js'); break; } } diff --git a/packages/cli/test/integration/task-runners/js-task-runner-execution.integration.test.ts b/packages/cli/test/integration/task-runners/js-task-runner-execution.integration.test.ts index dfa736cd758..61e61e4f53a 100644 --- a/packages/cli/test/integration/task-runners/js-task-runner-execution.integration.test.ts +++ b/packages/cli/test/integration/task-runners/js-task-runner-execution.integration.test.ts @@ -239,7 +239,7 @@ describe('JS TaskRunner execution on internal mode', () => { beforeAll(async () => { process.env.NODE_FUNCTION_ALLOW_BUILTIN = 'crypto'; process.env.NODE_FUNCTION_ALLOW_EXTERNAL = 'moment'; - const { TaskBroker } = await import('@/task-runners/task-broker/task-broker.service'); + const { TaskBroker } = await import('@/task-runners/task-broker/task-broker.service.js'); Container.get(TaskBroker).stopDraining(); await taskRunnerModule.start(); }); diff --git a/packages/cli/test/integration/wait-error-propagation.test.ts b/packages/cli/test/integration/wait-error-propagation.test.ts index a227ce4b7bc..517e0417355 100644 --- a/packages/cli/test/integration/wait-error-propagation.test.ts +++ b/packages/cli/test/integration/wait-error-propagation.test.ts @@ -161,7 +161,7 @@ describe('sub-workflow error propagation to parent after Wait resume', () => { // changes the module evaluation order and trips a circular import — WorkflowRunner // is still undefined when DI constructs WorkflowExecutionService, and // `executeManually` then fails with "Cannot read properties of undefined (reading 'run')". - const { WaitTracker } = await import('@/wait-tracker'); + const { WaitTracker } = await import('@/wait-tracker.js'); await Container.get(WaitTracker).startExecution(subExecution.id); // The sub-workflow errors diff --git a/packages/cli/test/integration/workflows/workflows.controller.test.ts b/packages/cli/test/integration/workflows/workflows.controller.test.ts index efeefa601cd..2c3d98035ab 100644 --- a/packages/cli/test/integration/workflows/workflows.controller.test.ts +++ b/packages/cli/test/integration/workflows/workflows.controller.test.ts @@ -5192,7 +5192,7 @@ describe('GET /workflows/:workflowId/executions/last-successful', () => { test('should return the last successful execution', async () => { const workflow = await createWorkflow({}, owner); - const { createSuccessfulExecution } = await import('../shared/db/executions'); + const { createSuccessfulExecution } = await import('../shared/db/executions.js'); // Create multiple executions with different statuses await createSuccessfulExecution(workflow); diff --git a/packages/cli/tsconfig.build.json b/packages/cli/tsconfig.build.json index a504e006ac3..bde25d585d3 100644 --- a/packages/cli/tsconfig.build.json +++ b/packages/cli/tsconfig.build.json @@ -1,5 +1,5 @@ { - "extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"], + "extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.go.json"], "compilerOptions": { "rootDir": "src", "outDir": "dist" diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index f7339ef6ffe..38430dceebb 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -1,7 +1,7 @@ { "extends": [ - "@n8n/typescript-config/tsconfig.common.json", - "@n8n/typescript-config/tsconfig.backend.json" + "@n8n/typescript-config/tsconfig.common.go.json", + "@n8n/typescript-config/tsconfig.backend.go.json" ], "compilerOptions": { "emitDecoratorMetadata": true, @@ -25,6 +25,8 @@ { "path": "../nodes-base/tsconfig.build.cjs.json" }, { "path": "../workflow/tsconfig.build.esm.json" }, { "path": "../@n8n/ai-utilities/tsconfig.build.cjs.json" }, + { "path": "../@n8n/ai-workflow-builder.ee/tsconfig.build.json" }, + { "path": "../@n8n/instance-ai/tsconfig.build.json" }, { "path": "../@n8n/api-types/tsconfig.build.json" }, { "path": "../@n8n/client-oauth2/tsconfig.build.json" }, { "path": "../@n8n/config/tsconfig.build.json" }, diff --git a/packages/cli/vitest.tsc-entity-transform.ts b/packages/cli/vitest.tsc-entity-transform.ts index 8d2a2922a2c..359ec482dcf 100644 --- a/packages/cli/vitest.tsc-entity-transform.ts +++ b/packages/cli/vitest.tsc-entity-transform.ts @@ -1,6 +1,8 @@ import fs from 'node:fs'; import path from 'node:path'; -import ts from 'typescript'; +// tsgo (typescript 7) ships no programmatic API, so this LanguageService-based +// transform imports the legacy JS compiler via the `typescript6` alias. +import ts from 'typescript6'; import type { Plugin } from 'vite'; /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 69ac7b67565..1e3f42e718e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2033,6 +2033,9 @@ importers: typescript: specifier: 'catalog:' version: 6.0.2 + typescript6: + specifier: npm:@typescript/typescript6@6.0.2 + version: '@typescript/typescript6@6.0.2' vite: specifier: 'catalog:' version: 8.0.2(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) @@ -3980,7 +3983,7 @@ importers: version: 5.1.2 '@qdrant/js-client-rest': specifier: 'catalog:' - version: 1.16.2(typescript@6.0.2) + version: 1.16.2(typescript@7.0.2) '@rudderstack/rudder-sdk-node': specifier: 'catalog:' version: 3.0.5 @@ -4425,13 +4428,16 @@ importers: version: 7.1.1 ts-essentials: specifier: ^7.0.3 - version: 7.0.3(typescript@6.0.2) + version: 7.0.3(typescript@7.0.2) tsconfig-paths: specifier: ^4.2.0 version: 4.2.0 typescript: - specifier: 'catalog:' - version: 6.0.2 + specifier: catalog:typescript + version: 7.0.2 + typescript6: + specifier: npm:@typescript/typescript6@6.0.2 + version: '@typescript/typescript6@6.0.2' vite: specifier: 'catalog:' version: 8.0.2(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) @@ -4440,7 +4446,7 @@ importers: version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' - version: 3.1.0(typescript@6.0.2)(vitest@4.1.9) + version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) packages/core: dependencies: @@ -12713,6 +12719,10 @@ packages: cpu: [x64] os: [win32] + '@typescript/typescript6@6.0.2': + resolution: {integrity: sha512-mbCddXd+jm7hfx7w2YU64/Av4/NqqeG3GoRZgxPcgoTxYjhrcfJRw9ULch71SS4G+Q3bOXFhRvPqjguN0Hyp5w==} + hasBin: true + '@typescript/vfs@1.6.0': resolution: {integrity: sha512-hvJUjNVeBMp77qPINuUvYXj4FyWeeMMKZkxEATEU3hqBAQ7qdTBCUFT7Sp0Zu0faeEtFf+ldXxMEDr/bk73ISg==} peerDependencies: @@ -29939,6 +29949,10 @@ snapshots: '@typescript/typescript-win32-x64@7.0.2': optional: true + '@typescript/typescript6@6.0.2': + dependencies: + '@typescript/old': typescript@6.0.2 + '@typescript/vfs@1.6.0(typescript@6.0.2)': dependencies: debug: 4.4.3(supports-color@8.1.1) @@ -40036,9 +40050,9 @@ snapshots: optionalDependencies: typescript: 7.0.2 - ts-essentials@7.0.3(typescript@6.0.2): + ts-essentials@7.0.3(typescript@7.0.2): dependencies: - typescript: 6.0.2 + typescript: 7.0.2 ts-graphviz@2.1.6: dependencies: diff --git a/scripts/typescript-migration/README.md b/scripts/typescript-migration/README.md index 88b0f571502..4be053ee1df 100644 --- a/scripts/typescript-migration/README.md +++ b/scripts/typescript-migration/README.md @@ -57,9 +57,13 @@ node scripts/typescript-migration/add-import-extensions.mjs packages/workflow -- - Uses `ts-morph` (already a catalog dep) and the package's `tsconfig.json` to find source files. -- **Skips** bare/alias specifiers (`@/…`, `@utils/…`, package names) — alias - tails get their extension from `tsc-alias`'s `resolveFullPaths` at build time. -- **Skips** specifiers that already carry a known extension. +- **Rewrites alias specifiers** that match a `paths` mapping in the tsconfig + (`@/foo` → `@/foo.js`, `@/widgets` → `@/widgets/index.js`), resolving the + extension against the mapped target dir. Typecheck (`tsc --noEmit`) doesn't run + `tsc-alias`, so aliases need the extension too; `resolveFullPaths` still handles + the already-suffixed alias in emit. Packages with no `paths` skip this step. +- **Skips** bare specifiers (package names — they never match a `@/*`-style + `paths` prefix) and specifiers that already carry a known extension. - Reports any relative specifier it can't resolve on disk for manual review; it does not guess. @@ -70,8 +74,10 @@ node scripts/typescript-migration/add-import-extensions.mjs packages/workflow -- `@n8n/typescript-config/modern`, or set `module: NodeNext` + `moduleResolution: NodeNext` locally. 3. **Codemod:** run `add-import-extensions.mjs ` (review), then `--write`. + This adds extensions to both relative and `paths`-alias specifiers in source. 4. **Aliases:** if the package keeps path aliases, enable `tsc-alias` - `resolveFullPaths` so alias tails also get extensions in emit. + `resolveFullPaths` so the alias prefix is rewritten to a relative path in + emit (the codemod already added the extension in source). 5. **Verify:** `pnpm --filter run build && pnpm --filter run typecheck && pnpm --filter test`. 6. **After:** `benchmark.mjs --label=after` → confirm the delta table. diff --git a/scripts/typescript-migration/SUMMARY.md b/scripts/typescript-migration/SUMMARY.md index 41325badbd4..9de03ccc0a8 100644 --- a/scripts/typescript-migration/SUMMARY.md +++ b/scripts/typescript-migration/SUMMARY.md @@ -1,6 +1,10 @@ # TypeScript 6 → 7 migration benchmarks +<<<<<<< typescript-cli +Generated 2026-07-14T13:18:27.416Z from `scripts/typescript-migration/results/`. +======= Generated 2026-07-15T09:07:07.705Z from `scripts/typescript-migration/results/`. +>>>>>>> master | Package | typecheck Δ | build Δ | | --- | --- | --- | @@ -25,6 +29,7 @@ Generated 2026-07-15T09:07:07.705Z from `scripts/typescript-migration/results/`. | `@n8n/instance-ai` | -70.0% | -40.3% | | `@n8n/n8n-benchmark` | -49.2% | -39.0% | | `@n8n/typeorm` | -37.9% | -68.7% | +| `n8n` | -75.5% | -71.3% | ``` === @n8n/agents — median times (Δ vs "before") === @@ -274,3 +279,15 @@ build: after 890ms -1949ms (-68.7%) ``` +``` +=== n8n — median times (Δ vs "before") === + +typecheck: + before 20.87s + after 5.12s -15750ms (-75.5%) + +build: + before 12.86s + after 3.69s -9172ms (-71.3%) +``` + diff --git a/scripts/typescript-migration/add-import-extensions.mjs b/scripts/typescript-migration/add-import-extensions.mjs index 92f07bad819..1d64a198dcf 100644 --- a/scripts/typescript-migration/add-import-extensions.mjs +++ b/scripts/typescript-migration/add-import-extensions.mjs @@ -11,9 +11,15 @@ // import cfg from './data' -> './data.json' (when data.json exists) // import x from './widgets' -> './widgets/index.js' // -// It SKIPS bare/alias specifiers (`@/…`, `@utils/…`, package names) — those are -// handled by tsc-alias's `resolveFullPaths` at build time — and anything that -// already has a known extension. +// It also rewrites alias specifiers that match a `paths` mapping in the package +// tsconfig (`@/foo` -> `@/foo.js`, `@/widgets` -> `@/widgets/index.js`), resolving +// the extension against the mapped target directory. `tsc --noEmit` (typecheck) +// does NOT run tsc-alias, so under NodeNext these aliases fail to resolve without +// an extension; tsc-alias's `resolveFullPaths` handles the already-suffixed alias +// at build time, so this is safe for both typecheck and build. +// +// It SKIPS bare specifiers (package names — they never match a `@/*`-style +// `paths` prefix) and anything that already has a known extension. // // Dry-run by default; pass --write to apply. // @@ -97,6 +103,48 @@ function resolveExtension(fromFile, spec) { return null; } +// Build matchers from the package tsconfig `paths` (e.g. `@/*` -> `./src/*`). +// `paths` are relative to the config dir when no `baseUrl` is set. Only simple +// single-`*` wildcard patterns are supported (that covers n8n's aliases). +function buildAliasMatchers(compilerOptions, configDir) { + const paths = compilerOptions.paths ?? {}; + const baseDir = compilerOptions.baseUrl ? resolve(configDir, compilerOptions.baseUrl) : configDir; + const matchers = []; + for (const [pattern, targets] of Object.entries(paths)) { + const starIdx = pattern.indexOf('*'); + if (starIdx === -1) continue; // exact (non-wildcard) aliases are rare; skip + const prefix = pattern.slice(0, starIdx); + const suffix = pattern.slice(starIdx + 1); + const targetTemplates = targets + .filter((t) => t.includes('*')) + .map((t) => resolve(baseDir, t)); + if (targetTemplates.length) matchers.push({ prefix, suffix, targetTemplates }); + } + return matchers; +} + +// Resolve an alias specifier to the extension it should carry (matching the +// first `paths` target that exists on disk), or null if unmatched/unresolvable. +function resolveAliasExtension(spec, matchers) { + for (const { prefix, suffix, targetTemplates } of matchers) { + if (!spec.startsWith(prefix) || !spec.endsWith(suffix)) continue; + if (spec.length < prefix.length + suffix.length) continue; + const captured = spec.slice(prefix.length, spec.length - suffix.length); + for (const template of targetTemplates) { + const base = template.replace('*', captured); + for (const ext of ['.ts', '.tsx', '.mts', '.cts']) { + if (existsSync(base + ext)) return '.js'; + } + if (existsSync(base + '.json')) return '.json'; + if (existsSync(base + '.js')) return '.js'; + for (const idx of ['index.ts', 'index.tsx', 'index.mts', 'index.cts', 'index.js']) { + if (existsSync(join(base, idx))) return '/index.js'; + } + } + } + return null; +} + async function main() { const opts = parseArgs(process.argv.slice(2)); const tsConfigFilePath = findTsConfig(opts.package); @@ -111,6 +159,10 @@ async function main() { // files — those aren't ours to touch. const pkgDir = dirname(tsConfigFilePath); const project = new Project({ tsConfigFilePath }); + const aliasMatchers = buildAliasMatchers(project.getCompilerOptions(), pkgDir); + if (aliasMatchers.length) { + console.log(` aliases: ${aliasMatchers.map((m) => `${m.prefix}*${m.suffix}`).join(', ')}\n`); + } const sourceFiles = project.getSourceFiles().filter((sf) => { const p = sf.getFilePath(); return ( @@ -146,8 +198,16 @@ async function main() { } for (const { spec, set } of targets) { - if (!isRelative(spec) || hasKnownExtension(spec)) continue; - const ext = resolveExtension(filePath, spec); + if (hasKnownExtension(spec)) continue; + let ext; + if (isRelative(spec)) { + ext = resolveExtension(filePath, spec); + } else if (aliasMatchers.length) { + ext = resolveAliasExtension(spec, aliasMatchers); + if (!ext) continue; // bare package spec (or unmapped) — leave it alone + } else { + continue; + } if (!ext) { unresolved.push(`${relFile}: '${spec}'`); continue; diff --git a/scripts/typescript-migration/results/n8n.json b/scripts/typescript-migration/results/n8n.json new file mode 100644 index 00000000000..fca6d1e3e45 --- /dev/null +++ b/scripts/typescript-migration/results/n8n.json @@ -0,0 +1,61 @@ +{ + "package": "n8n", + "runs": { + "before": { + "label": "before", + "tsVersion": "6.0.2", + "node": "v24.13.0", + "timestamp": "2026-07-14T13:14:19.235Z", + "tasks": { + "typecheck": { + "runsMs": [ + 20872, + 22190, + 20170 + ], + "min": 20170, + "median": 20872, + "mean": 21077 + }, + "build": { + "runsMs": [ + 12860, + 12735, + 14095 + ], + "min": 12735, + "median": 12860, + "mean": 13230 + } + } + }, + "after": { + "label": "after", + "tsVersion": "7.0.2", + "node": "v24.13.0", + "timestamp": "2026-07-14T13:18:18.589Z", + "tasks": { + "typecheck": { + "runsMs": [ + 5722, + 5122, + 4568 + ], + "min": 4568, + "median": 5122, + "mean": 5137 + }, + "build": { + "runsMs": [ + 3688, + 3607, + 4781 + ], + "min": 3607, + "median": 3688, + "mean": 4025 + } + } + } + } +}