diff --git a/.github/workflows/test-unit-reusable.yml b/.github/workflows/test-unit-reusable.yml index 8adff5314d3..b8a54955a9a 100644 --- a/.github/workflows/test-unit-reusable.yml +++ b/.github/workflows/test-unit-reusable.yml @@ -66,7 +66,7 @@ jobs: # @n8n/vitest-config/node), peak forks ≈ 2 × 50% ≈ the core count, instead # of ~10 concurrent suites each spawning a core-sized pool and pegging CPU. - name: Test Unit (backend, except cli and nodes-base) - run: pnpm turbo test:unit --continue --concurrency=2 --filter='!./packages/frontend/**' --filter='!n8n' --filter='!n8n-nodes-base' --summarize + run: pnpm turbo test:unit --continue --concurrency=2 --filter='!./packages/frontend/**' --filter='!./packages/modules/**' --filter='!n8n' --filter='!n8n-nodes-base' --summarize - name: Test Unit (cli, scoped) if: ${{ !cancelled() }} @@ -126,7 +126,7 @@ jobs: # run serially to avoid cross-package interference. Unit jobs above # don't need this because they isolate per-process. - name: Test Integration (backend, except cli) - run: pnpm turbo test:integration --continue --concurrency=1 --filter='!./packages/frontend/**' --filter='!n8n' --summarize + run: pnpm turbo test:integration --continue --concurrency=1 --filter='!./packages/frontend/**' --filter='!./packages/modules/**' --filter='!n8n' --summarize - name: Test Integration (cli, scoped) if: ${{ !cancelled() }} diff --git a/biome.jsonc b/biome.jsonc index 42fa685a33b..caa0178e2a5 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -42,6 +42,14 @@ ] }, "organizeImports": { "enabled": false }, + // Every `tsconfig*.json` is JSONC. Biome only infers that from the exact name `tsconfig.json`, + // so variants like `tsconfig.frontend-module.json` need it spelled out. + "overrides": [ + { + "include": ["**/tsconfig*.json"], + "json": { "parser": { "allowComments": true } } + } + ], "linter": { "enabled": false }, diff --git a/package.json b/package.json index 55d5a89333a..bbf34f86218 100644 --- a/package.json +++ b/package.json @@ -58,12 +58,12 @@ "mutate": "node scripts/mutation-health/mutate.mjs", "test": "turbo run test", "test:ci": "turbo run test --continue --concurrency=1", - "test:ci:frontend": "turbo run test --continue --filter='./packages/frontend/**'", - "test:ci:frontend:changed": "turbo run test:changed --continue --filter='./packages/frontend/**'", - "test:ci:backend": "turbo run test --continue --concurrency=1 --filter='!./packages/frontend/**'", - "test:ci:backend:unit": "turbo run test:unit --continue --filter='!./packages/frontend/**'", - "test:ci:backend:integration": "turbo run test:integration --continue --concurrency=1 --filter='!./packages/frontend/**'", - "test:ci:backend:integration:changed": "turbo run test:integration:changed --continue --concurrency=1 --filter='!./packages/frontend/**'", + "test:ci:frontend": "turbo run test --continue --filter='./packages/frontend/**' --filter='./packages/modules/**'", + "test:ci:frontend:changed": "turbo run test:changed --continue --filter='./packages/frontend/**' --filter='./packages/modules/**'", + "test:ci:backend": "turbo run test --continue --concurrency=1 --filter='!./packages/frontend/**' --filter='!./packages/modules/**'", + "test:ci:backend:unit": "turbo run test:unit --continue --filter='!./packages/frontend/**' --filter='!./packages/modules/**'", + "test:ci:backend:integration": "turbo run test:integration --continue --concurrency=1 --filter='!./packages/frontend/**' --filter='!./packages/modules/**'", + "test:ci:backend:integration:changed": "turbo run test:integration:changed --continue --concurrency=1 --filter='!./packages/frontend/**' --filter='!./packages/modules/**'", "test:affected": "turbo run test --affected --concurrency=1", "db:schema:docs": "turbo run build --filter=@n8n/db && pnpm --filter=@n8n/db schema:docs", "db:schema:check": "turbo run build --filter=@n8n/db && pnpm --filter=@n8n/db schema:check", diff --git a/packages/@n8n/typescript-config/package.json b/packages/@n8n/typescript-config/package.json index 76a51584e1b..a514338e1ad 100644 --- a/packages/@n8n/typescript-config/package.json +++ b/packages/@n8n/typescript-config/package.json @@ -6,13 +6,15 @@ "tsconfig.backend.json", "tsconfig.build.json", "tsconfig.common.json", - "tsconfig.frontend.json" + "tsconfig.frontend.json", + "tsconfig.frontend-module.json" ], "exports": { "./tsconfig.backend.json": "./tsconfig.backend.json", "./tsconfig.build.json": "./tsconfig.build.json", "./tsconfig.common.json": "./tsconfig.common.json", "./tsconfig.frontend.json": "./tsconfig.frontend.json", + "./tsconfig.frontend-module.json": "./tsconfig.frontend-module.json", "./*": "./*" }, "license": "LicenseRef-n8n-sustainable-use" diff --git a/packages/@n8n/typescript-config/tsconfig.frontend-module.json b/packages/@n8n/typescript-config/tsconfig.frontend-module.json new file mode 100644 index 00000000000..d8781402b61 --- /dev/null +++ b/packages/@n8n/typescript-config/tsconfig.frontend-module.json @@ -0,0 +1,53 @@ +// Base tsconfig for frontend module packages (`packages/modules/*/frontend`). +// +// Hand-maintained, and it has to agree with editor-ui's Vite aliases and its own tsconfig `paths` +// — a package pointed at a different src here would typecheck modules against something the +// editor never bundles. `editor-ui/vite/aliases.test.ts` fails when they disagree. +// +// `paths` lists the L0-L2 packages a module consumes from source. Module packages are absent +// from it on purpose, which stops an *accidental* cross-module import — but it is not a +// boundary: `paths` is additive, so once a module declares another module as a dependency, +// pnpm symlinks it and the import typechecks clean. Boundary enforcement is the ESLint rule. +// +// Modules declare their own `include`, `rootDirs` and `types`, and cannot inherit them: relative +// entries in those three resolve against the *consuming* config, so inheriting them from here +// would point them at this directory. `paths` is the exception — it anchors to the file that +// declares it, which is what lets one base serve modules at any depth. +// +// `useUnknownInCatchVariables` is a cost of consuming L1/L2 packages from source rather than from +// a built `dist`; see the comment on it below. The ambient `.d.ts` shims are the other half of +// that cost, and live in each module's own tsconfig for the resolution reason above. +{ + "extends": "./tsconfig.frontend.json", + "compilerOptions": { + "moduleResolution": "bundler", + "noEmit": true, + "useUnknownInCatchVariables": false, + "paths": { + "@n8n/api-types": ["../api-types/src/index.ts"], + "@n8n/api-types/*": ["../api-types/src/*"], + "@n8n/chat": ["../../frontend/@n8n/chat/src/index.ts"], + "@n8n/chat/*": ["../../frontend/@n8n/chat/src/*"], + "@n8n/chat-hub": ["../chat-hub/src/index.ts"], + "@n8n/chat-hub/*": ["../chat-hub/src/*"], + "@n8n/composables/*": ["../../frontend/@n8n/composables/src/*"], + "@n8n/constants": ["../constants/src/index.ts"], + "@n8n/constants/*": ["../constants/src/*"], + "@n8n/design-system": ["../../frontend/@n8n/design-system/src/index.ts"], + "@n8n/design-system/*": ["../../frontend/@n8n/design-system/src/*"], + "@n8n/frontend-constants/*": ["../../frontend/@n8n/frontend-constants/src/*"], + "@n8n/frontend-module-sdk": ["../../frontend/@n8n/frontend-module-sdk/src/index.ts"], + "@n8n/frontend-module-sdk/*": ["../../frontend/@n8n/frontend-module-sdk/src/*"], + "@n8n/frontend-utils/*": ["../../frontend/@n8n/frontend-utils/src/*"], + "@n8n/i18n": ["../../frontend/@n8n/i18n/src/index.ts"], + "@n8n/i18n/*": ["../../frontend/@n8n/i18n/src/*"], + "@n8n/rest-api-client": ["../../frontend/@n8n/rest-api-client/src/index.ts"], + "@n8n/rest-api-client/*": ["../../frontend/@n8n/rest-api-client/src/*"], + "@n8n/stores": ["../../frontend/@n8n/stores/src/index.ts"], + "@n8n/stores/*": ["../../frontend/@n8n/stores/src/*"], + "@n8n/telemetry": ["../telemetry/src/index.ts"], + "@n8n/telemetry/*": ["../telemetry/src/*"], + "@n8n/utils/*": ["../utils/src/*"] + } + } +} diff --git a/packages/modules/README.md b/packages/modules/README.md new file mode 100644 index 00000000000..ef2d2bd7649 --- /dev/null +++ b/packages/modules/README.md @@ -0,0 +1,13 @@ +# Modules + +Feature modules carved out of `packages/frontend/editor-ui`. Each one is a workspace package at +`/frontend`: source-only (`main: "src/index.ts"`, no `dist`), consumed by the editor-ui shell +through Vite aliases. + +`/backend` is a reserved path rather than a workspace package — the backend runtime discovers +modules under `packages/cli/src/modules/`. The extra nesting level is what lets both halves of +a module sit together later. + +The directory is empty until the first module lands, and tracked in the meantime because turbo +rejects a `--filter` whose directory does not exist. The root `test:ci:*` scripts and the two +backend jobs in `.github/workflows/test-unit-reusable.yml` all name `packages/modules/**`. diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 10b0e36704b..84baedad24c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,6 +2,9 @@ packages: - packages/* - packages/@n8n/* - packages/frontend/** + # `packages/*` stops one level short of `/frontend`, so feature modules need their own + # glob or they are not workspace packages at all. + - packages/modules/** - packages/extensions/** - packages/testing/**