@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { contextFromProjects } from './type-context.mjs';
|
|
3
4
|
import { createRequire } from 'node:module';
|
|
4
5
|
import { canonical, isInside, slash } from './config.mjs';
|
|
5
6
|
import { crossesSide } from './side-boundary.mjs';
|
|
@@ -10,8 +11,10 @@ import { sourceLocation } from '../../lib/ts-ast.mjs';
|
|
|
10
11
|
import { locateDeclaration } from '../slots.mjs';
|
|
11
12
|
|
|
12
13
|
const CODE_EXTENSIONS = /\.(?:[cm]?[jt]sx?)$/i;
|
|
13
|
-
const
|
|
14
|
-
const
|
|
14
|
+
const SCRIPT_TAIL = '[cm]?[jt]sx?';
|
|
15
|
+
const TEST_FILE = new RegExp(String.raw`(?:^|[.-])(?:spec|test)\.${SCRIPT_TAIL}$`, 'i');
|
|
16
|
+
const ASSET_EXTENSION_NAMES = 'css|scss|sass|less|svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|eot|ya?ml|json';
|
|
17
|
+
const ASSET_EXTENSION = new RegExp(String.raw`\.(?:${ASSET_EXTENSION_NAMES})$`, 'i');
|
|
15
18
|
// Framework build output a tsconfig may include (Next writes `.next/types/**/*.ts` back into tsconfig.json on
|
|
16
19
|
// every build) is compiled for type resolution but is never source: it is gitignored, regenerated, and no
|
|
17
20
|
// canon or architecture rule applies to it (inc-ffe60c49f502).
|
|
@@ -47,7 +50,7 @@ function compilerError(ts, root, diagnostic, project, ruleId = 'ARCH_TSCONFIG_IN
|
|
|
47
50
|
*/
|
|
48
51
|
function loadTargetTypeScript(repositoryRoot) {
|
|
49
52
|
const packageFile = path.join(locateDeclaration(repositoryRoot).appRoot, 'package.json');
|
|
50
|
-
if (!fs.existsSync(packageFile)) throw Error('ARCH_TYPESCRIPT_MISSING: target package.json is required to resolve target-installed TypeScript.');
|
|
53
|
+
if (!fs.existsSync(packageFile)) throw new Error('ARCH_TYPESCRIPT_MISSING: target package.json is required to resolve target-installed TypeScript.');
|
|
51
54
|
const targetRequire = createRequire(packageFile);
|
|
52
55
|
let resolved;
|
|
53
56
|
let ts;
|
|
@@ -55,10 +58,10 @@ function loadTargetTypeScript(repositoryRoot) {
|
|
|
55
58
|
resolved = targetRequire.resolve('typescript');
|
|
56
59
|
ts = targetRequire('typescript');
|
|
57
60
|
} catch {
|
|
58
|
-
throw Error('ARCH_TYPESCRIPT_MISSING: install TypeScript in the checked repository; StarCi does not substitute its own parser.');
|
|
61
|
+
throw new Error('ARCH_TYPESCRIPT_MISSING: install TypeScript in the checked repository; StarCi does not substitute its own parser.');
|
|
59
62
|
}
|
|
60
63
|
if (!ts?.createProgram || !ts?.resolveModuleName || !ts?.readConfigFile) {
|
|
61
|
-
throw Error('ARCH_TYPESCRIPT_INVALID: the target TypeScript package does not expose the compiler API.');
|
|
64
|
+
throw new Error('ARCH_TYPESCRIPT_INVALID: the target TypeScript package does not expose the compiler API.');
|
|
62
65
|
}
|
|
63
66
|
return { ts, resolved, version: String(ts.version ?? 'unknown') };
|
|
64
67
|
}
|
|
@@ -73,20 +76,25 @@ function pathAliasMatches(specifier, paths = {}) {
|
|
|
73
76
|
});
|
|
74
77
|
}
|
|
75
78
|
|
|
79
|
+
function importDeclarationRuntime(ts, node) {
|
|
80
|
+
const clause = node.importClause;
|
|
81
|
+
if (!clause) return true;
|
|
82
|
+
if (clause.isTypeOnly) return false;
|
|
83
|
+
if (clause.name) return true;
|
|
84
|
+
const named = clause.namedBindings;
|
|
85
|
+
if (named && ts.isNamedImports(named)) return named.elements.some(element => !element.isTypeOnly);
|
|
86
|
+
return Boolean(named);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function exportDeclarationRuntime(ts, node) {
|
|
90
|
+
if (node.isTypeOnly) return false;
|
|
91
|
+
if (node.exportClause && ts.isNamedExports(node.exportClause)) return node.exportClause.elements.some(element => !element.isTypeOnly);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
76
95
|
function runtimeImport(ts, node) {
|
|
77
|
-
if (ts.isImportDeclaration(node))
|
|
78
|
-
|
|
79
|
-
if (!clause) return true;
|
|
80
|
-
if (clause.isTypeOnly) return false;
|
|
81
|
-
if (clause.name) return true;
|
|
82
|
-
const named = clause.namedBindings;
|
|
83
|
-
if (named && ts.isNamedImports(named)) return named.elements.some(element => !element.isTypeOnly);
|
|
84
|
-
return Boolean(named);
|
|
85
|
-
}
|
|
86
|
-
if (ts.isExportDeclaration(node)) {
|
|
87
|
-
if (node.isTypeOnly) return false;
|
|
88
|
-
if (node.exportClause && ts.isNamedExports(node.exportClause)) return node.exportClause.elements.some(element => !element.isTypeOnly);
|
|
89
|
-
}
|
|
96
|
+
if (ts.isImportDeclaration(node)) return importDeclarationRuntime(ts, node);
|
|
97
|
+
if (ts.isExportDeclaration(node)) return exportDeclarationRuntime(ts, node);
|
|
90
98
|
return true;
|
|
91
99
|
}
|
|
92
100
|
|
|
@@ -105,17 +113,19 @@ export function isUnshadowedCommonJsRequire(ts, checker, expression) {
|
|
|
105
113
|
* existing directory and a data-asset extension tail qualify; code never does, since a context of code would
|
|
106
114
|
* be an import graph nobody wrote down. Returns the specifiers, or null when the import stays unproven.
|
|
107
115
|
*/
|
|
108
|
-
|
|
116
|
+
/** The data-asset extension tail and relative head of an import template, or null when the template cannot name a data context. */
|
|
117
|
+
function assetContextParts(ts, argument) {
|
|
109
118
|
if (!argument || !ts.isTemplateExpression(argument)) return null;
|
|
110
119
|
const head = argument.head.text;
|
|
111
120
|
const spans = argument.templateSpans;
|
|
112
121
|
const tail = spans[spans.length - 1].literal.text;
|
|
113
122
|
if (!/^\.\.?\//.test(head) || !head.endsWith('/') || !/^\.[a-z0-9]+$/i.test(tail) || !ASSET_EXTENSION.test(tail)) return null;
|
|
114
123
|
if (spans.slice(0, -1).some(span => span.literal.text.split('/').includes('..'))) return null;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
return { head, tail };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The specifiers of every file below `directory` (node_modules skipped) whose name ends in `tail`, sorted by name at each level. */
|
|
128
|
+
function assetFileSpecifiers(directory, head, tail) {
|
|
119
129
|
const specifiers = [];
|
|
120
130
|
const visit = (dir) => {
|
|
121
131
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
@@ -126,34 +136,62 @@ function assetContextSpecifiers(ts, sourceFile, argument) {
|
|
|
126
136
|
}
|
|
127
137
|
};
|
|
128
138
|
visit(directory);
|
|
139
|
+
return specifiers;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function assetContextSpecifiers(ts, sourceFile, argument) {
|
|
143
|
+
const parts = assetContextParts(ts, argument);
|
|
144
|
+
if (!parts) return null;
|
|
145
|
+
const directory = path.resolve(path.dirname(sourceFile.fileName), parts.head);
|
|
146
|
+
let stat;
|
|
147
|
+
try { stat = fs.statSync(directory); } catch { return null; }
|
|
148
|
+
if (!stat.isDirectory()) return null;
|
|
149
|
+
const specifiers = assetFileSpecifiers(directory, parts.head, parts.tail);
|
|
129
150
|
return specifiers.length ? specifiers : null;
|
|
130
151
|
}
|
|
131
152
|
|
|
153
|
+
function staticModuleReference(ts, node, found, unproven) {
|
|
154
|
+
if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier && ts.isStringLiteralLike(node.moduleSpecifier)) {
|
|
155
|
+
found.push({ node: node.moduleSpecifier, specifier: node.moduleSpecifier.text, runtime: runtimeImport(ts, node), declaration: node });
|
|
156
|
+
return true;
|
|
157
|
+
} else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference)
|
|
158
|
+
&& node.moduleReference.expression && ts.isStringLiteralLike(node.moduleReference.expression)) {
|
|
159
|
+
found.push({ node: node.moduleReference.expression, specifier: node.moduleReference.expression.text, runtime: !node.isTypeOnly, declaration: node });
|
|
160
|
+
return true;
|
|
161
|
+
} else if (ts.isImportTypeNode(node)) {
|
|
162
|
+
const literal = ts.isLiteralTypeNode(node.argument) && ts.isStringLiteralLike(node.argument.literal) ? node.argument.literal : null;
|
|
163
|
+
if (literal) found.push({ node: literal, specifier: literal.text, runtime: false, declaration: node });
|
|
164
|
+
else unproven.push({ node, kind: 'TypeScript import type' });
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function dynamicImportReference(ts, sourceFile, node, found, unproven) {
|
|
171
|
+
const context = [1, 2].includes(node.arguments.length) ? assetContextSpecifiers(ts, sourceFile, node.arguments[0]) : null;
|
|
172
|
+
if ([1, 2].includes(node.arguments.length) && ts.isStringLiteralLike(node.arguments[0])) {
|
|
173
|
+
found.push({ node: node.arguments[0], specifier: node.arguments[0].text, runtime: true, declaration: node });
|
|
174
|
+
} else if (context) {
|
|
175
|
+
for (const specifier of context) found.push({ node: node.arguments[0], specifier, runtime: true, declaration: node });
|
|
176
|
+
} else unproven.push({ node, kind: 'dynamic import()' });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function requireReference(ts, checker, node, found, unproven) {
|
|
180
|
+
if (node.arguments.length === 1 && ts.isStringLiteralLike(node.arguments[0])) {
|
|
181
|
+
found.push({ node: node.arguments[0], specifier: node.arguments[0].text, runtime: true, declaration: node });
|
|
182
|
+
} else unproven.push({ node, kind: 'dynamic require()' });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function callModuleReference(ts, sourceFile, checker, node, found, unproven) {
|
|
186
|
+
if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) dynamicImportReference(ts, sourceFile, node, found, unproven);
|
|
187
|
+
else if (ts.isCallExpression(node) && isUnshadowedCommonJsRequire(ts, checker, node.expression)) requireReference(ts, checker, node, found, unproven);
|
|
188
|
+
}
|
|
189
|
+
|
|
132
190
|
function moduleReferences(ts, sourceFile, checker) {
|
|
133
191
|
const found = [];
|
|
134
192
|
const unproven = [];
|
|
135
193
|
const visit = node => {
|
|
136
|
-
if ((ts
|
|
137
|
-
found.push({ node: node.moduleSpecifier, specifier: node.moduleSpecifier.text, runtime: runtimeImport(ts, node), declaration: node });
|
|
138
|
-
} else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference)
|
|
139
|
-
&& node.moduleReference.expression && ts.isStringLiteralLike(node.moduleReference.expression)) {
|
|
140
|
-
found.push({ node: node.moduleReference.expression, specifier: node.moduleReference.expression.text, runtime: !node.isTypeOnly, declaration: node });
|
|
141
|
-
} else if (ts.isImportTypeNode(node)) {
|
|
142
|
-
const literal = ts.isLiteralTypeNode(node.argument) && ts.isStringLiteralLike(node.argument.literal) ? node.argument.literal : null;
|
|
143
|
-
if (literal) found.push({ node: literal, specifier: literal.text, runtime: false, declaration: node });
|
|
144
|
-
else unproven.push({ node, kind: 'TypeScript import type' });
|
|
145
|
-
} else if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
146
|
-
const context = [1, 2].includes(node.arguments.length) ? assetContextSpecifiers(ts, sourceFile, node.arguments[0]) : null;
|
|
147
|
-
if ([1, 2].includes(node.arguments.length) && ts.isStringLiteralLike(node.arguments[0])) {
|
|
148
|
-
found.push({ node: node.arguments[0], specifier: node.arguments[0].text, runtime: true, declaration: node });
|
|
149
|
-
} else if (context) {
|
|
150
|
-
for (const specifier of context) found.push({ node: node.arguments[0], specifier, runtime: true, declaration: node });
|
|
151
|
-
} else unproven.push({ node, kind: 'dynamic import()' });
|
|
152
|
-
} else if (ts.isCallExpression(node) && isUnshadowedCommonJsRequire(ts, checker, node.expression)) {
|
|
153
|
-
if (node.arguments.length === 1 && ts.isStringLiteralLike(node.arguments[0])) {
|
|
154
|
-
found.push({ node: node.arguments[0], specifier: node.arguments[0].text, runtime: true, declaration: node });
|
|
155
|
-
} else unproven.push({ node, kind: 'dynamic require()' });
|
|
156
|
-
}
|
|
194
|
+
if (!staticModuleReference(ts, node, found, unproven)) callModuleReference(ts, sourceFile, checker, node, found, unproven);
|
|
157
195
|
ts.forEachChild(node, visit);
|
|
158
196
|
};
|
|
159
197
|
visit(sourceFile);
|
|
@@ -205,27 +243,29 @@ export function exportTargetStrings(value) {
|
|
|
205
243
|
}
|
|
206
244
|
|
|
207
245
|
/** The package-relative export targets a request (`.` or `./sub`) of a workspace package declares, from exports, else types/main. */
|
|
246
|
+
function rootExportCandidates(declaration, request) {
|
|
247
|
+
if (request !== '.') return [];
|
|
248
|
+
return exportTargetStrings(declaration);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function exportMapCandidates(declaration, request) {
|
|
252
|
+
const keys = Object.keys(declaration);
|
|
253
|
+
if (!keys.some(key => key.startsWith('.'))) return rootExportCandidates(declaration, request);
|
|
254
|
+
const candidates = [];
|
|
255
|
+
for (const key of keys) {
|
|
256
|
+
const capture = exportPatternCapture(key, request);
|
|
257
|
+
if (capture !== null) candidates.push(...exportTargetStrings(declaration[key]).map(target => target.replaceAll('*', capture)));
|
|
258
|
+
}
|
|
259
|
+
return candidates;
|
|
260
|
+
}
|
|
261
|
+
|
|
208
262
|
function exportCandidates(workspace, specifier) {
|
|
209
263
|
if (!workspace.name || !sameOrUnder(specifier, workspace.name)) return [];
|
|
210
264
|
const request = specifier === workspace.name ? '.' : `.${specifier.slice(workspace.name.length)}`;
|
|
211
265
|
const declaration = workspace.exports;
|
|
212
|
-
|
|
213
|
-
if (typeof declaration === '
|
|
214
|
-
|
|
215
|
-
candidates = exportTargetStrings(declaration);
|
|
216
|
-
} else if (declaration && typeof declaration === 'object') {
|
|
217
|
-
const keys = Object.keys(declaration);
|
|
218
|
-
if (!keys.some(key => key.startsWith('.'))) {
|
|
219
|
-
if (request !== '.') return [];
|
|
220
|
-
candidates = exportTargetStrings(declaration);
|
|
221
|
-
} else {
|
|
222
|
-
for (const key of keys) {
|
|
223
|
-
const capture = exportPatternCapture(key, request);
|
|
224
|
-
if (capture !== null) candidates.push(...exportTargetStrings(declaration[key]).map(target => target.replaceAll('*', capture)));
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
return candidates;
|
|
266
|
+
if (typeof declaration === 'string' || Array.isArray(declaration)) return rootExportCandidates(declaration, request);
|
|
267
|
+
if (declaration && typeof declaration === 'object') return exportMapCandidates(declaration, request);
|
|
268
|
+
return [];
|
|
229
269
|
}
|
|
230
270
|
|
|
231
271
|
const SOURCE_EXTENSIONS = { '.d.ts': ['.ts', '.tsx'], '.d.mts': ['.mts'], '.d.cts': ['.cts'], '.js': ['.ts', '.tsx'], '.jsx': ['.tsx'], '.mjs': ['.mts'], '.cjs': ['.cts'] };
|
|
@@ -262,20 +302,24 @@ function sourceOfTarget(ts, workspace, target) {
|
|
|
262
302
|
return null;
|
|
263
303
|
}
|
|
264
304
|
|
|
305
|
+
const manifestEntryTargets = manifest => [manifest?.types, manifest?.typings, manifest?.module, manifest?.main].filter(item => typeof item === 'string');
|
|
306
|
+
|
|
307
|
+
const packageIndexSource = workspace => {
|
|
308
|
+
for (const name of ['index.ts', 'index.tsx']) if (fs.existsSync(path.join(workspace.root, 'src', name))) return canonical(path.join(workspace.root, 'src', name));
|
|
309
|
+
return null;
|
|
310
|
+
};
|
|
311
|
+
|
|
265
312
|
/** The source entry a workspace import resolves to without a build: its export targets mapped back to source, `src/index.ts` for the package root. */
|
|
266
313
|
function workspaceSourceEntry(ts, workspace, specifier) {
|
|
267
314
|
if (!workspace.workspace || !workspace.name || !sameOrUnder(specifier, workspace.name)) return null;
|
|
268
|
-
|
|
269
|
-
|
|
315
|
+
const isRoot = specifier === workspace.name;
|
|
316
|
+
const candidates = isRoot && !workspace.exports ? manifestEntryTargets(workspace.manifest) : exportCandidates(workspace, specifier);
|
|
270
317
|
for (const target of candidates) {
|
|
271
318
|
if (!target.startsWith('./') || target.includes('..')) continue;
|
|
272
319
|
const source = sourceOfTarget(ts, workspace, target);
|
|
273
320
|
if (source) return source;
|
|
274
321
|
}
|
|
275
|
-
|
|
276
|
-
for (const name of ['index.ts', 'index.tsx']) if (fs.existsSync(path.join(workspace.root, 'src', name))) return canonical(path.join(workspace.root, 'src', name));
|
|
277
|
-
}
|
|
278
|
-
return null;
|
|
322
|
+
return isRoot ? packageIndexSource(workspace) : null;
|
|
279
323
|
}
|
|
280
324
|
|
|
281
325
|
/** Every source file the package.json `exports` of a workspace package maps (each non-wildcard subpath, dist back to source): its public entries. */
|
|
@@ -328,216 +372,13 @@ export function buildTypeScriptContext(config, injectedTypeScript, paths = []) {
|
|
|
328
372
|
}
|
|
329
373
|
|
|
330
374
|
function typeScriptContext(config, loaded, paths) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const queue = [...config.projects];
|
|
337
|
-
const seenProjects = new Set();
|
|
338
|
-
while (queue.length) {
|
|
339
|
-
const relative = queue.shift();
|
|
340
|
-
if (seenProjects.has(relative)) continue;
|
|
341
|
-
seenProjects.add(relative);
|
|
342
|
-
const configFile = path.join(config.root, ...relative.split('/'));
|
|
343
|
-
if (!fs.existsSync(configFile)) {
|
|
344
|
-
errors.push({ ruleId: 'ARCH_TSCONFIG_MISSING', project: relative, message: `${relative} does not exist.` });
|
|
345
|
-
invalidProjects.add(relative);
|
|
346
|
-
continue;
|
|
347
|
-
}
|
|
348
|
-
const { read, parsed } = readTypeScriptProject(ts, configFile);
|
|
349
|
-
if (read.error) {
|
|
350
|
-
errors.push(compilerError(ts, config.root, read.error, relative));
|
|
351
|
-
invalidProjects.add(relative);
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
if (parsed.errors.length) {
|
|
355
|
-
errors.push(...parsed.errors.map(item => compilerError(ts, config.root, item, relative)));
|
|
356
|
-
invalidProjects.add(relative);
|
|
357
|
-
continue;
|
|
358
|
-
}
|
|
359
|
-
for (const reference of parsed.projectReferences ?? []) {
|
|
360
|
-
const referencedFile = typeScriptProjectReferencePath(ts, reference);
|
|
361
|
-
const absoluteReference = path.resolve(referencedFile);
|
|
362
|
-
if (!isInside(config.root, absoluteReference)) {
|
|
363
|
-
errors.push({ ruleId: 'ARCH_TSCONFIG_REFERENCE_OUTSIDE', project: relative, message: `Project reference leaves the repository: ${slash(path.relative(config.root, absoluteReference))}.` });
|
|
364
|
-
} else {
|
|
365
|
-
queue.push(slash(path.relative(config.root, absoluteReference)));
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
parsedProjects.set(relative, parsed);
|
|
369
|
-
}
|
|
370
|
-
const matches = file => paths.some(prefix => {
|
|
371
|
-
const relative = slash(path.relative(config.root, file));
|
|
372
|
-
const normalized = slash(prefix).replace(/\/$/, '');
|
|
373
|
-
return sameOrUnder(relative, normalized);
|
|
375
|
+
return contextFromProjects(config, loaded, paths, {
|
|
376
|
+
canonical, compilerError, isInside, slash, readTypeScriptProject, typeScriptProjectReferencePath,
|
|
377
|
+
sameOrUnder, createTypeScriptProgram, isProductionSource, sourceLocation, workspaceMetadata, workspaceOf,
|
|
378
|
+
pathAliasMatches, moduleReferences, workspaceSourceEntry, resolveTypeScriptModule, crossesSide, assetExtension: ASSET_EXTENSION,
|
|
379
|
+
relativePath, packageExported, boundaryViolation,
|
|
374
380
|
});
|
|
375
|
-
const selected = new Set();
|
|
376
|
-
const direct = new Set();
|
|
377
|
-
if (paths.length) {
|
|
378
|
-
// TypeScript follows imports from these root files. Referenced projects need their declared
|
|
379
|
-
// roots as well, since a project reference need not be an import in the selected source.
|
|
380
|
-
const visit = relative => {
|
|
381
|
-
if (selected.has(relative)) return;
|
|
382
|
-
selected.add(relative);
|
|
383
|
-
for (const reference of parsedProjects.get(relative)?.projectReferences ?? []) {
|
|
384
|
-
const referencedFile = typeScriptProjectReferencePath(ts, reference);
|
|
385
|
-
const referenced = slash(path.relative(config.root, path.resolve(referencedFile)));
|
|
386
|
-
if (parsedProjects.has(referenced)) visit(referenced);
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
for (const [relative, parsed] of parsedProjects) if (parsed.fileNames.some(matches)) {
|
|
390
|
-
direct.add(relative);
|
|
391
|
-
visit(relative);
|
|
392
|
-
}
|
|
393
|
-
for (const relative of invalidProjects) {
|
|
394
|
-
const directory = path.dirname(path.join(config.root, ...relative.split('/')));
|
|
395
|
-
if (paths.some(prefix => {
|
|
396
|
-
const absolute = path.join(config.root, ...slash(prefix).split('/'));
|
|
397
|
-
return isInside(directory, absolute);
|
|
398
|
-
})) selected.add(relative);
|
|
399
|
-
}
|
|
400
|
-
errors.splice(0, errors.length, ...errors.filter(error => !error.project || selected.has(error.project)));
|
|
401
|
-
}
|
|
402
|
-
for (const [relative, parsed] of parsedProjects) {
|
|
403
|
-
if (paths.length && !selected.has(relative)) continue;
|
|
404
|
-
const rootNames = paths.length && direct.has(relative) ? parsed.fileNames.filter(matches) : parsed.fileNames;
|
|
405
|
-
const program = createTypeScriptProgram(ts, { rootNames, options: parsed.options, projectReferences: parsed.projectReferences });
|
|
406
|
-
errors.push(...program.getSyntacticDiagnostics().filter(item => !item.file || isInside(config.root, item.file.fileName))
|
|
407
|
-
.map(item => compilerError(ts, config.root, item, relative, 'ARCH_SYNTAX_INVALID')));
|
|
408
|
-
projects.push({ relative, program, options: parsed.options });
|
|
409
|
-
}
|
|
410
|
-
const occurrences = new Map();
|
|
411
|
-
const sourceIn = new Map();
|
|
412
|
-
for (const project of projects) {
|
|
413
|
-
for (const sourceFile of project.program.getSourceFiles().filter(file => isProductionSource(config.root, file))) {
|
|
414
|
-
const name = canonical(sourceFile.fileName);
|
|
415
|
-
if (!occurrences.has(name)) occurrences.set(name, []);
|
|
416
|
-
occurrences.get(name).push(project);
|
|
417
|
-
sourceIn.set(`${project.relative}|${name}`, sourceFile);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
// The deepest project directory holding a file owns it: its source file, its checker and its resolution. A side or repository
|
|
421
|
-
// root tsconfig without paths must not shadow the app project whose aliases (`@/*`) the file actually uses.
|
|
422
|
-
const owningProject = (candidates, file) => candidates.filter(item => isInside(path.dirname(path.join(config.root, ...item.relative.split('/'))), file))
|
|
423
|
-
.sort((x, y) => y.relative.split('/').length - x.relative.split('/').length)[0] ?? candidates[0];
|
|
424
|
-
const fileMap = new Map();
|
|
425
|
-
const checkerByFile = new Map();
|
|
426
|
-
const ownerByFile = new Map();
|
|
427
|
-
for (const [name, candidates] of occurrences) {
|
|
428
|
-
const owner = owningProject(candidates, name);
|
|
429
|
-
ownerByFile.set(name, owner);
|
|
430
|
-
fileMap.set(name, sourceIn.get(`${owner.relative}|${name}`));
|
|
431
|
-
checkerByFile.set(name, owner.program.getTypeChecker());
|
|
432
|
-
}
|
|
433
|
-
const files = [...fileMap.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([, file]) => file);
|
|
434
|
-
const edges = new Map([...fileMap.keys()].map(file => [file, []]));
|
|
435
|
-
for (const [file, sourceFile] of fileMap) {
|
|
436
|
-
const sourceName = path.resolve(sourceFile.fileName);
|
|
437
|
-
if (sourceName !== file) edges.set(sourceName, edges.get(file));
|
|
438
|
-
}
|
|
439
|
-
const edgeKeys = new Set();
|
|
440
|
-
const host = { ...ts.sys, fileExists: ts.sys.fileExists, readFile: ts.sys.readFile, realpath: ts.sys.realpath };
|
|
441
|
-
const workspaces = workspaceMetadata(config);
|
|
442
|
-
const workspaceNames = new Set(workspaces.map(item => item.name).filter(Boolean));
|
|
443
|
-
for (const [from, sourceFile] of fileMap) {
|
|
444
|
-
const owningWorkspace = workspaceOf(workspaces, from);
|
|
445
|
-
const project = ownerByFile.get(from);
|
|
446
|
-
if (!project) continue;
|
|
447
|
-
const references = moduleReferences(ts, sourceFile, project.program.getTypeChecker());
|
|
448
|
-
for (const item of references.unproven) errors.push({
|
|
449
|
-
ruleId: 'ARCH_DYNAMIC_DEPENDENCY_UNPROVEN',
|
|
450
|
-
project: project.relative,
|
|
451
|
-
path: relativePath(config.root, sourceFile.fileName),
|
|
452
|
-
...sourceLocation(sourceFile, item.node),
|
|
453
|
-
message: `${item.kind} must use a string-literal module name so architecture coverage can resolve its dependency.`,
|
|
454
|
-
});
|
|
455
|
-
for (const reference of references.found) {
|
|
456
|
-
// A workspace package is read at its source, never at its build: the same import resolves whether or not dist exists.
|
|
457
|
-
const workspaceTarget = workspaces.filter(item => item.root !== canonical(config.root)).map(item => workspaceSourceEntry(ts, item, reference.specifier)).find(Boolean);
|
|
458
|
-
const resolvedName = workspaceTarget ?? resolveTypeScriptModule(ts, reference.specifier, sourceFile.fileName, project.options, host);
|
|
459
|
-
if (!resolvedName) {
|
|
460
|
-
const codeLike = !ASSET_EXTENSION.test(reference.specifier);
|
|
461
|
-
const workspaceImport = [...workspaceNames].some(name => sameOrUnder(reference.specifier, name));
|
|
462
|
-
const internal = reference.specifier.startsWith('.') || pathAliasMatches(reference.specifier, project.options.paths) || workspaceImport;
|
|
463
|
-
if (codeLike && internal) {
|
|
464
|
-
errors.push({
|
|
465
|
-
ruleId: 'ARCH_INTERNAL_IMPORT_UNRESOLVED',
|
|
466
|
-
project: project.relative,
|
|
467
|
-
path: relativePath(config.root, sourceFile.fileName),
|
|
468
|
-
...sourceLocation(sourceFile, reference.node),
|
|
469
|
-
specifier: reference.specifier,
|
|
470
|
-
message: `Internal import ${reference.specifier} is not resolvable with ${project.relative}.`,
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
continue;
|
|
474
|
-
}
|
|
475
|
-
const actualTarget = canonical(resolvedName);
|
|
476
|
-
const workspaceImport = [...workspaceNames].some(name => reference.specifier === name || reference.specifier.startsWith(`${name}/`));
|
|
477
|
-
const internal = reference.specifier.startsWith('.') || pathAliasMatches(reference.specifier, project.options.paths) || workspaceImport;
|
|
478
|
-
// The boundary is the checkout, not the checked project: a path alias that lands in a sibling
|
|
479
|
-
// package of the same repository (`@fe-kit/*` -> packages/fe-kit) is still source a reviewer can
|
|
480
|
-
// open, while anything past the repository, or anything inside an installed dependency tree, is
|
|
481
|
-
// not. `config.repository` is null when there is no git checkout around the project, and the
|
|
482
|
-
// project is then the boundary it always was.
|
|
483
|
-
const reviewable = isInside(config.root, actualTarget)
|
|
484
|
-
|| (config.repository && isInside(config.repository, actualTarget) && !slash(actualTarget).includes('/node_modules/'));
|
|
485
|
-
// A side of an app (config.root is be/ or fe/) imports nothing of the app outside itself: the root holds no source, the other
|
|
486
|
-
// side is another program, and a declared read (sides.fe.reads, be/contracts/) is codegen input, never an import.
|
|
487
|
-
if (internal && crossesSide(config, actualTarget)) {
|
|
488
|
-
errors.push({
|
|
489
|
-
ruleId: 'ARCH_INTERNAL_IMPORT_OUTSIDE',
|
|
490
|
-
project: project.relative,
|
|
491
|
-
path: relativePath(config.root, sourceFile.fileName),
|
|
492
|
-
...sourceLocation(sourceFile, reference.node),
|
|
493
|
-
specifier: reference.specifier,
|
|
494
|
-
message: `Internal import ${reference.specifier} leaves the ${path.basename(config.root)} side for ${slash(path.relative(config.packageRoot, actualTarget))}; nothing crosses the sides of an app.`,
|
|
495
|
-
});
|
|
496
|
-
continue;
|
|
497
|
-
}
|
|
498
|
-
if (internal && !reviewable) {
|
|
499
|
-
errors.push({
|
|
500
|
-
ruleId: 'ARCH_INTERNAL_IMPORT_OUTSIDE',
|
|
501
|
-
project: project.relative,
|
|
502
|
-
path: relativePath(config.root, sourceFile.fileName),
|
|
503
|
-
...sourceLocation(sourceFile, reference.node),
|
|
504
|
-
specifier: reference.specifier,
|
|
505
|
-
message: `Internal import ${reference.specifier} resolves outside the checked repository.`,
|
|
506
|
-
});
|
|
507
|
-
continue;
|
|
508
|
-
}
|
|
509
|
-
if (!fileMap.has(actualTarget)) continue;
|
|
510
|
-
const edge = {
|
|
511
|
-
from,
|
|
512
|
-
to: actualTarget,
|
|
513
|
-
runtime: reference.runtime,
|
|
514
|
-
specifier: reference.specifier,
|
|
515
|
-
node: reference.node,
|
|
516
|
-
declaration: reference.declaration,
|
|
517
|
-
reexport: ts.isExportDeclaration(reference.declaration),
|
|
518
|
-
sourceFile,
|
|
519
|
-
project: project.relative,
|
|
520
|
-
...sourceLocation(sourceFile, reference.node),
|
|
521
|
-
};
|
|
522
|
-
const key = `${from}\0${actualTarget}\0${reference.node.getStart(sourceFile)}\0${reference.specifier}`;
|
|
523
|
-
if (!edgeKeys.has(key)) { edges.get(from).push(edge); edgeKeys.add(key); }
|
|
524
|
-
const targetWorkspace = workspaceOf(workspaces, actualTarget);
|
|
525
|
-
if (owningWorkspace && targetWorkspace && owningWorkspace !== targetWorkspace) {
|
|
526
|
-
if (!owningWorkspace.app && targetWorkspace.app) {
|
|
527
|
-
errors.push(boundaryViolation(config.root, edge, owningWorkspace, targetWorkspace, 'ARCH_PACKAGE_IMPORTS_APP', 'A reusable workspace package cannot depend on an application workspace.'));
|
|
528
|
-
}
|
|
529
|
-
if (!packageExported(ts, targetWorkspace, reference.specifier, actualTarget)) {
|
|
530
|
-
errors.push(boundaryViolation(config.root, edge, owningWorkspace, targetWorkspace, 'ARCH_PACKAGE_EXPORT_BYPASS', 'Cross-package imports must use the target package name and a declared package export.'));
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
if (projects.length && files.length === 0) errors.push({ ruleId: 'ARCH_NO_SOURCE', message: 'The configured TypeScript projects contain no production TypeScript or JavaScript source.' });
|
|
536
|
-
return { loaded, errors, files, edges, programs: projects.map(item => item.program), program: projects[0]?.program ?? null, projects, ts, workspaces,
|
|
537
|
-
checkerFor: file => checkerByFile.get(canonical(file)) ?? null,
|
|
538
|
-
workspaceOf: file => workspaceOf(workspaces, canonical(file)) };
|
|
539
381
|
}
|
|
540
|
-
|
|
541
382
|
export function relativePath(root, fileName) {
|
|
542
383
|
return slash(path.relative(root, fileName));
|
|
543
384
|
}
|
|
@@ -581,4 +422,3 @@ export function referencedExports(ts, statement, expected) {
|
|
|
581
422
|
}
|
|
582
423
|
return [...expected];
|
|
583
424
|
}
|
|
584
|
-
|