@starci/hfs 4.0.10 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -1
- package/emit/operations.mjs +1 -1
- package/emit/type-schema.mjs +2 -1
- package/lint/run.mjs +3 -2
- package/package.json +1 -1
- package/report/order.mjs +2 -0
- package/runtime/config.example.yaml +187 -0
- package/runtime/engine/by-code-unit.mjs +12 -0
- package/runtime/engine/config.mjs +221 -239
- package/runtime/engine/invalid-config.mjs +5 -5
- package/runtime/engine/model-config.mjs +90 -0
- package/runtime/engine/orca-config.mjs +3 -1
- package/runtime/engine/removed-vocabulary.mjs +28 -0
- package/runtime/engine/resources-config.mjs +52 -0
- package/runtime/engine/runtime-root.mjs +17 -0
- package/runtime/engine/secrets.mjs +35 -31
- package/runtime/engine/sonar-config.mjs +21 -0
- package/runtime/engine/temp-root.mjs +32 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +14 -14
- package/runtime/knowledge/hfs/peer-integrations.yaml +2 -3
- package/runtime/knowledge/hfs/rules.yaml +98 -65
- package/runtime/knowledge/hfs/slots.yaml +11 -14
- package/runtime/knowledge/patterns/be/api.yaml +7 -24
- package/runtime/knowledge/patterns/be/cli.yaml +3 -12
- package/runtime/knowledge/patterns/be/realtime.yaml +1 -14
- package/runtime/knowledge/patterns/be/webhooks.yaml +0 -19
- package/runtime/modules/kernel/failure-codes.yaml +1 -41
- package/runtime/modules/kernel/removed-vocabulary.yaml +168 -0
- package/runtime/modules/models/registry.yaml +25 -359
- package/runtime/modules/models/runtimes.yaml +92 -256
- package/runtime/modules/models/tiers.yaml +65 -0
- package/runtime/scripts/api/fs/claim-file.mjs +30 -0
- package/runtime/scripts/api/fs/ensure-temp-root.mjs +27 -0
- package/runtime/scripts/api/fs/forbidden-root.mjs +2 -1
- package/runtime/scripts/api/fs/make-temp-dir.mjs +11 -0
- package/runtime/scripts/api/fs/safe-remove.mjs +52 -35
- package/runtime/scripts/api/git/lib.mjs +34 -1
- package/runtime/scripts/api/process/resolve-real-tool.mjs +14 -8
- package/runtime/scripts/api/process/run-program.mjs +2 -1
- package/runtime/scripts/api/sops/lib.mjs +198 -120
- package/runtime/scripts/hfs/allows.mjs +3 -3
- package/runtime/scripts/hfs/architecture/ast-walks.mjs +63 -50
- package/runtime/scripts/hfs/architecture/automatic-gates.mjs +96 -0
- package/runtime/scripts/hfs/architecture/backend.mjs +261 -175
- package/runtime/scripts/hfs/architecture/background-unowned.mjs +69 -34
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +55 -54
- package/runtime/scripts/hfs/architecture/clones.mjs +179 -106
- package/runtime/scripts/hfs/architecture/config-unread.mjs +5 -22
- package/runtime/scripts/hfs/architecture/config.mjs +95 -80
- package/runtime/scripts/hfs/architecture/connection-map.mjs +233 -141
- package/runtime/scripts/hfs/architecture/constructor-deps.mjs +15 -9
- package/runtime/scripts/hfs/architecture/context-coupling.mjs +76 -52
- package/runtime/scripts/hfs/architecture/context-map.mjs +215 -141
- package/runtime/scripts/hfs/architecture/context-owner.mjs +39 -30
- package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +31 -17
- package/runtime/scripts/hfs/architecture/context-transaction.mjs +38 -25
- package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +86 -40
- package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +342 -0
- package/runtime/scripts/hfs/architecture/contracts.mjs +263 -455
- package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +43 -34
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +201 -154
- package/runtime/scripts/hfs/architecture/default-deny.mjs +118 -93
- package/runtime/scripts/hfs/architecture/doc-language.mjs +2 -1
- package/runtime/scripts/hfs/architecture/entrypoint.mjs +45 -30
- package/runtime/scripts/hfs/architecture/error-codes.mjs +19 -12
- package/runtime/scripts/hfs/architecture/error-masked.mjs +39 -26
- package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +2 -1
- package/runtime/scripts/hfs/architecture/feature-shape.mjs +26 -20
- package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
- package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +190 -0
- package/runtime/scripts/hfs/architecture/frontend-routing.mjs +186 -0
- package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +311 -0
- package/runtime/scripts/hfs/architecture/frontend-world.mjs +328 -0
- package/runtime/scripts/hfs/architecture/frontend.mjs +109 -829
- package/runtime/scripts/hfs/architecture/hfs-graph.mjs +14 -2
- package/runtime/scripts/hfs/architecture/hfs.mjs +120 -288
- package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +46 -26
- package/runtime/scripts/hfs/architecture/i18n-keys.mjs +114 -76
- package/runtime/scripts/hfs/architecture/index.mjs +158 -102
- package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +33 -28
- package/runtime/scripts/hfs/architecture/machine-ast.mjs +180 -144
- package/runtime/scripts/hfs/architecture/managed-scripts.mjs +8 -2
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +150 -101
- package/runtime/scripts/hfs/architecture/next-data-contract.mjs +51 -43
- package/runtime/scripts/hfs/architecture/next-data-key.mjs +338 -0
- package/runtime/scripts/hfs/architecture/next-data.mjs +179 -427
- package/runtime/scripts/hfs/architecture/owners.mjs +27 -13
- package/runtime/scripts/hfs/architecture/package-shape.mjs +51 -23
- package/runtime/scripts/hfs/architecture/presentation.mjs +165 -0
- package/runtime/scripts/hfs/architecture/reachability.mjs +127 -61
- package/runtime/scripts/hfs/architecture/register-once.mjs +112 -63
- package/runtime/scripts/hfs/architecture/registration.mjs +187 -110
- package/runtime/scripts/hfs/architecture/required-files.mjs +100 -68
- package/runtime/scripts/hfs/architecture/route-files-thin.mjs +83 -58
- package/runtime/scripts/hfs/architecture/schema-owner.mjs +182 -108
- package/runtime/scripts/hfs/architecture/source-names-shape.mjs +318 -0
- package/runtime/scripts/hfs/architecture/source-names.mjs +83 -248
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +175 -118
- package/runtime/scripts/hfs/architecture/sql-returning.mjs +39 -28
- package/runtime/scripts/hfs/architecture/sql-tokens.mjs +302 -172
- package/runtime/scripts/hfs/architecture/supabase-ast.mjs +1 -1
- package/runtime/scripts/hfs/architecture/supabase-be.mjs +55 -45
- package/runtime/scripts/hfs/architecture/supabase-results.mjs +182 -0
- package/runtime/scripts/hfs/architecture/supabase-tables.mjs +20 -12
- package/runtime/scripts/hfs/architecture/supabase.mjs +191 -263
- package/runtime/scripts/hfs/architecture/symbols.mjs +175 -99
- package/runtime/scripts/hfs/architecture/test-world-files.mjs +82 -41
- package/runtime/scripts/hfs/architecture/tiers.mjs +120 -69
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +84 -57
- package/runtime/scripts/hfs/architecture/type-context.mjs +282 -0
- package/runtime/scripts/hfs/architecture/typescript.mjs +116 -276
- package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +84 -62
- package/runtime/scripts/hfs/architecture.mjs +3 -4
- package/runtime/scripts/hfs/check.mjs +61 -70
- package/runtime/scripts/hfs/coverage-scope.mjs +84 -38
- package/runtime/scripts/hfs/declaration-shape.mjs +77 -36
- package/runtime/scripts/hfs/declaration-slots.mjs +1 -1
- package/runtime/scripts/hfs/edition-slots.mjs +5 -3
- package/runtime/scripts/hfs/linear-text.mjs +30 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +164 -63
- package/runtime/scripts/hfs/path-findings.mjs +58 -38
- package/runtime/scripts/hfs/pin-findings.mjs +31 -0
- package/runtime/scripts/hfs/repo-identity.mjs +5 -2
- package/runtime/scripts/hfs/rule-catalog.mjs +194 -0
- package/runtime/scripts/hfs/rule-params-shape.mjs +33 -27
- package/runtime/scripts/hfs/rules/cli.mjs +5 -1
- package/runtime/scripts/hfs/rules/contract-compat.mjs +40 -22
- package/runtime/scripts/hfs/rules/contract.mjs +35 -27
- package/runtime/scripts/hfs/rules/database-config.mjs +57 -32
- package/runtime/scripts/hfs/rules/database-migrations.mjs +52 -31
- package/runtime/scripts/hfs/rules/database-plpgsql.mjs +106 -91
- package/runtime/scripts/hfs/rules/database-sql.mjs +254 -173
- package/runtime/scripts/hfs/rules/database.mjs +36 -23
- package/runtime/scripts/hfs/rules/deps.mjs +64 -32
- package/runtime/scripts/hfs/rules/docker.mjs +113 -65
- package/runtime/scripts/hfs/rules/edition.mjs +118 -111
- package/runtime/scripts/hfs/rules/event-bus.mjs +91 -45
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +17 -16
- package/runtime/scripts/hfs/rules/fe-no-tests.mjs +36 -16
- package/runtime/scripts/hfs/rules/frontend-tree.mjs +4 -3
- package/runtime/scripts/hfs/rules/integration-specs.mjs +75 -41
- package/runtime/scripts/hfs/rules/kinds.mjs +59 -25
- package/runtime/scripts/hfs/rules/lint-suppression.mjs +9 -3
- package/runtime/scripts/hfs/rules/monorepo.mjs +82 -45
- package/runtime/scripts/hfs/rules/peer-integrations.mjs +24 -3
- package/runtime/scripts/hfs/rules/pipeline.mjs +58 -9
- package/runtime/scripts/hfs/rules/proof-commands.mjs +14 -8
- package/runtime/scripts/hfs/rules/repo-local-checks.mjs +11 -6
- package/runtime/scripts/hfs/rules/saga.mjs +128 -61
- package/runtime/scripts/hfs/rules/secrets.mjs +4 -3
- package/runtime/scripts/hfs/rules/services.mjs +34 -16
- package/runtime/scripts/hfs/rules/stacks.mjs +21 -14
- package/runtime/scripts/hfs/rules/supabase-secrets.mjs +32 -23
- package/runtime/scripts/hfs/rules/test-topology.mjs +25 -11
- package/runtime/scripts/hfs/secret.mjs +59 -48
- package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
- package/runtime/scripts/hfs/slot-classify.mjs +65 -0
- package/runtime/scripts/hfs/slot-errors.mjs +11 -0
- package/runtime/scripts/hfs/slot-imports.mjs +76 -0
- package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
- package/runtime/scripts/hfs/slot-match.mjs +153 -0
- package/runtime/scripts/hfs/slot-path.mjs +8 -0
- package/runtime/scripts/hfs/slot-required.mjs +76 -0
- package/runtime/scripts/hfs/slot-semantic-problems.mjs +148 -0
- package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
- package/runtime/scripts/hfs/slots.mjs +72 -693
- package/runtime/scripts/hfs/sql/pg-parse.mjs +5 -2
- package/runtime/scripts/hfs/trailing-slashes.mjs +8 -0
- package/runtime/scripts/hfs/tree.mjs +19 -14
- package/runtime/scripts/hfs/typescript-programs.mjs +6 -4
- package/runtime/scripts/hfs/view.mjs +1 -1
- package/runtime/scripts/lib/dockerfile.mjs +56 -33
- package/runtime/scripts/lib/event-contract.mjs +17 -13
- package/runtime/scripts/lib/git.mjs +2 -2
- package/runtime/scripts/lib/graphql-contract.mjs +158 -322
- package/runtime/scripts/lib/graphql-sdl.mjs +262 -0
- package/runtime/scripts/lib/i18n.mjs +3 -2
- package/runtime/scripts/lib/in-order.mjs +71 -0
- package/runtime/scripts/lib/language.mjs +3 -3
- package/runtime/scripts/lib/list.mjs +8 -1
- package/runtime/scripts/lib/path-key.mjs +38 -5
- package/runtime/scripts/lib/pid-alive.mjs +7 -0
- package/runtime/scripts/lib/regex.mjs +1 -1
- package/runtime/scripts/lib/same-text.mjs +1 -1
- package/runtime/scripts/lib/secret-patterns.mjs +6 -2
- package/runtime/scripts/lib/sleep-sync.mjs +2 -2
- package/runtime/scripts/lib/sops-envelope.mjs +95 -48
- package/runtime/scripts/lib/stack-services.mjs +1 -2
- package/runtime/scripts/lib/ts-ast.mjs +9 -0
- package/runtime/scripts/lib/walk.mjs +6 -1
- package/scaffold/add-cli-lite.mjs +2 -1
- package/scaffold/add-table.mjs +2 -2
- package/scaffold/add.mjs +3 -2
- package/scaffold/app.mjs +4 -2
- package/scaffold/edition-gate.mjs +25 -25
- package/scaffold/lite-exports.mjs +2 -1
- package/scaffold/service.mjs +5 -4
- package/sync/hygiene.mjs +15 -9
- package/sync/index.mjs +7 -3
- package/templates/app/ci-workflows/github/workflows/ci.yml +4 -4
- package/templates/app/ci-workflows/github/workflows/e2e.yml +3 -3
- package/templates/app/ci-workflows/github/workflows/images.yml +3 -3
- package/templates/app/ci-workflows/sonar-steps.yml +2 -2
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +6 -6
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +3 -3
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +3 -3
- package/templates/app/starciwork.gitignore +1 -1
- package/templates/be/image/api/Dockerfile +1 -1
- package/templates/be/image/cli/Dockerfile +1 -1
- package/templates/be/image/worker/Dockerfile +1 -1
- package/templates/be/patterns/cli/group.cli.spec.ts.tpl +1 -1
- package/templates/be/patterns/cli/group.cli.ts.tpl +2 -2
- package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +13 -0
- package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +7 -2
- package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +4 -1
- package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +2 -1
- package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +24 -9
- package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +3 -2
- package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +8 -5
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +1 -1
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +2 -2
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +1 -1
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +2 -2
- package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +5 -4
- package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +47 -0
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.ts +16 -0
- package/templates/be/skeleton-lite/apps/api/Dockerfile +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
- package/templates/fe/image/next/Dockerfile +2 -1
- package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +7 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +1 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +10 -2
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +10 -3
- package/upgrade/index.mjs +9 -2
- package/runtime/engine/admission.mjs +0 -308
- package/runtime/engine/canonical-json.mjs +0 -10
- package/runtime/engine/db/blob.mjs +0 -315
- package/runtime/engine/db/ledger-paths.mjs +0 -83
- package/runtime/engine/db/ledger.mjs +0 -1118
- package/runtime/engine/db/machine-connection.mjs +0 -135
- package/runtime/engine/db/machine-schema.mjs +0 -84
- package/runtime/engine/db/machine.mjs +0 -1367
- package/runtime/engine/db/migrations/machine/0001-init.sql +0 -924
- package/runtime/engine/db/migrations/runtime/0001-init.sql +0 -1108
- package/runtime/engine/db/provider-reservations.mjs +0 -101
- package/runtime/engine/digest.mjs +0 -16
- package/runtime/engine/refuse.mjs +0 -11
- package/runtime/modules/ops/_labels.yaml +0 -53
- package/runtime/scripts/api/node/lib.mjs +0 -14
- package/runtime/scripts/api/node/spawn-node.mjs +0 -6
- package/runtime/scripts/api/process/lib.mjs +0 -111
- package/runtime/scripts/api/process/owned-process.mjs +0 -78
- package/runtime/scripts/api/process/stop-owned-process.mjs +0 -9
- package/runtime/scripts/connectors/lib.mjs +0 -488
- package/runtime/scripts/lib/clip.mjs +0 -19
- package/runtime/scripts/lib/display-names.mjs +0 -259
- package/runtime/scripts/lib/example-refs.mjs +0 -158
- package/runtime/scripts/lib/json-schema.mjs +0 -52
- package/runtime/scripts/lib/process-identity.mjs +0 -6
- package/runtime/scripts/lib/read-yaml.mjs +0 -18
- package/runtime/scripts/lib/redact.mjs +0 -161
- package/runtime/scripts/lib/sleep.mjs +0 -7
- package/runtime/scripts/lib/source-phrases.mjs +0 -34
- package/runtime/scripts/lib/sqlite.mjs +0 -21
|
@@ -3,11 +3,21 @@ import { isInside } from './config.mjs';
|
|
|
3
3
|
import { slotAdmitsFile } from '../allows.mjs';
|
|
4
4
|
import { reachableViolation, relativePath } from './typescript.mjs';
|
|
5
5
|
import { sourceLocation } from '../../lib/ts-ast.mjs';
|
|
6
|
+
import { byCodeUnit } from '../../lib/list.mjs';
|
|
6
7
|
|
|
7
8
|
const FORBIDDEN_APP_ROLE = /(?:^|\.)(?:service|provider|providers|resolver|controller|handler|repository|entity|use-case|command|query|listener|consumer|processor)\.[cm]?[jt]sx?$/i;
|
|
8
9
|
const FORBIDDEN_DECLARATION = /(?:Service|Provider|Resolver|Controller|Handler|Repository|Entity|UseCase|Command|Query|Listener|Consumer|Processor)$/;
|
|
9
10
|
const FORBIDDEN_DECORATORS = new Set(['Controller', 'Resolver', 'Injectable', 'Processor', 'WebSocketGateway']);
|
|
10
|
-
const TRANSPORT_PACKAGES =
|
|
11
|
+
const TRANSPORT_PACKAGES = new RegExp(`^(?:${[
|
|
12
|
+
'@nestjs/(?:graphql|microservices|platform-[^/]+|websockets)(?:/|$)',
|
|
13
|
+
'@apollo/',
|
|
14
|
+
'apollo-server(?:/|$)',
|
|
15
|
+
'express(?:/|$)',
|
|
16
|
+
'fastify(?:/|$)',
|
|
17
|
+
'graphql(?:/|$)',
|
|
18
|
+
'class-validator(?:/|$)',
|
|
19
|
+
'class-transformer(?:/|$)',
|
|
20
|
+
].join('|')})`);
|
|
11
21
|
const NEST_COMMON_TRANSPORT = new Set(['Controller', 'Get', 'Post', 'Put', 'Patch', 'Delete', 'Options', 'Head', 'Body', 'Param', 'Query', 'Req', 'Request', 'Res', 'Response', 'Headers', 'Header', 'HttpCode', 'Redirect', 'Render', 'Sse', 'UploadedFile', 'UploadedFiles', 'UseGuards', 'UseInterceptors', 'UsePipes']);
|
|
12
22
|
|
|
13
23
|
function absolute(root, relative) {
|
|
@@ -72,111 +82,142 @@ function roleEvidence(ts, sourceFile) {
|
|
|
72
82
|
return found;
|
|
73
83
|
}
|
|
74
84
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
: ts.isStringLiteralLike(element.propertyName)
|
|
85
|
-
? element.propertyName.text
|
|
86
|
-
: null;
|
|
85
|
+
// The destructured binding elements naming a transport export (or a computed binding, which cannot be
|
|
86
|
+
// proved to exclude one): `const { Controller, [x]: y } = ...`. `rest` elements are always opaque.
|
|
87
|
+
function destructuredTransportBindings(ts, elements, detail) {
|
|
88
|
+
return elements.flatMap(element => {
|
|
89
|
+
let selected = null;
|
|
90
|
+
if (!element.dotDotDotToken) {
|
|
91
|
+
const imported = element.propertyName ?? element.name;
|
|
92
|
+
if (ts.isIdentifier(imported) || ts.isStringLiteralLike(imported)) selected = imported.text;
|
|
93
|
+
}
|
|
87
94
|
return selected === null || NEST_COMMON_TRANSPORT.has(selected) ? [{ node: element, detail: selected ?? detail }] : [];
|
|
88
95
|
});
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const namespaceMemberUsages = (ts, node, isAlias) => {
|
|
99
|
+
if (ts.isPropertyAccessExpression(node) && isAlias(node.expression)
|
|
100
|
+
&& NEST_COMMON_TRANSPORT.has(node.name.text)) return [{ node: node.name, detail: node.name.text }];
|
|
101
|
+
if (ts.isElementAccessExpression(node) && isAlias(node.expression)) {
|
|
102
|
+
const selected = ts.isStringLiteralLike(node.argumentExpression) ? node.argumentExpression.text : null;
|
|
103
|
+
if (selected === null || NEST_COMMON_TRANSPORT.has(selected)) return [{ node: node.argumentExpression, detail: selected ?? 'computed @nestjs/common namespace access' }];
|
|
104
|
+
}
|
|
105
|
+
return [];
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const namespaceEscapeUsages = (ts, node, isAlias) => {
|
|
109
|
+
if (!ts.isVariableDeclaration(node) || !isAlias(node.initializer)) return [];
|
|
110
|
+
if (!ts.isObjectBindingPattern(node.name)) return [{ node: node.name, detail: 'escaped @nestjs/common namespace access' }];
|
|
111
|
+
return destructuredTransportBindings(ts, node.name.elements, 'computed @nestjs/common namespace destructuring');
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
function namespaceTransportUsages(ts, sourceFile, checker, binding) {
|
|
115
|
+
const usages = [];
|
|
116
|
+
const alias = binding.text;
|
|
117
|
+
const bindingSymbol = checker?.getSymbolAtLocation(binding);
|
|
118
|
+
const isAlias = node => ts.isIdentifier(node) && node.text === alias
|
|
119
|
+
&& (!bindingSymbol || checker?.getSymbolAtLocation(node) === bindingSymbol);
|
|
120
|
+
const visit = node => {
|
|
121
|
+
usages.push(...namespaceMemberUsages(ts, node, isAlias), ...namespaceEscapeUsages(ts, node, isAlias));
|
|
122
|
+
ts.forEachChild(node, visit);
|
|
113
123
|
};
|
|
124
|
+
visit(sourceFile);
|
|
125
|
+
return usages;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function importSpecifierOf(ts, statement) {
|
|
129
|
+
const importEqualsSpecifier = ts.isImportEqualsDeclaration(statement)
|
|
130
|
+
&& ts.isExternalModuleReference(statement.moduleReference)
|
|
131
|
+
&& statement.moduleReference.expression
|
|
132
|
+
&& ts.isStringLiteralLike(statement.moduleReference.expression)
|
|
133
|
+
? statement.moduleReference.expression
|
|
134
|
+
: null;
|
|
135
|
+
let importSpecifier = importEqualsSpecifier;
|
|
136
|
+
if (ts.isImportDeclaration(statement) && ts.isStringLiteralLike(statement.moduleSpecifier)) importSpecifier = statement.moduleSpecifier;
|
|
137
|
+
return importSpecifier;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function addCommonImportEvidence(ts, statement, importSpecifier, sourceFile, checker, found) {
|
|
141
|
+
if (ts.isImportEqualsDeclaration(statement)) {
|
|
142
|
+
found.push(...namespaceTransportUsages(ts, sourceFile, checker, statement.name).map(item => ({ ...item, specifier: importSpecifier.text })));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (!statement.importClause) return;
|
|
146
|
+
const bindings = statement.importClause.namedBindings;
|
|
147
|
+
if (bindings && ts.isNamespaceImport(bindings)) {
|
|
148
|
+
found.push(...namespaceTransportUsages(ts, sourceFile, checker, bindings.name).map(item => ({ ...item, specifier: importSpecifier.text })));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (!bindings || !ts.isNamedImports(bindings)) return;
|
|
152
|
+
for (const element of bindings.elements) {
|
|
153
|
+
const imported = element.propertyName?.text ?? element.name.text;
|
|
154
|
+
if (NEST_COMMON_TRANSPORT.has(imported)) found.push({ node: element, specifier: importSpecifier.text, detail: imported });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function importTransportEvidence(ts, sourceFile, checker, found) {
|
|
114
159
|
for (const statement of sourceFile.statements) {
|
|
115
|
-
const
|
|
116
|
-
&& ts.isExternalModuleReference(statement.moduleReference)
|
|
117
|
-
&& statement.moduleReference.expression
|
|
118
|
-
&& ts.isStringLiteralLike(statement.moduleReference.expression)
|
|
119
|
-
? statement.moduleReference.expression
|
|
120
|
-
: null;
|
|
121
|
-
const importSpecifier = ts.isImportDeclaration(statement) && ts.isStringLiteralLike(statement.moduleSpecifier)
|
|
122
|
-
? statement.moduleSpecifier
|
|
123
|
-
: importEqualsSpecifier;
|
|
160
|
+
const importSpecifier = importSpecifierOf(ts, statement);
|
|
124
161
|
if (!importSpecifier) continue;
|
|
125
162
|
const specifier = importSpecifier.text;
|
|
126
163
|
if (TRANSPORT_PACKAGES.test(specifier)) {
|
|
127
164
|
found.push({ node: importSpecifier, specifier, detail: specifier });
|
|
128
165
|
continue;
|
|
129
166
|
}
|
|
130
|
-
if (specifier
|
|
131
|
-
if (ts.isImportEqualsDeclaration(statement)) {
|
|
132
|
-
found.push(...namespaceUsages(statement.name).map(item => ({ ...item, specifier })));
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
if (!statement.importClause) continue;
|
|
136
|
-
const bindings = statement.importClause.namedBindings;
|
|
137
|
-
if (bindings && ts.isNamespaceImport(bindings)) {
|
|
138
|
-
found.push(...namespaceUsages(bindings.name).map(item => ({ ...item, specifier })));
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
if (!bindings || !ts.isNamedImports(bindings)) continue;
|
|
142
|
-
for (const element of bindings.elements) {
|
|
143
|
-
const imported = element.propertyName?.text ?? element.name.text;
|
|
144
|
-
if (NEST_COMMON_TRANSPORT.has(imported)) found.push({ node: element, specifier, detail: imported });
|
|
145
|
-
}
|
|
167
|
+
if (specifier === '@nestjs/common') addCommonImportEvidence(ts, statement, importSpecifier, sourceFile, checker, found);
|
|
146
168
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function requiredSpecifier(ts, call) {
|
|
172
|
+
return ts.isCallExpression(call)
|
|
173
|
+
&& ts.isIdentifier(call.expression) && call.expression.text === 'require'
|
|
174
|
+
&& call.arguments.length === 1 && ts.isStringLiteralLike(call.arguments[0])
|
|
175
|
+
? call.arguments[0].text
|
|
176
|
+
: null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const requiredMemberSelection = (ts, node) => {
|
|
180
|
+
if (ts.isPropertyAccessExpression(node)) return node.name.text;
|
|
181
|
+
return ts.isStringLiteralLike(node.argumentExpression) ? node.argumentExpression.text : null;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
function requiredMemberEvidence(ts, node, found) {
|
|
185
|
+
if (!(ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) || !ts.isCallExpression(node.expression)) return;
|
|
186
|
+
const specifier = requiredSpecifier(ts, node.expression);
|
|
187
|
+
const selected = requiredMemberSelection(ts, node);
|
|
188
|
+
const member = ts.isPropertyAccessExpression(node) ? node.name : node.argumentExpression;
|
|
189
|
+
if (specifier && TRANSPORT_PACKAGES.test(specifier)) {
|
|
190
|
+
found.push({ node: member, specifier, detail: `${selected ?? 'computed member'} from ${specifier}` });
|
|
191
|
+
} else if (specifier === '@nestjs/common' && (selected === null || NEST_COMMON_TRANSPORT.has(selected))) {
|
|
192
|
+
found.push({ node: member, specifier, detail: selected ?? 'computed @nestjs/common require access' });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function requiredVariableEvidence(ts, node, sourceFile, checker, found) {
|
|
197
|
+
if (!ts.isVariableDeclaration(node) || !node.initializer || !ts.isCallExpression(node.initializer)
|
|
198
|
+
|| !requiredSpecifier(ts, node.initializer)) return;
|
|
199
|
+
const specifier = requiredSpecifier(ts, node.initializer);
|
|
200
|
+
if (TRANSPORT_PACKAGES.test(specifier)) {
|
|
201
|
+
found.push({ node: node.initializer.arguments[0], specifier, detail: specifier });
|
|
202
|
+
} else if (specifier === '@nestjs/common') {
|
|
203
|
+
if (ts.isIdentifier(node.name)) {
|
|
204
|
+
found.push(...namespaceTransportUsages(ts, sourceFile, checker, node.name).map(item => ({ ...item, specifier })));
|
|
205
|
+
} else if (ts.isObjectBindingPattern(node.name)) {
|
|
206
|
+
found.push(...destructuredTransportBindings(ts, node.name.elements, 'computed @nestjs/common require destructuring').map(item => ({ ...item, specifier })));
|
|
176
207
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function requiredTransportEvidence(ts, node, sourceFile, checker, found) {
|
|
212
|
+
requiredMemberEvidence(ts, node, found);
|
|
213
|
+
requiredVariableEvidence(ts, node, sourceFile, checker, found);
|
|
214
|
+
ts.forEachChild(node, child => requiredTransportEvidence(ts, child, sourceFile, checker, found));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function transportFrameworkEvidence(ts, sourceFile, checker) {
|
|
218
|
+
const found = [];
|
|
219
|
+
importTransportEvidence(ts, sourceFile, checker, found);
|
|
220
|
+
requiredTransportEvidence(ts, sourceFile, sourceFile, checker, found);
|
|
180
221
|
return found;
|
|
181
222
|
}
|
|
182
223
|
|
|
@@ -207,7 +248,7 @@ function selectedReexportSurface(ts, declaration, selected) {
|
|
|
207
248
|
return next.size ? next : undefined;
|
|
208
249
|
}
|
|
209
250
|
|
|
210
|
-
function
|
|
251
|
+
function importedTransportBindings(ts, sourceFile) {
|
|
211
252
|
const imported = new Map();
|
|
212
253
|
for (const statement of sourceFile.statements) {
|
|
213
254
|
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteralLike(statement.moduleSpecifier) || !statement.importClause) continue;
|
|
@@ -217,60 +258,92 @@ function externalTransportReexport(ts, sourceFile, selected) {
|
|
|
217
258
|
for (const element of bindings.elements) imported.set(element.name.text, { specifier, imported: element.propertyName?.text ?? element.name.text });
|
|
218
259
|
} else if (bindings && ts.isNamespaceImport(bindings)) imported.set(bindings.name.text, { specifier, imported: null });
|
|
219
260
|
}
|
|
261
|
+
return imported;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const unnamedReexport = (statement, specifier, selected) => {
|
|
265
|
+
if (specifier !== '@nestjs/common' || selected === null || [...selected].some(name => NEST_COMMON_TRANSPORT.has(name))) {
|
|
266
|
+
return { node: statement.moduleSpecifier, specifier, detail: selected === null ? `unbounded ${specifier} re-export` : `re-exported ${[...selected].join(', ')}` };
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const namedReexport = (clause, specifier, selected) => {
|
|
272
|
+
for (const element of clause.elements) {
|
|
273
|
+
if (selected !== null && !selected.has(element.name.text)) continue;
|
|
274
|
+
const original = element.propertyName?.text ?? element.name.text;
|
|
275
|
+
if (specifier !== '@nestjs/common' || NEST_COMMON_TRANSPORT.has(original)) return { node: element, specifier, detail: `${element.name.text} re-exported from ${specifier}` };
|
|
276
|
+
}
|
|
277
|
+
return null;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
function directTransportReexport(ts, statement, specifier, selected) {
|
|
281
|
+
const clause = statement.exportClause;
|
|
282
|
+
if (!clause) return unnamedReexport(statement, specifier, selected);
|
|
283
|
+
if (ts.isNamespaceExport(clause)) {
|
|
284
|
+
return selected === null || selected.has(clause.name.text) ? { node: clause.name, specifier, detail: `namespace re-export from ${specifier}` } : null;
|
|
285
|
+
}
|
|
286
|
+
return ts.isNamedExports(clause) ? namedReexport(clause, specifier, selected) : null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** True for an import binding of a transport package, or of a transport symbol of @nestjs/common. */
|
|
290
|
+
const isTransportBinding = binding => (binding.specifier === '@nestjs/common'
|
|
291
|
+
? binding.imported !== null && NEST_COMMON_TRANSPORT.has(binding.imported)
|
|
292
|
+
: TRANSPORT_PACKAGES.test(binding.specifier));
|
|
293
|
+
|
|
294
|
+
function localTransportReexport(ts, statement, selected, imported) {
|
|
295
|
+
const clause = statement.exportClause;
|
|
296
|
+
if (!clause || !ts.isNamedExports(clause)) return null;
|
|
297
|
+
for (const element of clause.elements) {
|
|
298
|
+
if (selected !== null && !selected.has(element.name.text)) continue;
|
|
299
|
+
const binding = imported.get(element.propertyName?.text ?? element.name.text);
|
|
300
|
+
if (!binding || !isTransportBinding(binding)) continue;
|
|
301
|
+
return { node: element, specifier: binding.specifier, detail: `${element.name.text} re-exported from ${binding.specifier}` };
|
|
302
|
+
}
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const statementTransportReexport = (ts, statement, selected, imported) => {
|
|
307
|
+
const specifier = statement.moduleSpecifier && ts.isStringLiteralLike(statement.moduleSpecifier) ? statement.moduleSpecifier.text : null;
|
|
308
|
+
if (specifier && (specifier === '@nestjs/common' || TRANSPORT_PACKAGES.test(specifier))) return directTransportReexport(ts, statement, specifier, selected);
|
|
309
|
+
return specifier ? null : localTransportReexport(ts, statement, selected, imported);
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
function externalTransportReexport(ts, sourceFile, selected) {
|
|
313
|
+
const imported = importedTransportBindings(ts, sourceFile);
|
|
220
314
|
for (const statement of sourceFile.statements) {
|
|
221
315
|
if (!ts.isExportDeclaration(statement)) continue;
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
if (specifier && (specifier === '@nestjs/common' || TRANSPORT_PACKAGES.test(specifier))) {
|
|
225
|
-
if (!clause) {
|
|
226
|
-
if (specifier !== '@nestjs/common' || selected === null || [...selected].some(name => NEST_COMMON_TRANSPORT.has(name))) {
|
|
227
|
-
return { node: statement.moduleSpecifier, specifier, detail: selected === null ? `unbounded ${specifier} re-export` : `re-exported ${[...selected].join(', ')}` };
|
|
228
|
-
}
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
if (ts.isNamespaceExport(clause)) {
|
|
232
|
-
if (selected === null || selected.has(clause.name.text)) return { node: clause.name, specifier, detail: `namespace re-export from ${specifier}` };
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
if (!ts.isNamedExports(clause)) continue;
|
|
236
|
-
for (const element of clause.elements) {
|
|
237
|
-
if (selected !== null && !selected.has(element.name.text)) continue;
|
|
238
|
-
const original = element.propertyName?.text ?? element.name.text;
|
|
239
|
-
if (specifier !== '@nestjs/common' || NEST_COMMON_TRANSPORT.has(original)) return { node: element, specifier, detail: `${element.name.text} re-exported from ${specifier}` };
|
|
240
|
-
}
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
if (specifier || !clause || !ts.isNamedExports(clause)) continue;
|
|
244
|
-
for (const element of clause.elements) {
|
|
245
|
-
if (selected !== null && !selected.has(element.name.text)) continue;
|
|
246
|
-
const local = element.propertyName?.text ?? element.name.text;
|
|
247
|
-
const binding = imported.get(local);
|
|
248
|
-
if (!binding) continue;
|
|
249
|
-
if (binding.specifier !== '@nestjs/common' && !TRANSPORT_PACKAGES.test(binding.specifier)) continue;
|
|
250
|
-
if (binding.specifier === '@nestjs/common' && (binding.imported === null || !NEST_COMMON_TRANSPORT.has(binding.imported))) continue;
|
|
251
|
-
return { node: element, specifier: binding.specifier, detail: `${element.name.text} re-exported from ${binding.specifier}` };
|
|
252
|
-
}
|
|
316
|
+
const evidence = statementTransportReexport(ts, statement, selected, imported);
|
|
317
|
+
if (evidence) return evidence;
|
|
253
318
|
}
|
|
254
319
|
return null;
|
|
255
320
|
}
|
|
256
321
|
|
|
322
|
+
function reexportQueueKey(current) {
|
|
323
|
+
return `${current.file}\0${current.selected === null ? '*' : [...current.selected].sort(byCodeUnit).join(',')}`;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function addReexportEdges(ts, context, current, queue) {
|
|
327
|
+
for (const edge of context.edges.get(current.file) ?? []) {
|
|
328
|
+
if (!edge.reexport) continue;
|
|
329
|
+
const selected = selectedReexportSurface(ts, edge.declaration, current.selected);
|
|
330
|
+
if (selected !== undefined) queue.push({ file: edge.to, selected, chain: [...current.chain, edge.to] });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
257
334
|
function reexportedTransportEvidence(ts, context, sourceFiles, firstEdge) {
|
|
258
335
|
const queue = [{ file: firstEdge.to, selected: importedSurface(ts, firstEdge.declaration), chain: [firstEdge.from, firstEdge.to] }];
|
|
259
336
|
const visited = new Set();
|
|
260
337
|
while (queue.length) {
|
|
261
338
|
const current = queue.shift();
|
|
262
|
-
const key =
|
|
339
|
+
const key = reexportQueueKey(current);
|
|
263
340
|
if (visited.has(key)) continue;
|
|
264
341
|
visited.add(key);
|
|
265
342
|
const sourceFile = sourceFiles.get(path.resolve(current.file));
|
|
266
343
|
if (!sourceFile) continue;
|
|
267
344
|
const evidence = externalTransportReexport(ts, sourceFile, current.selected);
|
|
268
345
|
if (evidence) return { evidence, chain: current.chain };
|
|
269
|
-
|
|
270
|
-
if (!edge.reexport) continue;
|
|
271
|
-
const selected = selectedReexportSurface(ts, edge.declaration, current.selected);
|
|
272
|
-
if (selected !== undefined) queue.push({ file: edge.to, selected, chain: [...current.chain, edge.to] });
|
|
273
|
-
}
|
|
346
|
+
addReexportEdges(ts, context, current, queue);
|
|
274
347
|
}
|
|
275
348
|
return null;
|
|
276
349
|
}
|
|
@@ -288,56 +361,69 @@ function finding(config, edge, ruleId, message, chain) {
|
|
|
288
361
|
};
|
|
289
362
|
}
|
|
290
363
|
|
|
364
|
+
function checkApplicationComposition(config, ts, sourceFile, fileName, app, violations) {
|
|
365
|
+
const evidence = roleEvidence(ts, sourceFile);
|
|
366
|
+
if (evidence) {
|
|
367
|
+
violations.push({
|
|
368
|
+
ruleId: 'BE_APP_BUSINESS_ROLE',
|
|
369
|
+
path: relativePath(config.root, fileName),
|
|
370
|
+
...sourceLocation(sourceFile, evidence.node),
|
|
371
|
+
message: `Application composition source contains measurable business/provider role ${evidence.detail}. Move that role under src/features or src/modules.`,
|
|
372
|
+
});
|
|
373
|
+
} else if (slotAdmitsFile(config.hfs, relativePath(config.root, fileName)) !== true) {
|
|
374
|
+
violations.push({
|
|
375
|
+
ruleId: FORBIDDEN_APP_ROLE.test(path.posix.basename(app.relative)) ? 'BE_APP_BUSINESS_ROLE' : 'BE_APP_COMPOSITION_ONLY',
|
|
376
|
+
path: relativePath(config.root, fileName),
|
|
377
|
+
line: 1,
|
|
378
|
+
column: 1,
|
|
379
|
+
message: `Application source ${app.relative} is not a file its app slot requires or allows (main.ts, app.module.ts, <app>.options.ts).`,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function checkApplicationTransport(config, context, sourceFile, fileName, violations) {
|
|
385
|
+
for (const evidence of transportFrameworkEvidence(context.ts, sourceFile, context.checkerFor(fileName))) {
|
|
386
|
+
violations.push({
|
|
387
|
+
ruleId: 'BE_APPLICATION_TRANSPORT_FRAMEWORK',
|
|
388
|
+
path: relativePath(config.root, fileName),
|
|
389
|
+
...sourceLocation(sourceFile, evidence.node),
|
|
390
|
+
specifier: evidence.specifier,
|
|
391
|
+
message: `Feature application code imports transport framework surface ${evidence.detail}. Keep protocol decorators, request/response types, validation DTOs, and generated transport types under transport/.`,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function checkFeatureTransportEdges(config, context, sourceFiles, featureRoots, fileName, violations) {
|
|
397
|
+
for (const edge of context.edges.get(fileName) ?? []) {
|
|
398
|
+
const reexported = reexportedTransportEvidence(context.ts, context, sourceFiles, edge);
|
|
399
|
+
if (reexported) violations.push(finding(config, edge, 'BE_APPLICATION_TRANSPORT_FRAMEWORK', `Feature application code imports protocol surface ${reexported.evidence.detail} through an internal re-export. Keep protocol decorators and types under transport/.`, reexported.chain));
|
|
400
|
+
const transportChain = reachableViolation(context.edges, edge, target => insideFeatureLayer(featureRoots, target, 'transport'));
|
|
401
|
+
if (transportChain) violations.push(finding(config, edge, 'BE_APPLICATION_IMPORTS_TRANSPORT', 'Feature application code cannot depend on transport adapters or DTOs, including through a type import or barrel.', transportChain));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function checkBackendSource(config, context, sourceFiles, roots, sourceFile, violations) {
|
|
406
|
+
const { moduleRoots, featureRoots, nonAppRoots } = roots;
|
|
407
|
+
const fileName = path.resolve(sourceFile.fileName);
|
|
408
|
+
const fromModules = insideAny(moduleRoots, fileName);
|
|
409
|
+
const fromFeatures = insideAny(featureRoots, fileName);
|
|
410
|
+
const fromApplication = fromFeatures && insideFeatureLayer(featureRoots, fileName, 'application');
|
|
411
|
+
const app = !fromModules && !fromFeatures ? appSource(config, fileName, nonAppRoots) : null;
|
|
412
|
+
if (app) checkApplicationComposition(config, context.ts, sourceFile, fileName, app, violations);
|
|
413
|
+
if (!fromModules && !fromFeatures) return;
|
|
414
|
+
if (fromApplication) checkApplicationTransport(config, context, sourceFile, fileName, violations);
|
|
415
|
+
if (fromApplication) checkFeatureTransportEdges(config, context, sourceFiles, featureRoots, fileName, violations);
|
|
416
|
+
}
|
|
417
|
+
|
|
291
418
|
/** Enforce backend direction and keep executable apps as measurable composition roots. */
|
|
292
419
|
export function checkBackend(config, context) {
|
|
293
420
|
const violations = [];
|
|
294
421
|
const moduleRoots = roots(config.root, config.backend.modules);
|
|
295
422
|
const featureRoots = roots(config.root, config.backend.features);
|
|
296
|
-
const
|
|
423
|
+
const sourceRoots = { moduleRoots, featureRoots, nonAppRoots: [...moduleRoots, ...featureRoots] };
|
|
297
424
|
const sourceFiles = new Map(context.files.map(file => [path.resolve(file.fileName), file]));
|
|
298
425
|
for (const sourceFile of context.files) {
|
|
299
|
-
|
|
300
|
-
const fromModules = insideAny(moduleRoots, fileName);
|
|
301
|
-
const fromFeatures = insideAny(featureRoots, fileName);
|
|
302
|
-
const fromApplication = fromFeatures && insideFeatureLayer(featureRoots, fileName, 'application');
|
|
303
|
-
const app = !fromModules && !fromFeatures ? appSource(config, fileName, nonAppRoots) : null;
|
|
304
|
-
if (app) {
|
|
305
|
-
const evidence = roleEvidence(context.ts, sourceFile);
|
|
306
|
-
if (evidence) {
|
|
307
|
-
violations.push({
|
|
308
|
-
ruleId: 'BE_APP_BUSINESS_ROLE',
|
|
309
|
-
path: relativePath(config.root, fileName),
|
|
310
|
-
...sourceLocation(sourceFile, evidence.node),
|
|
311
|
-
message: `Application composition source contains measurable business/provider role ${evidence.detail}. Move that role under src/features or src/modules.`,
|
|
312
|
-
});
|
|
313
|
-
} else if (slotAdmitsFile(config.hfs, relativePath(config.root, fileName)) !== true) {
|
|
314
|
-
violations.push({
|
|
315
|
-
ruleId: FORBIDDEN_APP_ROLE.test(path.posix.basename(app.relative)) ? 'BE_APP_BUSINESS_ROLE' : 'BE_APP_COMPOSITION_ONLY',
|
|
316
|
-
path: relativePath(config.root, fileName),
|
|
317
|
-
line: 1,
|
|
318
|
-
column: 1,
|
|
319
|
-
message: `Application source ${app.relative} is not a file its app slot requires or allows (main.ts, app.module.ts, <app>.options.ts).`,
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
if (!fromModules && !fromFeatures) continue;
|
|
324
|
-
if (fromApplication) {
|
|
325
|
-
for (const evidence of transportFrameworkEvidence(context.ts, sourceFile, context.checkerFor(fileName))) violations.push({
|
|
326
|
-
ruleId: 'BE_APPLICATION_TRANSPORT_FRAMEWORK',
|
|
327
|
-
path: relativePath(config.root, fileName),
|
|
328
|
-
...sourceLocation(sourceFile, evidence.node),
|
|
329
|
-
specifier: evidence.specifier,
|
|
330
|
-
message: `Feature application code imports transport framework surface ${evidence.detail}. Keep protocol decorators, request/response types, validation DTOs, and generated transport types under transport/.`,
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
for (const edge of context.edges.get(fileName) ?? []) {
|
|
334
|
-
if (fromApplication) {
|
|
335
|
-
const reexported = reexportedTransportEvidence(context.ts, context, sourceFiles, edge);
|
|
336
|
-
if (reexported) violations.push(finding(config, edge, 'BE_APPLICATION_TRANSPORT_FRAMEWORK', `Feature application code imports protocol surface ${reexported.evidence.detail} through an internal re-export. Keep protocol decorators and types under transport/.`, reexported.chain));
|
|
337
|
-
const transportChain = reachableViolation(context.edges, edge, target => insideFeatureLayer(featureRoots, target, 'transport'));
|
|
338
|
-
if (transportChain) violations.push(finding(config, edge, 'BE_APPLICATION_IMPORTS_TRANSPORT', 'Feature application code cannot depend on transport adapters or DTOs, including through a type import or barrel.', transportChain));
|
|
339
|
-
}
|
|
340
|
-
}
|
|
426
|
+
checkBackendSource(config, context, sourceFiles, sourceRoots, sourceFile, violations);
|
|
341
427
|
}
|
|
342
428
|
return violations;
|
|
343
429
|
}
|