From 59d43005969edfffffded72d96012c3e36b46e2c Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Tue, 18 Aug 2026 12:00:24 +0000 Subject: [PATCH] refactor(editor): Extract instance-registry into a frontend module package (no-changelog) (#36325) Co-authored-by: Claude Opus 5 Co-authored-by: multica-agent --- .../@n8n/frontend-vite-config/index.ts | 4 +- packages/frontend/editor-ui/package.json | 1 + .../src/app/components/AboutModal.vue | 2 +- .../src/app/composables/useDebugInfo.test.ts | 2 +- .../src/app/composables/useDebugInfo.ts | 2 +- .../editor-ui/src/app/modules.manifest.ts | 2 + packages/frontend/editor-ui/tsconfig.json | 6 ++ .../instance-registry/frontend/README.md | 27 ++++++ .../instance-registry/frontend/biome.jsonc | 4 + .../frontend/eslint.config.mjs | 4 + .../instance-registry/frontend/package.json | 51 +++++++++++ .../frontend/src/__tests__/setup.ts | 12 +++ .../instance-registry/frontend/src/index.ts | 5 ++ .../frontend/src/instance-registry.module.ts | 15 ++++ .../src/instance-registry.store.test.ts} | 7 +- .../frontend/src/instance-registry.store.ts} | 0 .../instance-registry/frontend/tsconfig.json | 21 +++++ .../instance-registry/frontend/vite.config.ts | 23 +++++ pnpm-lock.yaml | 85 +++++++++++++++++++ 19 files changed, 266 insertions(+), 7 deletions(-) create mode 100644 packages/modules/instance-registry/frontend/README.md create mode 100644 packages/modules/instance-registry/frontend/biome.jsonc create mode 100644 packages/modules/instance-registry/frontend/eslint.config.mjs create mode 100644 packages/modules/instance-registry/frontend/package.json create mode 100644 packages/modules/instance-registry/frontend/src/__tests__/setup.ts create mode 100644 packages/modules/instance-registry/frontend/src/index.ts create mode 100644 packages/modules/instance-registry/frontend/src/instance-registry.module.ts rename packages/{frontend/editor-ui/src/features/instanceRegistry/stores/__tests__/instanceRegistry.store.test.ts => modules/instance-registry/frontend/src/instance-registry.store.test.ts} (96%) rename packages/{frontend/editor-ui/src/features/instanceRegistry/stores/instanceRegistry.store.ts => modules/instance-registry/frontend/src/instance-registry.store.ts} (100%) create mode 100644 packages/modules/instance-registry/frontend/tsconfig.json create mode 100644 packages/modules/instance-registry/frontend/vite.config.ts diff --git a/packages/frontend/@n8n/frontend-vite-config/index.ts b/packages/frontend/@n8n/frontend-vite-config/index.ts index ed745df623b..7a666670007 100644 --- a/packages/frontend/@n8n/frontend-vite-config/index.ts +++ b/packages/frontend/@n8n/frontend-vite-config/index.ts @@ -42,7 +42,9 @@ export const sourcePackages = [ * A module that aliases the other modules lets a cross-module import resolve in its own test run. * The module tsconfig base holds that boundary. */ -export const modulePackages: Array<{ name: string; dir: string; entry?: boolean }> = []; +export const modulePackages: Array<{ name: string; dir: string; entry?: boolean }> = [ + { name: '@n8n/frontend-module-instance-registry', dir: 'modules/instance-registry/frontend' }, +]; // The code below makes the Vite aliases from the two tables. Keep this code in the same file as // the tables. A second file needs an import with a `.ts` specifier. That import causes error diff --git a/packages/frontend/editor-ui/package.json b/packages/frontend/editor-ui/package.json index 6df772bc96d..f654988f842 100644 --- a/packages/frontend/editor-ui/package.json +++ b/packages/frontend/editor-ui/package.json @@ -49,6 +49,7 @@ "@n8n/constants": "workspace:*", "@n8n/design-system": "workspace:*", "@n8n/frontend-constants": "workspace:*", + "@n8n/frontend-module-instance-registry": "workspace:*", "@n8n/frontend-module-sdk": "workspace:*", "@n8n/frontend-utils": "workspace:*", "@n8n/i18n": "workspace:*", diff --git a/packages/frontend/editor-ui/src/app/components/AboutModal.vue b/packages/frontend/editor-ui/src/app/components/AboutModal.vue index aac935e16b1..316f500beb5 100644 --- a/packages/frontend/editor-ui/src/app/components/AboutModal.vue +++ b/packages/frontend/editor-ui/src/app/components/AboutModal.vue @@ -7,7 +7,7 @@ import { useRootStore } from '@n8n/stores/useRootStore'; import { useToast } from '@n8n/composables/useToast'; import { useClipboard } from '@n8n/composables/useClipboard'; import { useDebugInfo } from '@/app/composables/useDebugInfo'; -import { useInstanceRegistryStore } from '@/features/instanceRegistry/stores/instanceRegistry.store'; +import { useInstanceRegistryStore } from '@n8n/frontend-module-instance-registry'; import { useI18n } from '@n8n/i18n'; import { getThirdPartyLicenses } from '@n8n/rest-api-client'; diff --git a/packages/frontend/editor-ui/src/app/composables/useDebugInfo.test.ts b/packages/frontend/editor-ui/src/app/composables/useDebugInfo.test.ts index b2dae823cd7..3ede8928500 100644 --- a/packages/frontend/editor-ui/src/app/composables/useDebugInfo.test.ts +++ b/packages/frontend/editor-ui/src/app/composables/useDebugInfo.test.ts @@ -62,7 +62,7 @@ const { mockClusterInfo } = vi.hoisted(() => ({ mockClusterInfo: { value: null as ClusterInfoResponse | null }, })); -vi.mock('@/features/instanceRegistry/stores/instanceRegistry.store', () => ({ +vi.mock('@n8n/frontend-module-instance-registry', () => ({ useInstanceRegistryStore: () => ({ get clusterInfo() { return mockClusterInfo.value; diff --git a/packages/frontend/editor-ui/src/app/composables/useDebugInfo.ts b/packages/frontend/editor-ui/src/app/composables/useDebugInfo.ts index 273c3e58984..96105947b4a 100644 --- a/packages/frontend/editor-ui/src/app/composables/useDebugInfo.ts +++ b/packages/frontend/editor-ui/src/app/composables/useDebugInfo.ts @@ -1,6 +1,6 @@ import { useRootStore } from '@n8n/stores/useRootStore'; import { useSettingsStore } from '@n8n/stores/settings.store'; -import { useInstanceRegistryStore } from '@/features/instanceRegistry/stores/instanceRegistry.store'; +import { useInstanceRegistryStore } from '@n8n/frontend-module-instance-registry'; import { useDeviceSupport } from '@n8n/composables/useDeviceSupport'; import type { WorkflowSettings } from 'n8n-workflow'; diff --git a/packages/frontend/editor-ui/src/app/modules.manifest.ts b/packages/frontend/editor-ui/src/app/modules.manifest.ts index 107fece9c3d..23833d25c4e 100644 --- a/packages/frontend/editor-ui/src/app/modules.manifest.ts +++ b/packages/frontend/editor-ui/src/app/modules.manifest.ts @@ -7,6 +7,7 @@ import { InstanceAiModule } from '@/features/ai/instanceAi/module.descriptor'; import { AgentsModule } from '@/features/agents/module.descriptor'; import { OtelModule } from '@/features/settings/otel/module.descriptor'; import { WorkflowReviewsModule } from '@/features/workflow-reviews/module.descriptor'; +import { InstanceRegistryModule } from '@n8n/frontend-module-instance-registry'; /** * Hard-coding modules list until we have a dynamic way to load modules. @@ -20,4 +21,5 @@ export const modules: FrontendModuleDescription[] = [ AgentsModule, OtelModule, WorkflowReviewsModule, + InstanceRegistryModule, ]; diff --git a/packages/frontend/editor-ui/tsconfig.json b/packages/frontend/editor-ui/tsconfig.json index 43fe01ccc39..f26492f3b11 100644 --- a/packages/frontend/editor-ui/tsconfig.json +++ b/packages/frontend/editor-ui/tsconfig.json @@ -25,6 +25,12 @@ "@n8n/composables*": ["../@n8n/composables/src*"], "@n8n/constants*": ["../../@n8n/constants/src*"], "@n8n/frontend-module-sdk": ["../@n8n/frontend-module-sdk/src/index.ts"], + "@n8n/frontend-module-instance-registry": [ + "../../modules/instance-registry/frontend/src/index.ts" + ], + "@n8n/frontend-module-instance-registry/*": [ + "../../modules/instance-registry/frontend/src/*" + ], "@n8n/frontend-utils*": ["../@n8n/frontend-utils/src*"], "@n8n/frontend-constants*": ["../@n8n/frontend-constants/src*"], "@n8n/chat*": ["../@n8n/chat/src*"], diff --git a/packages/modules/instance-registry/frontend/README.md b/packages/modules/instance-registry/frontend/README.md new file mode 100644 index 00000000000..ce38d7154b8 --- /dev/null +++ b/packages/modules/instance-registry/frontend/README.md @@ -0,0 +1,27 @@ +# @n8n/frontend-module-instance-registry + +Frontend feature module. Consumed from source by the editor-ui shell through +`src/app/modules.manifest.ts`; there is no build step and no `dist`. + +```bash +pnpm turbo typecheck --filter=@n8n/frontend-module-instance-registry +pnpm turbo lint --filter=@n8n/frontend-module-instance-registry +pnpm turbo test --filter=@n8n/frontend-module-instance-registry +``` + +Go through turbo, not `pnpm --filter typecheck`: this package is consumed +from source, and on a cold tree its platform dependencies have not been built +yet. Turbo builds them first; the bare pnpm form does not. + +## Import rules + +- Depend on foundation and platform packages only (`@n8n/design-system`, + `@n8n/stores`, `@n8n/composables`, `@n8n/i18n`, `@n8n/rest-api-client`, + `@n8n/api-types`, `@n8n/frontend-module-sdk`). Never import another + `@n8n/frontend-module-*`, and never import `@/…` from the shell. +- `@n8n/stores` and `@n8n/composables` are **subpath-only** — import + `@n8n/stores/settings.store`, not `@n8n/stores`. +- The no-cross-module rule is currently a convention: the shared tsconfig base + omits sibling modules from `paths`, which blocks an accidental import but not + a deliberate one (declaring the dependency makes it typecheck clean). The + ESLint rule that actually enforces it is CAT-3692. diff --git a/packages/modules/instance-registry/frontend/biome.jsonc b/packages/modules/instance-registry/frontend/biome.jsonc new file mode 100644 index 00000000000..f882da95a58 --- /dev/null +++ b/packages/modules/instance-registry/frontend/biome.jsonc @@ -0,0 +1,4 @@ +{ + "$schema": "../../../../node_modules/@biomejs/biome/configuration_schema.json", + "extends": ["../../../../biome.jsonc"] +} diff --git a/packages/modules/instance-registry/frontend/eslint.config.mjs b/packages/modules/instance-registry/frontend/eslint.config.mjs new file mode 100644 index 00000000000..3709bc37c42 --- /dev/null +++ b/packages/modules/instance-registry/frontend/eslint.config.mjs @@ -0,0 +1,4 @@ +import { defineConfig } from 'eslint/config'; +import { frontendConfig } from '@n8n/eslint-config/frontend'; + +export default defineConfig(frontendConfig); diff --git a/packages/modules/instance-registry/frontend/package.json b/packages/modules/instance-registry/frontend/package.json new file mode 100644 index 00000000000..69fb39465ea --- /dev/null +++ b/packages/modules/instance-registry/frontend/package.json @@ -0,0 +1,51 @@ +{ + "name": "@n8n/frontend-module-instance-registry", + "version": "0.1.0", + "type": "module", + "main": "src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "clean": "rimraf .turbo", + "typecheck": "vue-tsc --noEmit", + "test": "vitest run", + "test:changed": "janitor test-scoped", + "test:dev": "vitest", + "lint": "eslint src --quiet", + "lint:fix": "eslint src --fix", + "format": "biome format --write . && prettier --write . --ignore-path ../../../../.prettierignore", + "format:check": "biome ci . && prettier --check . --ignore-path ../../../../.prettierignore" + }, + "dependencies": { + "@n8n/api-types": "workspace:*", + "@n8n/composables": "workspace:*", + "@n8n/design-system": "workspace:*", + "@n8n/frontend-module-sdk": "workspace:*", + "@n8n/i18n": "workspace:*", + "@n8n/permissions": "workspace:*", + "@n8n/rest-api-client": "workspace:*", + "@n8n/stores": "workspace:*", + "n8n-workflow": "workspace:*", + "pinia": "catalog:frontend", + "vue": "catalog:frontend", + "vue-router": "catalog:frontend" + }, + "devDependencies": { + "@n8n/eslint-config": "workspace:*", + "@n8n/frontend-vite-config": "workspace:*", + "@n8n/playwright-janitor": "workspace:*", + "@n8n/typescript-config": "workspace:*", + "@n8n/vitest-config": "workspace:*", + "@testing-library/jest-dom": "catalog:frontend", + "@testing-library/vue": "catalog:frontend", + "@vitejs/plugin-vue": "catalog:frontend", + "eslint": "catalog:", + "typescript": "catalog:", + "unplugin-icons": "catalog:frontend", + "vite": "catalog:", + "vitest": "catalog:", + "vue-tsc": "catalog:frontend" + }, + "license": "LicenseRef-n8n-sustainable-use" +} diff --git a/packages/modules/instance-registry/frontend/src/__tests__/setup.ts b/packages/modules/instance-registry/frontend/src/__tests__/setup.ts new file mode 100644 index 00000000000..ee2a5e6341a --- /dev/null +++ b/packages/modules/instance-registry/frontend/src/__tests__/setup.ts @@ -0,0 +1,12 @@ +// The shared jsdom harness — observers, matchMedia, canvas, timers, teardown guards. +import '@n8n/vitest-config/setup/frontend'; + +import { createPinia, setActivePinia } from 'pinia'; +import { beforeEach } from 'vitest'; + +// Framework boot stays per-package on purpose: `@n8n/i18n` devDepends on +// `@n8n/vitest-config`, so booting i18n from inside the shared harness would +// close a turbo build cycle. Add `useI18n` boot here if this module needs it. +beforeEach(() => { + setActivePinia(createPinia()); +}); diff --git a/packages/modules/instance-registry/frontend/src/index.ts b/packages/modules/instance-registry/frontend/src/index.ts new file mode 100644 index 00000000000..f8c4fdb21e2 --- /dev/null +++ b/packages/modules/instance-registry/frontend/src/index.ts @@ -0,0 +1,5 @@ +// The module's only public entry. The shell imports the descriptor from here via +// `modules.manifest.ts`; anything else the shell (or a test) needs must be exported +// here too — deep paths into `src/` are not part of the contract. +export { InstanceRegistryModule } from './instance-registry.module'; +export { useInstanceRegistryStore } from './instance-registry.store'; diff --git a/packages/modules/instance-registry/frontend/src/instance-registry.module.ts b/packages/modules/instance-registry/frontend/src/instance-registry.module.ts new file mode 100644 index 00000000000..3696ae9c76b --- /dev/null +++ b/packages/modules/instance-registry/frontend/src/instance-registry.module.ts @@ -0,0 +1,15 @@ +import type { FrontendModuleDescription } from '@n8n/frontend-module-sdk'; + +/** + * Store-only: this module contributes no UI surface, so registering it is a no-op — + * `moduleInitializer` guards every surface it reads. `AboutModal` and `useDebugInfo` + * consume the store directly; the descriptor is what makes this a module the shell + * knows about rather than a library it happens to import. + */ +export const InstanceRegistryModule: FrontendModuleDescription = { + // Must match the backend module id: both gate off `/rest/module-settings`. + id: 'instance-registry', + name: 'Instance Registry', + description: 'Reports which instances are in this deployment and their health', + icon: 'server', +}; diff --git a/packages/frontend/editor-ui/src/features/instanceRegistry/stores/__tests__/instanceRegistry.store.test.ts b/packages/modules/instance-registry/frontend/src/instance-registry.store.test.ts similarity index 96% rename from packages/frontend/editor-ui/src/features/instanceRegistry/stores/__tests__/instanceRegistry.store.test.ts rename to packages/modules/instance-registry/frontend/src/instance-registry.store.test.ts index 357a5ac28a6..bc4cf9823fe 100644 --- a/packages/frontend/editor-ui/src/features/instanceRegistry/stores/__tests__/instanceRegistry.store.test.ts +++ b/packages/modules/instance-registry/frontend/src/instance-registry.store.test.ts @@ -1,7 +1,8 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { setActivePinia, createPinia } from 'pinia'; import type { ClusterInfoResponse } from '@n8n/api-types'; -import { useInstanceRegistryStore } from '../instanceRegistry.store'; +import { setActivePinia, createPinia } from 'pinia'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +import { useInstanceRegistryStore } from './instance-registry.store'; const mocks = vi.hoisted(() => ({ getClusterInfo: vi.fn(), diff --git a/packages/frontend/editor-ui/src/features/instanceRegistry/stores/instanceRegistry.store.ts b/packages/modules/instance-registry/frontend/src/instance-registry.store.ts similarity index 100% rename from packages/frontend/editor-ui/src/features/instanceRegistry/stores/instanceRegistry.store.ts rename to packages/modules/instance-registry/frontend/src/instance-registry.store.ts diff --git a/packages/modules/instance-registry/frontend/tsconfig.json b/packages/modules/instance-registry/frontend/tsconfig.json new file mode 100644 index 00000000000..878dd5f1145 --- /dev/null +++ b/packages/modules/instance-registry/frontend/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@n8n/typescript-config/tsconfig.frontend-module.json", + "compilerOptions": { + // `rootDirs`, `types` and `include` cannot be inherited from the base: relative entries in + // them resolve against this file, so a copy in the base would point at the base's directory. + // (`paths` is the exception and does come from the base.) + "rootDirs": [".", "../../../frontend/@n8n/design-system/src"], + // The two `.d.ts` entries are ambient declarations this package never imports, so nothing + // pulls them into the program: `~icons/*` and `markdown-it-task-lists` for design-system's + // source, `window.BASE_PATH` for `@n8n/stores`'s. Consuming those packages from source is + // what makes them the consumer's problem — a built `dist` would have carried them. + "types": [ + "vite/client", + "vitest/globals", + "unplugin-icons/types/vue", + "../../../frontend/@n8n/design-system/src/shims-modules.d.ts", + "../../../frontend/@n8n/stores/src/shims.d.ts" + ] + }, + "include": ["src/**/*.ts", "src/**/*.vue", "vite.config.ts"] +} diff --git a/packages/modules/instance-registry/frontend/vite.config.ts b/packages/modules/instance-registry/frontend/vite.config.ts new file mode 100644 index 00000000000..5838abd5643 --- /dev/null +++ b/packages/modules/instance-registry/frontend/vite.config.ts @@ -0,0 +1,23 @@ +import vue from '@vitejs/plugin-vue'; +import { fileURLToPath } from 'node:url'; +import { resolve } from 'node:path'; +import { defineConfig, mergeConfig } from 'vite'; +import { frontendAliases } from '@n8n/frontend-vite-config'; +import { vitestConfig } from '@n8n/vitest-config/frontend'; + +const packageDir = fileURLToPath(new URL('.', import.meta.url)); +const packagesDir = resolve(packageDir, '..', '..', '..'); + +export default mergeConfig( + defineConfig({ + plugins: [vue()], + resolve: { + // The same platform mapping the editor-ui dev server uses, so a test resolves + // `@n8n/stores/...` from source rather than from a stale `dist` — the two disagreeing is + // what put 1,111 specifiers on the wrong side of the src/dist line. Sibling modules are + // deliberately absent: nothing here should make a cross-module import resolve. + alias: frontendAliases(packagesDir), + }, + }), + vitestConfig, +); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 15546c0019c..487d8decfee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5904,6 +5904,9 @@ importers: '@n8n/frontend-constants': specifier: workspace:* version: link:../@n8n/frontend-constants + '@n8n/frontend-module-instance-registry': + specifier: workspace:* + version: link:../../modules/instance-registry/frontend '@n8n/frontend-module-sdk': specifier: workspace:* version: link:../@n8n/frontend-module-sdk @@ -6296,6 +6299,88 @@ importers: specifier: ^2.2.8 version: 2.2.8(patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)(typescript@6.0.2) + packages/modules/instance-registry/frontend: + dependencies: + '@n8n/api-types': + specifier: workspace:* + version: link:../../../@n8n/api-types + '@n8n/composables': + specifier: workspace:* + version: link:../../../frontend/@n8n/composables + '@n8n/design-system': + specifier: workspace:* + version: link:../../../frontend/@n8n/design-system + '@n8n/frontend-module-sdk': + specifier: workspace:* + version: link:../../../frontend/@n8n/frontend-module-sdk + '@n8n/i18n': + specifier: workspace:* + version: link:../../../frontend/@n8n/i18n + '@n8n/permissions': + specifier: workspace:* + version: link:../../../@n8n/permissions + '@n8n/rest-api-client': + specifier: workspace:* + version: link:../../../frontend/@n8n/rest-api-client + '@n8n/stores': + specifier: workspace:* + version: link:../../../frontend/@n8n/stores + n8n-workflow: + specifier: workspace:* + version: link:../../../workflow + pinia: + specifier: catalog:frontend + version: 2.2.4(typescript@6.0.2)(vue@3.5.26(typescript@6.0.2)) + vue: + specifier: catalog:frontend + version: 3.5.26(typescript@6.0.2) + vue-router: + specifier: catalog:frontend + version: 4.5.0(vue@3.5.26(typescript@6.0.2)) + devDependencies: + '@n8n/eslint-config': + specifier: workspace:* + version: link:../../../@n8n/eslint-config + '@n8n/frontend-vite-config': + specifier: workspace:* + version: link:../../../frontend/@n8n/frontend-vite-config + '@n8n/playwright-janitor': + specifier: workspace:* + version: link:../../../testing/janitor + '@n8n/typescript-config': + specifier: workspace:* + version: link:../../../@n8n/typescript-config + '@n8n/vitest-config': + specifier: workspace:* + version: link:../../../@n8n/vitest-config + '@testing-library/jest-dom': + specifier: catalog:frontend + version: 6.6.3 + '@testing-library/vue': + specifier: catalog:frontend + version: 8.1.0(@vue/compiler-sfc@3.5.26)(vue@3.5.26(typescript@6.0.2)) + '@vitejs/plugin-vue': + specifier: catalog:frontend + version: 5.2.4(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@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))(vue@3.5.26(typescript@6.0.2)) + eslint: + specifier: 'catalog:' + version: 9.29.0(jiti@2.6.1) + typescript: + specifier: 'catalog:' + version: 6.0.2 + unplugin-icons: + specifier: catalog:frontend + version: 23.0.1(@vue/compiler-sfc@3.5.26) + vite: + specifier: 'catalog:' + version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@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: + specifier: 'catalog:' + version: 4.1.9(@opentelemetry/api@1.9.1)(@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(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@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)) + vue-tsc: + specifier: ^2.2.8 + version: 2.2.8(patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)(typescript@6.0.2) + packages/node-dev: dependencies: '@n8n/di':