mirror of
https://github.com/n8n-io/n8n.git
synced 2026-08-28 17:22:01 +08:00
refactor(editor): Extract instance-registry into a frontend module package (no-changelog) (#36325)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:*",
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
|
||||
@@ -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*"],
|
||||
|
||||
@@ -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 <pkg> 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.
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "../../../../node_modules/@biomejs/biome/configuration_schema.json",
|
||||
"extends": ["../../../../biome.jsonc"]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { defineConfig } from 'eslint/config';
|
||||
import { frontendConfig } from '@n8n/eslint-config/frontend';
|
||||
|
||||
export default defineConfig(frontendConfig);
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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());
|
||||
});
|
||||
@@ -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';
|
||||
@@ -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',
|
||||
};
|
||||
+4
-3
@@ -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(),
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
Generated
+85
@@ -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':
|
||||
|
||||
Reference in New Issue
Block a user