From f7f51fbcd396e1f650a1955a95b6f061384148fb Mon Sep 17 00:00:00 2001 From: Theodore Nastos <15264814+theonastos@users.noreply.github.com> Date: Tue, 15 Sep 2026 11:17:15 +0000 Subject: [PATCH] feat(editor): Add the type-availability-policies frontend module (no-changelog) (#38634) --- OWNERS | 5 ++ .../@n8n/frontend-vite-config/index.ts | 4 ++ packages/frontend/editor-ui/package.json | 1 + .../editor-ui/src/app/modules.manifest.ts | 2 + packages/frontend/editor-ui/tsconfig.json | 6 ++ .../frontend/README.md | 21 +++++++ .../frontend/biome.jsonc | 4 ++ .../frontend/eslint.config.mjs | 4 ++ .../frontend/package.json | 41 ++++++++++++++ .../frontend/src/__tests__/setup.ts | 4 ++ .../frontend/src/index.ts | 1 + .../type-availability-policies.constants.ts | 2 + .../type-availability-policies.module.test.ts | 9 +++ .../src/type-availability-policies.module.ts | 11 ++++ .../frontend/tsconfig.json | 21 +++++++ .../frontend/vite.config.ts | 23 ++++++++ pnpm-lock.yaml | 55 +++++++++++++++++++ 17 files changed, 214 insertions(+) create mode 100644 packages/modules/type-availability-policies/frontend/README.md create mode 100644 packages/modules/type-availability-policies/frontend/biome.jsonc create mode 100644 packages/modules/type-availability-policies/frontend/eslint.config.mjs create mode 100644 packages/modules/type-availability-policies/frontend/package.json create mode 100644 packages/modules/type-availability-policies/frontend/src/__tests__/setup.ts create mode 100644 packages/modules/type-availability-policies/frontend/src/index.ts create mode 100644 packages/modules/type-availability-policies/frontend/src/type-availability-policies.constants.ts create mode 100644 packages/modules/type-availability-policies/frontend/src/type-availability-policies.module.test.ts create mode 100644 packages/modules/type-availability-policies/frontend/src/type-availability-policies.module.ts create mode 100644 packages/modules/type-availability-policies/frontend/tsconfig.json create mode 100644 packages/modules/type-availability-policies/frontend/vite.config.ts diff --git a/OWNERS b/OWNERS index 1a14f3a5c4c..ec297a875f1 100644 --- a/OWNERS +++ b/OWNERS @@ -235,6 +235,11 @@ packages/@n8n/client-oauth2/ @n8n-io/iam packages/extensions/insights/ @n8n-io/ligo +# Governance & Observability + +packages/modules/type-availability-policies/ @n8n-io/governance-and-observability +packages/cli/src/modules/type-availability-policies/ @n8n-io/governance-and-observability + # CI/CD .github/ @n8n-io/qa-dx diff --git a/packages/frontend/@n8n/frontend-vite-config/index.ts b/packages/frontend/@n8n/frontend-vite-config/index.ts index 9f0a2d0a987..f8eec649407 100644 --- a/packages/frontend/@n8n/frontend-vite-config/index.ts +++ b/packages/frontend/@n8n/frontend-vite-config/index.ts @@ -44,6 +44,10 @@ export const sourcePackages = [ * The module tsconfig base holds that boundary. */ export const modulePackages: Array<{ name: string; dir: string; entry?: boolean }> = [ + { + name: '@n8n/frontend-module-type-availability-policies', + dir: 'modules/type-availability-policies/frontend', + }, { name: '@n8n/frontend-module-insights', dir: 'modules/insights/frontend' }, { name: '@n8n/frontend-module-instance-registry', dir: 'modules/instance-registry/frontend' }, { name: '@n8n/frontend-module-otel', dir: 'modules/otel/frontend' }, diff --git a/packages/frontend/editor-ui/package.json b/packages/frontend/editor-ui/package.json index 5f95fdda68c..bac3bf420d1 100644 --- a/packages/frontend/editor-ui/package.json +++ b/packages/frontend/editor-ui/package.json @@ -57,6 +57,7 @@ "@n8n/frontend-module-instance-registry": "workspace:*", "@n8n/frontend-module-otel": "workspace:*", "@n8n/frontend-module-sdk": "workspace:*", + "@n8n/frontend-module-type-availability-policies": "workspace:*", "@n8n/frontend-test-utils": "workspace:*", "@n8n/frontend-utils": "workspace:*", "@n8n/frontend-vite-config": "workspace:*", diff --git a/packages/frontend/editor-ui/src/app/modules.manifest.ts b/packages/frontend/editor-ui/src/app/modules.manifest.ts index 38b89097db9..cd1634f413f 100644 --- a/packages/frontend/editor-ui/src/app/modules.manifest.ts +++ b/packages/frontend/editor-ui/src/app/modules.manifest.ts @@ -10,6 +10,7 @@ import { OtelModule } from '@n8n/frontend-module-otel'; import { InsightsModule } from '@n8n/frontend-module-insights/insights.module'; import { PromotionsModule } from '@/features/integrations/promotions.ee/module.descriptor'; import { ContextModule } from '@/features/settings/context/module.descriptor'; +import { TypeAvailabilityPoliciesModule } from '@n8n/frontend-module-type-availability-policies'; /** * The static list is the design, not a placeholder (design §9). n8n self-hosted @@ -39,4 +40,5 @@ export const modules: FrontendModuleDescription[] = [ InsightsModule, PromotionsModule, ContextModule, + TypeAvailabilityPoliciesModule, ]; diff --git a/packages/frontend/editor-ui/tsconfig.json b/packages/frontend/editor-ui/tsconfig.json index f5bb0c81cf2..7743b647a16 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-type-availability-policies": [ + "../../modules/type-availability-policies/frontend/src/index.ts" + ], + "@n8n/frontend-module-type-availability-policies/*": [ + "../../modules/type-availability-policies/frontend/src/*" + ], "@n8n/frontend-module-insights": ["../../modules/insights/frontend/src/index.ts"], "@n8n/frontend-module-insights/*": ["../../modules/insights/frontend/src/*"], "@n8n/frontend-module-instance-registry": [ diff --git a/packages/modules/type-availability-policies/frontend/README.md b/packages/modules/type-availability-policies/frontend/README.md new file mode 100644 index 00000000000..357c0b94eea --- /dev/null +++ b/packages/modules/type-availability-policies/frontend/README.md @@ -0,0 +1,21 @@ +# @n8n/frontend-module-type-availability-policies + +Frontend module for node type availability policies. The editor-ui shell consumes it from +source through `src/app/modules.manifest.ts`, so there is no build step and no `dist`. + +The descriptor `id` must match the backend module id +(`packages/cli/src/modules/type-availability-policies`). Both `settings.activeModules` and +`/rest/module-settings` are keyed by that string, and a mismatch fails silently. + +The module is license-gated on `feat:nodeTypePolicies` and is not a default module. Enable it +in a dev instance with `N8N_ENABLED_MODULES=type-availability-policies`. + +```bash +pnpm turbo typecheck lint test --filter=@n8n/frontend-module-type-availability-policies +``` + +Use turbo and not `pnpm --filter`: this package reads its platform dependencies from source, +and on a cold tree turbo builds them first. + +`packages/@n8n/module-cli/frontend-module-guide.md` holds the descriptor contract, the +registration points, the import boundaries and the setup a module needs to render UI. diff --git a/packages/modules/type-availability-policies/frontend/biome.jsonc b/packages/modules/type-availability-policies/frontend/biome.jsonc new file mode 100644 index 00000000000..f882da95a58 --- /dev/null +++ b/packages/modules/type-availability-policies/frontend/biome.jsonc @@ -0,0 +1,4 @@ +{ + "$schema": "../../../../node_modules/@biomejs/biome/configuration_schema.json", + "extends": ["../../../../biome.jsonc"] +} diff --git a/packages/modules/type-availability-policies/frontend/eslint.config.mjs b/packages/modules/type-availability-policies/frontend/eslint.config.mjs new file mode 100644 index 00000000000..3709bc37c42 --- /dev/null +++ b/packages/modules/type-availability-policies/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/type-availability-policies/frontend/package.json b/packages/modules/type-availability-policies/frontend/package.json new file mode 100644 index 00000000000..92a24f342ac --- /dev/null +++ b/packages/modules/type-availability-policies/frontend/package.json @@ -0,0 +1,41 @@ +{ + "name": "@n8n/frontend-module-type-availability-policies", + "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/frontend-module-sdk": "workspace:*" + }, + "devDependencies": { + "@n8n/eslint-config": "workspace:*", + "@n8n/frontend-test-utils": "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/type-availability-policies/frontend/src/__tests__/setup.ts b/packages/modules/type-availability-policies/frontend/src/__tests__/setup.ts new file mode 100644 index 00000000000..e648829d4ea --- /dev/null +++ b/packages/modules/type-availability-policies/frontend/src/__tests__/setup.ts @@ -0,0 +1,4 @@ +// The shared frontend test harness: the jsdom environment, english messages, and a fresh pinia +// per test. It lives in `@n8n/frontend-test-utils` rather than in `@n8n/vitest-config`, because +// `@n8n/i18n` devDepends on the latter and importing i18n there closes a cycle in the turbo graph. +import '@n8n/frontend-test-utils/setup'; diff --git a/packages/modules/type-availability-policies/frontend/src/index.ts b/packages/modules/type-availability-policies/frontend/src/index.ts new file mode 100644 index 00000000000..4c0507aedce --- /dev/null +++ b/packages/modules/type-availability-policies/frontend/src/index.ts @@ -0,0 +1 @@ +export { TypeAvailabilityPoliciesModule } from './type-availability-policies.module'; diff --git a/packages/modules/type-availability-policies/frontend/src/type-availability-policies.constants.ts b/packages/modules/type-availability-policies/frontend/src/type-availability-policies.constants.ts new file mode 100644 index 00000000000..d319f5cd5d4 --- /dev/null +++ b/packages/modules/type-availability-policies/frontend/src/type-availability-policies.constants.ts @@ -0,0 +1,2 @@ +/** Must stay the same as the backend module id: `settings.activeModules` is keyed by it. */ +export const TYPE_AVAILABILITY_POLICIES_MODULE_ID = 'type-availability-policies'; diff --git a/packages/modules/type-availability-policies/frontend/src/type-availability-policies.module.test.ts b/packages/modules/type-availability-policies/frontend/src/type-availability-policies.module.test.ts new file mode 100644 index 00000000000..ae1bc085dfd --- /dev/null +++ b/packages/modules/type-availability-policies/frontend/src/type-availability-policies.module.test.ts @@ -0,0 +1,9 @@ +import { TypeAvailabilityPoliciesModule } from './type-availability-policies.module'; + +describe('TypeAvailabilityPoliciesModule', () => { + it('keeps the id the backend module publishes in `activeModules`', () => { + // A mismatch is silent: `isModuleActive` compares strings, so the module would + // never report active. The twin is `packages/cli/src/modules/type-availability-policies`. + expect(TypeAvailabilityPoliciesModule.id).toBe('type-availability-policies'); + }); +}); diff --git a/packages/modules/type-availability-policies/frontend/src/type-availability-policies.module.ts b/packages/modules/type-availability-policies/frontend/src/type-availability-policies.module.ts new file mode 100644 index 00000000000..242b607161c --- /dev/null +++ b/packages/modules/type-availability-policies/frontend/src/type-availability-policies.module.ts @@ -0,0 +1,11 @@ +import { defineFrontendModule } from '@n8n/frontend-module-sdk'; + +import { TYPE_AVAILABILITY_POLICIES_MODULE_ID } from './type-availability-policies.constants'; + +/** Keep this file import-light: the shell runs every descriptor body before Pinia starts. */ +export const TypeAvailabilityPoliciesModule = defineFrontendModule({ + id: TYPE_AVAILABILITY_POLICIES_MODULE_ID, + name: 'Type Availability Policies', + description: 'Reports which node types a project can use, and why a type is unavailable', + icon: 'shield', +}); diff --git a/packages/modules/type-availability-policies/frontend/tsconfig.json b/packages/modules/type-availability-policies/frontend/tsconfig.json new file mode 100644 index 00000000000..878dd5f1145 --- /dev/null +++ b/packages/modules/type-availability-policies/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/type-availability-policies/frontend/vite.config.ts b/packages/modules/type-availability-policies/frontend/vite.config.ts new file mode 100644 index 00000000000..5838abd5643 --- /dev/null +++ b/packages/modules/type-availability-policies/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 6230b786eb8..6c051e83500 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6047,6 +6047,9 @@ importers: '@n8n/frontend-module-sdk': specifier: workspace:* version: link:../@n8n/frontend-module-sdk + '@n8n/frontend-module-type-availability-policies': + specifier: workspace:* + version: link:../../modules/type-availability-policies/frontend '@n8n/frontend-test-utils': specifier: workspace:* version: link:../@n8n/frontend-test-utils @@ -6726,6 +6729,58 @@ importers: specifier: ^2.2.8 version: 2.2.8(patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)(typescript@6.0.2) + packages/modules/type-availability-policies/frontend: + dependencies: + '@n8n/frontend-module-sdk': + specifier: workspace:* + version: link:../../../frontend/@n8n/frontend-module-sdk + devDependencies: + '@n8n/eslint-config': + specifier: workspace:* + version: link:../../../@n8n/eslint-config + '@n8n/frontend-test-utils': + specifier: workspace:* + version: link:../../../frontend/@n8n/frontend-test-utils + '@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.9.1 + '@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: 6.0.8(vite@8.0.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.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))(vue@3.5.26(typescript@6.0.2)) + eslint: + specifier: 'catalog:' + version: 9.29.0(jiti@2.6.1)(supports-color@8.1.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.2)(@emnapi/runtime@1.11.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: + 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)(supports-color@8.1.1)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.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)) + vue-tsc: + specifier: ^2.2.8 + version: 2.2.8(patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)(typescript@6.0.2) + packages/node-dev: dependencies: '@n8n/di':