@starci/hfs 4.0.10 → 4.2.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 +5 -1
- package/emit/operations.mjs +1 -1
- package/emit/type-schema.mjs +2 -1
- package/lint/run.mjs +65 -24
- package/package.json +1 -1
- package/report/order.mjs +2 -0
- package/runtime/config.example.yaml +194 -0
- package/runtime/engine/by-code-unit.mjs +12 -0
- package/runtime/engine/config.mjs +222 -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/release-config.mjs +28 -0
- 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 +161 -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 +268 -0
- package/runtime/modules/models/registry.yaml +25 -359
- package/runtime/modules/models/runtimes.yaml +126 -265
- package/runtime/modules/models/tiers.yaml +66 -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 +164 -104
- 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 +121 -70
- 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/catalog-once.mjs +15 -0
- package/runtime/scripts/hfs/check.mjs +63 -71
- 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 +21 -4
- 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 +74 -695
- 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/env.mjs +10 -0
- 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/runtime/scripts/lib/yaml-cached.mjs +14 -0
- 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
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { canonical, isInside } from './config.mjs';
|
|
4
|
+
import { readJsonFile as readJson } from '../../lib/json.mjs';
|
|
5
|
+
import { exportTargetStrings, isUnshadowedCommonJsRequire, relativePath } from './typescript.mjs';
|
|
6
|
+
import { byCodeUnit } from '../../lib/list.mjs';
|
|
7
|
+
import { sameOrUnder } from '../../lib/path-key.mjs';
|
|
8
|
+
|
|
9
|
+
function grammarExport(manifest, packageName, specifier) {
|
|
10
|
+
const key = specifier === packageName ? '.' : `.${specifier.slice(packageName.length)}`;
|
|
11
|
+
const value = manifest?.exports?.[key];
|
|
12
|
+
const targets = exportTargetStrings(value);
|
|
13
|
+
return targets.length > 0 && targets.every(target => target.startsWith('./') && !target.includes('\\') && !target.split('/').includes('..'));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function grammarExportTargets(manifest, packageRoot, packageName, specifier) {
|
|
17
|
+
const key = specifier === packageName ? '.' : `.${specifier.slice(packageName.length)}`;
|
|
18
|
+
return exportTargetStrings(manifest?.exports?.[key]).filter(target => target.startsWith('./') && !target.includes('\\') && !target.split('/').includes('..'))
|
|
19
|
+
.map(target => canonical(path.resolve(packageRoot, target)));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function literalModules(ts, sourceFile, checker) {
|
|
23
|
+
const modules = [];
|
|
24
|
+
const visit = node => {
|
|
25
|
+
if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier && ts.isStringLiteralLike(node.moduleSpecifier)) {
|
|
26
|
+
modules.push({ node: node.moduleSpecifier, specifier: node.moduleSpecifier.text });
|
|
27
|
+
} else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference)
|
|
28
|
+
&& node.moduleReference.expression && ts.isStringLiteralLike(node.moduleReference.expression)) {
|
|
29
|
+
modules.push({ node: node.moduleReference.expression, specifier: node.moduleReference.expression.text });
|
|
30
|
+
} else if (ts.isCallExpression(node) && node.arguments.length > 0 && ts.isStringLiteralLike(node.arguments[0])
|
|
31
|
+
&& (node.expression.kind === ts.SyntaxKind.ImportKeyword || isUnshadowedCommonJsRequire(ts, checker, node.expression))) {
|
|
32
|
+
modules.push({ node: node.arguments[0], specifier: node.arguments[0].text });
|
|
33
|
+
}
|
|
34
|
+
ts.forEachChild(node, visit);
|
|
35
|
+
};
|
|
36
|
+
visit(sourceFile);
|
|
37
|
+
return modules;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function cssImports(content) {
|
|
41
|
+
const imports = [];
|
|
42
|
+
const withoutComments = content.replace(/\/\*[\s\S]*?\*\//g, match => match.replace(/[^\r\n]/g, ' '));
|
|
43
|
+
const pattern = new RegExp([String.raw`@import\s+(?:url\(\s*(?:(["'])`, String.raw`(.*?)\1|([^\s)]+))\s*\)|(["'])(.*?)\4)`].join(''), 'gi');
|
|
44
|
+
for (const match of withoutComments.matchAll(pattern)) {
|
|
45
|
+
imports.push({ specifier: match[2] ?? match[3] ?? match[5], index: match.index });
|
|
46
|
+
}
|
|
47
|
+
return imports;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function installRootFor(config) {
|
|
51
|
+
return config.packageRoot ?? config.root;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function installedPackageRoot(installRoot, packageName, from) {
|
|
55
|
+
for (let dir = from; isInside(installRoot, dir); dir = path.dirname(dir)) {
|
|
56
|
+
const candidate = path.join(dir, 'node_modules', ...packageName.split('/'));
|
|
57
|
+
if (fs.existsSync(path.join(candidate, 'package.json'))) return candidate;
|
|
58
|
+
if (dir === installRoot || path.dirname(dir) === dir) break;
|
|
59
|
+
}
|
|
60
|
+
return path.join(installRoot, 'node_modules', ...packageName.split('/'));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function grammarConsumers(config, grammar, local, installRoot) {
|
|
64
|
+
return grammar.consumerManifests.map(relative => {
|
|
65
|
+
const root = path.dirname(path.join(config.root, ...relative.split('/')));
|
|
66
|
+
const packageRoot = local?.root ?? installedPackageRoot(installRoot, grammar.package, root);
|
|
67
|
+
return { relative, root, packageRoot, packageManifest: readJson(path.join(packageRoot, 'package.json')),
|
|
68
|
+
manifest: readJson(path.join(config.root, ...relative.split('/'))) ?? {} };
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function uniqueGrammarInstalls(consumers) {
|
|
73
|
+
return [...new Map(consumers.map(consumer => [consumer.packageRoot, consumer])).values()];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function grammarPackageRoot(installs, local, installRoot, packageName) {
|
|
77
|
+
return installs[0]?.packageRoot ?? local?.root ?? path.join(installRoot, 'node_modules', ...packageName.split('/'));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function addInstallContractProblems(install, installs, grammar, configRoot, problems) {
|
|
81
|
+
const where = installs.length > 1 ? ` (as ${install.relative} resolves it, ${relativePath(configRoot, install.packageRoot)})` : '';
|
|
82
|
+
if (install.packageManifest?.name !== grammar.package) problems.push(`installed package ${grammar.package} is unavailable or has a different name${where}`);
|
|
83
|
+
if (!grammarExport(install.packageManifest, grammar.package, grammar.entry)) problems.push(`${grammar.entry} is not a safe declared package export${where}`);
|
|
84
|
+
if (!grammarExport(install.packageManifest, grammar.package, grammar.styleEntry)) problems.push(`${grammar.styleEntry} is not a safe declared style export${where}`);
|
|
85
|
+
const actualPeers = Object.keys(install.packageManifest?.peerDependencies ?? {}).sort(byCodeUnit);
|
|
86
|
+
const selectedPeers = [...grammar.peers].sort(byCodeUnit);
|
|
87
|
+
if (JSON.stringify(actualPeers) !== JSON.stringify(selectedPeers)) {
|
|
88
|
+
problems.push(`${grammar.package} peerDependencies must exactly match the selected peers ${selectedPeers.join(', ')}${where}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function addConsumerDependencyProblems(grammar, consumers, problems) {
|
|
93
|
+
for (const consumer of consumers) if (!Object.hasOwn(consumer.manifest.dependencies ?? {}, grammar.package)) {
|
|
94
|
+
problems.push(`${consumer.relative} does not declare ${grammar.package} as a runtime dependency`);
|
|
95
|
+
}
|
|
96
|
+
for (const peer of grammar.peers) {
|
|
97
|
+
for (const consumer of consumers) if (!Object.hasOwn(consumer.manifest.dependencies ?? {}, peer) && !Object.hasOwn(consumer.manifest.peerDependencies ?? {}, peer)) {
|
|
98
|
+
problems.push(`${consumer.relative} does not declare Grammar peer ${peer}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function addGrammarStyleProblems(config, grammar, problems, styleBypasses) {
|
|
104
|
+
for (const source of grammar.styleSources) {
|
|
105
|
+
const content = fs.readFileSync(path.join(config.root, ...source.split('/')), 'utf8');
|
|
106
|
+
const grammarStyles = cssImports(content).filter(item => item.specifier === grammar.package || item.specifier.startsWith(`${grammar.package}/`));
|
|
107
|
+
if (!grammarStyles.some(item => item.specifier === grammar.styleEntry)) problems.push(`${source} does not import ${grammar.styleEntry}`);
|
|
108
|
+
for (const imported of grammarStyles) if (imported.specifier !== grammar.styleEntry) {
|
|
109
|
+
problems.push(`${source} imports Grammar style ${imported.specifier} outside the selected style entry`);
|
|
110
|
+
styleBypasses.push({ ruleId: 'ARCH_GRAMMAR_EXPORT_BYPASS', path: source,
|
|
111
|
+
line: content.slice(0, imported.index).split(/\r?\n/u).length, column: 1, package: grammar.package, specifier: imported.specifier,
|
|
112
|
+
message: `Style source must use the selected Grammar style entry ${grammar.styleEntry}; ${imported.specifier} is outside that contract.` });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function grammarOwner(consumers, fileName) {
|
|
118
|
+
return consumers.filter(consumer => isInside(consumer.root, fileName))
|
|
119
|
+
.sort((a, b) => b.root.length - a.root.length)[0] ?? null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function sourceEdges(context, sourceFile) {
|
|
123
|
+
return new Map((context.edges.get(path.resolve(sourceFile.fileName)) ?? []).map(edge => [edge.node.getStart(sourceFile), edge]));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function grammarReferenceAllowed(reference, sourceFile, owner, edgesByStart, grammar, allowed) {
|
|
127
|
+
if (!allowed.has(reference.specifier)) return false;
|
|
128
|
+
const edge = edgesByStart.get(reference.node.getStart(sourceFile));
|
|
129
|
+
const expected = grammarExportTargets(owner.packageManifest, owner.packageRoot, grammar.package, reference.specifier);
|
|
130
|
+
return !edge || expected.includes(canonical(edge.to));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function reportGrammarReference(scan, sourceFile, reference, owner, edgesByStart) {
|
|
134
|
+
const { config, grammar, allowed, violations, makeViolation } = scan;
|
|
135
|
+
if (!sameOrUnder(reference.specifier, grammar.package)) return;
|
|
136
|
+
if (grammarReferenceAllowed(reference, sourceFile, owner, edgesByStart, grammar, allowed)) return;
|
|
137
|
+
violations.push(makeViolation(config, sourceFile, reference.node, 'ARCH_GRAMMAR_EXPORT_BYPASS',
|
|
138
|
+
`Product source must import the selected Grammar code entry ${grammar.entry} or style entry ${grammar.styleEntry}; ${reference.specifier} is outside that contract.`,
|
|
139
|
+
{ specifier: reference.specifier, package: grammar.package }));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function inspectGrammarSource(scan, sourceFile) {
|
|
143
|
+
const { context, consumers, installs } = scan;
|
|
144
|
+
if (installs.some(install => isInside(install.packageRoot, sourceFile.fileName))) return;
|
|
145
|
+
const owner = grammarOwner(consumers, sourceFile.fileName);
|
|
146
|
+
if (!owner) return;
|
|
147
|
+
const edgesByStart = sourceEdges(context, sourceFile);
|
|
148
|
+
const references = literalModules(context.ts, sourceFile, context.checkerFor(sourceFile.fileName));
|
|
149
|
+
for (const reference of references) reportGrammarReference(scan, sourceFile, reference, owner, edgesByStart);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function addGrammarContractFinding(config, grammar, manifestFile, problems, violations) {
|
|
153
|
+
if (!problems.length) return;
|
|
154
|
+
violations.push({
|
|
155
|
+
ruleId: 'ARCH_GRAMMAR_CONTRACT_INVALID',
|
|
156
|
+
path: fs.existsSync(manifestFile) && isInside(config.root, manifestFile) ? relativePath(config.root, manifestFile) : 'package.json',
|
|
157
|
+
line: 1,
|
|
158
|
+
column: 1,
|
|
159
|
+
package: grammar.package,
|
|
160
|
+
message: `Grammar contract is invalid: ${problems.join('; ')}.`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function checkGrammar(config, context, makeViolation) {
|
|
165
|
+
const grammar = config.frontend.grammar;
|
|
166
|
+
if (!grammar) return [];
|
|
167
|
+
const violations = [];
|
|
168
|
+
const local = context.workspaces.find(workspace => workspace.name === grammar.package);
|
|
169
|
+
// Node resolution per consumer: in an npm-workspaces monorepo a consumer whose range differs from the
|
|
170
|
+
// hoisted copy gets its own apps/<app>/node_modules/<package> (one consumer: apps/app on 0.5.0 beside a hoisted
|
|
171
|
+
// 0.4.11). Every consumer is judged against the copy it actually resolves, never the hoisted one alone.
|
|
172
|
+
// An app installs once, at its root (config.packageRoot): a side folder has no node_modules of its own.
|
|
173
|
+
const installRoot = installRootFor(config);
|
|
174
|
+
const consumers = grammarConsumers(config, grammar, local, installRoot);
|
|
175
|
+
const installs = uniqueGrammarInstalls(consumers);
|
|
176
|
+
const packageRoot = grammarPackageRoot(installs, local, installRoot, grammar.package);
|
|
177
|
+
const manifestFile = path.join(packageRoot, 'package.json');
|
|
178
|
+
readJson(manifestFile);
|
|
179
|
+
const contractProblems = [];
|
|
180
|
+
const styleBypasses = [];
|
|
181
|
+
for (const install of installs) addInstallContractProblems(install, installs, grammar, config.root, contractProblems);
|
|
182
|
+
addConsumerDependencyProblems(grammar, consumers, contractProblems);
|
|
183
|
+
addGrammarStyleProblems(config, grammar, contractProblems, styleBypasses);
|
|
184
|
+
addGrammarContractFinding(config, grammar, manifestFile, contractProblems, violations);
|
|
185
|
+
const allowed = new Set([grammar.entry, grammar.styleEntry]);
|
|
186
|
+
violations.push(...styleBypasses);
|
|
187
|
+
const scan = { config, context, grammar, consumers, installs, allowed, violations, makeViolation };
|
|
188
|
+
for (const sourceFile of context.files) inspectGrammarSource(scan, sourceFile);
|
|
189
|
+
return violations;
|
|
190
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { reachableViolation, relativePath } from './typescript.mjs';
|
|
3
|
+
|
|
4
|
+
function hasClientDirective(ts, sourceFile) {
|
|
5
|
+
return sourceFile.statements.find(statement => ts.isExpressionStatement(statement)
|
|
6
|
+
&& ts.isStringLiteral(statement.expression) && statement.expression.text === 'use client');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function bindImportClause(ts, clause, edge, bindings) {
|
|
10
|
+
if (clause.name && !clause.isTypeOnly) bindings.set(clause.name.text, edge);
|
|
11
|
+
if (!clause.namedBindings) return;
|
|
12
|
+
if (ts.isNamespaceImport(clause.namedBindings)) bindings.set(clause.namedBindings.name.text, edge);
|
|
13
|
+
if (!ts.isNamedImports(clause.namedBindings)) return;
|
|
14
|
+
for (const element of clause.namedBindings.elements) {
|
|
15
|
+
if (!element.isTypeOnly && !clause.isTypeOnly) bindings.set(element.name.text, edge);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function importBindings(ts, sourceFile, edges) {
|
|
20
|
+
const byStart = new Map(edges.map(edge => [edge.node.getStart(sourceFile), edge]));
|
|
21
|
+
const bindings = new Map();
|
|
22
|
+
for (const statement of sourceFile.statements) {
|
|
23
|
+
if (!ts.isImportDeclaration(statement) || !statement.importClause || !ts.isStringLiteralLike(statement.moduleSpecifier)) continue;
|
|
24
|
+
const edge = byStart.get(statement.moduleSpecifier.getStart(sourceFile));
|
|
25
|
+
if (!edge) continue;
|
|
26
|
+
bindImportClause(ts, statement.importClause, edge, bindings);
|
|
27
|
+
}
|
|
28
|
+
return bindings;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function importedNames(ts, sourceFile, moduleName) {
|
|
32
|
+
const names = new Map();
|
|
33
|
+
for (const statement of sourceFile.statements) {
|
|
34
|
+
if (!ts.isImportDeclaration(statement) || statement.moduleSpecifier.text !== moduleName || !statement.importClause || statement.importClause.isTypeOnly) continue;
|
|
35
|
+
const named = statement.importClause.namedBindings;
|
|
36
|
+
if (!named || !ts.isNamedImports(named)) continue;
|
|
37
|
+
for (const element of named.elements) if (!element.isTypeOnly) names.set(element.name.text, element.propertyName?.text ?? element.name.text);
|
|
38
|
+
}
|
|
39
|
+
return names;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function jsxRootName(ts, tagName) {
|
|
43
|
+
if (ts.isIdentifier(tagName)) return tagName.text;
|
|
44
|
+
if (ts.isPropertyAccessExpression(tagName)) {
|
|
45
|
+
let current = tagName;
|
|
46
|
+
while (ts.isPropertyAccessExpression(current)) current = current.expression;
|
|
47
|
+
return ts.isIdentifier(current) ? current.text : null;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function addRouteVariableFunctions(ts, statement, variables) {
|
|
53
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
54
|
+
if (ts.isIdentifier(declaration.name) && declaration.initializer
|
|
55
|
+
&& (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))) variables.set(declaration.name.text, declaration.initializer);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function recordRouteStatement(ts, statement, state) {
|
|
60
|
+
if (ts.isVariableStatement(statement)) addRouteVariableFunctions(ts, statement, state.variables);
|
|
61
|
+
if (ts.isFunctionDeclaration(statement) && statement.name) state.functions.set(statement.name.text, statement);
|
|
62
|
+
if (ts.isFunctionDeclaration(statement) && statement.modifiers?.some(item => item.kind === ts.SyntaxKind.DefaultKeyword)) state.directDefault = statement;
|
|
63
|
+
if (ts.isExportAssignment(statement) && !statement.isExportEquals) state.exportNode = statement;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function routeFunction(ts, sourceFile) {
|
|
67
|
+
const state = { variables: new Map(), functions: new Map(), directDefault: null, exportNode: null };
|
|
68
|
+
for (const statement of sourceFile.statements) recordRouteStatement(ts, statement, state);
|
|
69
|
+
if (state.directDefault) return { fn: state.directDefault, exportNode: state.directDefault };
|
|
70
|
+
if (!state.exportNode || !ts.isIdentifier(state.exportNode.expression)) return { fn: null, exportNode: state.exportNode };
|
|
71
|
+
return { fn: state.variables.get(state.exportNode.expression.text) ?? state.functions.get(state.exportNode.expression.text) ?? null, exportNode: state.exportNode };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function routePageRoots(context, sourceFile, featureRoots) {
|
|
75
|
+
const workspace = context.workspaceOf(sourceFile.fileName);
|
|
76
|
+
if (!workspace) return featureRoots.map(root => path.join(root, 'pages'));
|
|
77
|
+
const local = featureRoots.filter(root => context.workspaceOf(root) === workspace);
|
|
78
|
+
return (local.length ? local : featureRoots).map(root => path.join(root, 'pages'));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function routingTermination(ts, statement, navigation) {
|
|
82
|
+
if (ts.isThrowStatement(statement)) return true;
|
|
83
|
+
if (ts.isBlock(statement)) return statement.statements.length > 0 && routingTermination(ts, statement.statements.at(-1), navigation);
|
|
84
|
+
const expression = ts.isExpressionStatement(statement) ? statement.expression : (ts.isReturnStatement(statement) && statement.expression) || null;
|
|
85
|
+
return Boolean(expression && ts.isCallExpression(expression) && ts.isIdentifier(expression.expression)
|
|
86
|
+
&& ['redirect', 'notFound'].includes(navigation.get(expression.expression.text)));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function routingGuard(ts, node, navigation) {
|
|
90
|
+
return ts.isIfStatement(node) && !node.elseStatement && routingTermination(ts, node.thenStatement, navigation);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function routeNavigationCall(ts, node, navigation) {
|
|
94
|
+
return ts.isCallExpression(node) && ts.isIdentifier(node.expression)
|
|
95
|
+
&& ['redirect', 'notFound'].includes(navigation.get(node.expression.text));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function recordRouteNode(ts, node, state, insideNavigationArgument) {
|
|
99
|
+
if (ts.isJsxSelfClosingElement(node) || ts.isJsxElement(node)) state.jsx.push(node);
|
|
100
|
+
if (ts.isIfStatement(node) || ts.isSwitchStatement(node) || (ts.isConditionalExpression(node) && !insideNavigationArgument)) state.decisions.push(node);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function visitRouteNode(ts, node, state, insideNavigationArgument = false) {
|
|
104
|
+
recordRouteNode(ts, node, state, insideNavigationArgument);
|
|
105
|
+
if (routeNavigationCall(ts, node, state.navigation)) {
|
|
106
|
+
state.adapterCalls.push(node);
|
|
107
|
+
for (const argument of node.arguments) visitRouteNode(ts, argument, state, true);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
ts.forEachChild(node, child => visitRouteNode(ts, child, state, insideNavigationArgument));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function collectRouteNodes(ts, routeFunction, navigation) {
|
|
114
|
+
const state = { jsx: [], adapterCalls: [], decisions: [], navigation };
|
|
115
|
+
visitRouteNode(ts, routeFunction, state);
|
|
116
|
+
return state;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function mountedRoutePages(ts, context, sourceFile, featureRoots, bindings, jsx, insideAny) {
|
|
120
|
+
const pages = routePageRoots(context, sourceFile, featureRoots);
|
|
121
|
+
return jsx.filter(node => {
|
|
122
|
+
const opening = ts.isJsxElement(node) ? node.openingElement : node;
|
|
123
|
+
const local = jsxRootName(ts, opening.tagName);
|
|
124
|
+
const edge = local ? bindings.get(local) : null;
|
|
125
|
+
return edge && Boolean(reachableViolation(context.edges, edge, target => insideAny(pages, target), { follow: candidate => candidate.reexport && candidate.runtime }));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function reportRouteComposition(at, route, scan, mountedPages) {
|
|
130
|
+
const { config, context, sourceFile, violations, violation } = at;
|
|
131
|
+
const navigationOnly = scan.jsx.length === 0 && scan.adapterCalls.length === 1;
|
|
132
|
+
if (!navigationOnly && (mountedPages.length !== 1 || scan.jsx.length !== 1)) {
|
|
133
|
+
violations.push(violation(config, sourceFile, route.fn, 'FE_ROUTE_ONE_PAGE', 'A page.tsx route must mount exactly one pages-tier component, or be a zero-JSX redirect/notFound adapter.', { ts: context.ts }));
|
|
134
|
+
}
|
|
135
|
+
return navigationOnly;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function reportRouteDecisions(at, decisions, navigation, navigationOnly) {
|
|
139
|
+
if (navigationOnly) return;
|
|
140
|
+
const { config, context, sourceFile, violations, violation } = at;
|
|
141
|
+
for (const decision of decisions) if (!routingGuard(context.ts, decision, navigation)) {
|
|
142
|
+
violations.push(violation(config, sourceFile, decision, 'FE_ROUTE_DRAWING_DECISION', 'Visual route files may use terminal redirect/notFound guards but cannot select or omit visual composition.', { ts: context.ts }));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function reportClientDirective(config, context, sourceFile, violations, violation) {
|
|
147
|
+
const clientDirective = hasClientDirective(context.ts, sourceFile);
|
|
148
|
+
if (clientDirective) violations.push(violation(config, sourceFile, clientDirective, 'FE_ROUTE_CLIENT_BOUNDARY', 'A page.tsx route adapter cannot own the client boundary.', { ts: context.ts }));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function routeHookName(ts, sourceFile, local) {
|
|
152
|
+
return sourceFile.statements.flatMap(statement => ts.isImportDeclaration(statement) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings)
|
|
153
|
+
? statement.importClause.namedBindings.elements.filter(item => item.name.text === local).map(item => item.name) : [])[0];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function reportForbiddenRouteHooks(config, context, sourceFile, navigation, violations, violation) {
|
|
157
|
+
const { ts } = context;
|
|
158
|
+
const forbiddenRouteHooks = new Set(['useRouter', 'usePathname', 'useSearchParams', 'useParams', 'useSelectedLayoutSegment', 'useSelectedLayoutSegments']);
|
|
159
|
+
for (const [local, imported] of navigation) if (forbiddenRouteHooks.has(imported)) {
|
|
160
|
+
const identifier = routeHookName(ts, sourceFile, local);
|
|
161
|
+
if (identifier) violations.push(violation(config, sourceFile, identifier, 'FE_ROUTE_CLIENT_HOOK', `Server route adapter imports ${imported}.`, { ts }));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function checkRoute(config, context, sourceFile, roots, helpers) {
|
|
166
|
+
const { violation, insideAny, importsForSource } = helpers;
|
|
167
|
+
const { ts } = context;
|
|
168
|
+
const violations = [];
|
|
169
|
+
const edges = importsForSource(context, sourceFile.fileName);
|
|
170
|
+
const bindings = importBindings(ts, sourceFile, edges);
|
|
171
|
+
const route = routeFunction(ts, sourceFile);
|
|
172
|
+
if (!route.exportNode || !route.fn) {
|
|
173
|
+
violations.push({ ruleId: 'FE_ROUTE_DEFAULT_EXPORT', path: relativePath(config.root, sourceFile.fileName), line: 1, column: 1,
|
|
174
|
+
message: 'A page.tsx must default-export one local route function.' });
|
|
175
|
+
return violations;
|
|
176
|
+
}
|
|
177
|
+
const navigation = importedNames(ts, sourceFile, 'next/navigation');
|
|
178
|
+
const scan = collectRouteNodes(ts, route.fn, navigation);
|
|
179
|
+
const mountedPages = mountedRoutePages(ts, context, sourceFile, roots.features, bindings, scan.jsx, insideAny);
|
|
180
|
+
const at = { config, context, sourceFile, violations, violation };
|
|
181
|
+
const navigationOnly = reportRouteComposition(at, route, scan, mountedPages);
|
|
182
|
+
reportRouteDecisions(at, scan.decisions, navigation, navigationOnly);
|
|
183
|
+
reportClientDirective(config, context, sourceFile, violations, violation);
|
|
184
|
+
reportForbiddenRouteHooks(config, context, sourceFile, navigation, violations, violation);
|
|
185
|
+
return violations;
|
|
186
|
+
}
|