@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.
Files changed (275) hide show
  1. package/CHANGELOG.md +5 -1
  2. package/emit/operations.mjs +1 -1
  3. package/emit/type-schema.mjs +2 -1
  4. package/lint/run.mjs +65 -24
  5. package/package.json +1 -1
  6. package/report/order.mjs +2 -0
  7. package/runtime/config.example.yaml +194 -0
  8. package/runtime/engine/by-code-unit.mjs +12 -0
  9. package/runtime/engine/config.mjs +222 -239
  10. package/runtime/engine/invalid-config.mjs +5 -5
  11. package/runtime/engine/model-config.mjs +90 -0
  12. package/runtime/engine/orca-config.mjs +3 -1
  13. package/runtime/engine/release-config.mjs +28 -0
  14. package/runtime/engine/removed-vocabulary.mjs +28 -0
  15. package/runtime/engine/resources-config.mjs +52 -0
  16. package/runtime/engine/runtime-root.mjs +17 -0
  17. package/runtime/engine/secrets.mjs +35 -31
  18. package/runtime/engine/sonar-config.mjs +21 -0
  19. package/runtime/engine/temp-root.mjs +32 -0
  20. package/runtime/knowledge/hfs/canon-pins.yaml +14 -14
  21. package/runtime/knowledge/hfs/peer-integrations.yaml +2 -3
  22. package/runtime/knowledge/hfs/rules.yaml +161 -65
  23. package/runtime/knowledge/hfs/slots.yaml +11 -14
  24. package/runtime/knowledge/patterns/be/api.yaml +7 -24
  25. package/runtime/knowledge/patterns/be/cli.yaml +3 -12
  26. package/runtime/knowledge/patterns/be/realtime.yaml +1 -14
  27. package/runtime/knowledge/patterns/be/webhooks.yaml +0 -19
  28. package/runtime/modules/kernel/failure-codes.yaml +1 -41
  29. package/runtime/modules/kernel/removed-vocabulary.yaml +268 -0
  30. package/runtime/modules/models/registry.yaml +25 -359
  31. package/runtime/modules/models/runtimes.yaml +126 -265
  32. package/runtime/modules/models/tiers.yaml +66 -0
  33. package/runtime/scripts/api/fs/claim-file.mjs +30 -0
  34. package/runtime/scripts/api/fs/ensure-temp-root.mjs +27 -0
  35. package/runtime/scripts/api/fs/forbidden-root.mjs +2 -1
  36. package/runtime/scripts/api/fs/make-temp-dir.mjs +11 -0
  37. package/runtime/scripts/api/fs/safe-remove.mjs +52 -35
  38. package/runtime/scripts/api/git/lib.mjs +34 -1
  39. package/runtime/scripts/api/process/resolve-real-tool.mjs +14 -8
  40. package/runtime/scripts/api/process/run-program.mjs +2 -1
  41. package/runtime/scripts/api/sops/lib.mjs +198 -120
  42. package/runtime/scripts/hfs/allows.mjs +3 -3
  43. package/runtime/scripts/hfs/architecture/ast-walks.mjs +63 -50
  44. package/runtime/scripts/hfs/architecture/automatic-gates.mjs +96 -0
  45. package/runtime/scripts/hfs/architecture/backend.mjs +261 -175
  46. package/runtime/scripts/hfs/architecture/background-unowned.mjs +69 -34
  47. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +55 -54
  48. package/runtime/scripts/hfs/architecture/clones.mjs +179 -106
  49. package/runtime/scripts/hfs/architecture/config-unread.mjs +5 -22
  50. package/runtime/scripts/hfs/architecture/config.mjs +95 -80
  51. package/runtime/scripts/hfs/architecture/connection-map.mjs +233 -141
  52. package/runtime/scripts/hfs/architecture/constructor-deps.mjs +15 -9
  53. package/runtime/scripts/hfs/architecture/context-coupling.mjs +76 -52
  54. package/runtime/scripts/hfs/architecture/context-map.mjs +215 -141
  55. package/runtime/scripts/hfs/architecture/context-owner.mjs +39 -30
  56. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +31 -17
  57. package/runtime/scripts/hfs/architecture/context-transaction.mjs +38 -25
  58. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +86 -40
  59. package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +342 -0
  60. package/runtime/scripts/hfs/architecture/contracts.mjs +263 -455
  61. package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +43 -34
  62. package/runtime/scripts/hfs/architecture/dead-exports.mjs +201 -154
  63. package/runtime/scripts/hfs/architecture/default-deny.mjs +118 -93
  64. package/runtime/scripts/hfs/architecture/doc-language.mjs +2 -1
  65. package/runtime/scripts/hfs/architecture/entrypoint.mjs +45 -30
  66. package/runtime/scripts/hfs/architecture/error-codes.mjs +19 -12
  67. package/runtime/scripts/hfs/architecture/error-masked.mjs +39 -26
  68. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +2 -1
  69. package/runtime/scripts/hfs/architecture/feature-shape.mjs +26 -20
  70. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  71. package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +190 -0
  72. package/runtime/scripts/hfs/architecture/frontend-routing.mjs +186 -0
  73. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +311 -0
  74. package/runtime/scripts/hfs/architecture/frontend-world.mjs +328 -0
  75. package/runtime/scripts/hfs/architecture/frontend.mjs +109 -829
  76. package/runtime/scripts/hfs/architecture/hfs-graph.mjs +14 -2
  77. package/runtime/scripts/hfs/architecture/hfs.mjs +120 -288
  78. package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +46 -26
  79. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +114 -76
  80. package/runtime/scripts/hfs/architecture/index.mjs +164 -104
  81. package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +33 -28
  82. package/runtime/scripts/hfs/architecture/machine-ast.mjs +180 -144
  83. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +8 -2
  84. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +150 -101
  85. package/runtime/scripts/hfs/architecture/next-data-contract.mjs +51 -43
  86. package/runtime/scripts/hfs/architecture/next-data-key.mjs +338 -0
  87. package/runtime/scripts/hfs/architecture/next-data.mjs +179 -427
  88. package/runtime/scripts/hfs/architecture/owners.mjs +27 -13
  89. package/runtime/scripts/hfs/architecture/package-shape.mjs +51 -23
  90. package/runtime/scripts/hfs/architecture/presentation.mjs +165 -0
  91. package/runtime/scripts/hfs/architecture/reachability.mjs +127 -61
  92. package/runtime/scripts/hfs/architecture/register-once.mjs +112 -63
  93. package/runtime/scripts/hfs/architecture/registration.mjs +187 -110
  94. package/runtime/scripts/hfs/architecture/required-files.mjs +121 -70
  95. package/runtime/scripts/hfs/architecture/route-files-thin.mjs +83 -58
  96. package/runtime/scripts/hfs/architecture/schema-owner.mjs +182 -108
  97. package/runtime/scripts/hfs/architecture/source-names-shape.mjs +318 -0
  98. package/runtime/scripts/hfs/architecture/source-names.mjs +83 -248
  99. package/runtime/scripts/hfs/architecture/sql-owner.mjs +175 -118
  100. package/runtime/scripts/hfs/architecture/sql-returning.mjs +39 -28
  101. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +302 -172
  102. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +1 -1
  103. package/runtime/scripts/hfs/architecture/supabase-be.mjs +55 -45
  104. package/runtime/scripts/hfs/architecture/supabase-results.mjs +182 -0
  105. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +20 -12
  106. package/runtime/scripts/hfs/architecture/supabase.mjs +191 -263
  107. package/runtime/scripts/hfs/architecture/symbols.mjs +175 -99
  108. package/runtime/scripts/hfs/architecture/test-world-files.mjs +82 -41
  109. package/runtime/scripts/hfs/architecture/tiers.mjs +120 -69
  110. package/runtime/scripts/hfs/architecture/transport-owner.mjs +84 -57
  111. package/runtime/scripts/hfs/architecture/type-context.mjs +282 -0
  112. package/runtime/scripts/hfs/architecture/typescript.mjs +116 -276
  113. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +84 -62
  114. package/runtime/scripts/hfs/architecture.mjs +3 -4
  115. package/runtime/scripts/hfs/catalog-once.mjs +15 -0
  116. package/runtime/scripts/hfs/check.mjs +63 -71
  117. package/runtime/scripts/hfs/coverage-scope.mjs +84 -38
  118. package/runtime/scripts/hfs/declaration-shape.mjs +77 -36
  119. package/runtime/scripts/hfs/declaration-slots.mjs +1 -1
  120. package/runtime/scripts/hfs/edition-slots.mjs +5 -3
  121. package/runtime/scripts/hfs/linear-text.mjs +30 -0
  122. package/runtime/scripts/hfs/manifest-shape.mjs +164 -63
  123. package/runtime/scripts/hfs/path-findings.mjs +58 -38
  124. package/runtime/scripts/hfs/pin-findings.mjs +31 -0
  125. package/runtime/scripts/hfs/repo-identity.mjs +21 -4
  126. package/runtime/scripts/hfs/rule-catalog.mjs +194 -0
  127. package/runtime/scripts/hfs/rule-params-shape.mjs +33 -27
  128. package/runtime/scripts/hfs/rules/cli.mjs +5 -1
  129. package/runtime/scripts/hfs/rules/contract-compat.mjs +40 -22
  130. package/runtime/scripts/hfs/rules/contract.mjs +35 -27
  131. package/runtime/scripts/hfs/rules/database-config.mjs +57 -32
  132. package/runtime/scripts/hfs/rules/database-migrations.mjs +52 -31
  133. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +106 -91
  134. package/runtime/scripts/hfs/rules/database-sql.mjs +254 -173
  135. package/runtime/scripts/hfs/rules/database.mjs +36 -23
  136. package/runtime/scripts/hfs/rules/deps.mjs +64 -32
  137. package/runtime/scripts/hfs/rules/docker.mjs +113 -65
  138. package/runtime/scripts/hfs/rules/edition.mjs +118 -111
  139. package/runtime/scripts/hfs/rules/event-bus.mjs +91 -45
  140. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +17 -16
  141. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +36 -16
  142. package/runtime/scripts/hfs/rules/frontend-tree.mjs +4 -3
  143. package/runtime/scripts/hfs/rules/integration-specs.mjs +75 -41
  144. package/runtime/scripts/hfs/rules/kinds.mjs +59 -25
  145. package/runtime/scripts/hfs/rules/lint-suppression.mjs +9 -3
  146. package/runtime/scripts/hfs/rules/monorepo.mjs +82 -45
  147. package/runtime/scripts/hfs/rules/peer-integrations.mjs +24 -3
  148. package/runtime/scripts/hfs/rules/pipeline.mjs +58 -9
  149. package/runtime/scripts/hfs/rules/proof-commands.mjs +14 -8
  150. package/runtime/scripts/hfs/rules/repo-local-checks.mjs +11 -6
  151. package/runtime/scripts/hfs/rules/saga.mjs +128 -61
  152. package/runtime/scripts/hfs/rules/secrets.mjs +4 -3
  153. package/runtime/scripts/hfs/rules/services.mjs +34 -16
  154. package/runtime/scripts/hfs/rules/stacks.mjs +21 -14
  155. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +32 -23
  156. package/runtime/scripts/hfs/rules/test-topology.mjs +25 -11
  157. package/runtime/scripts/hfs/secret.mjs +59 -48
  158. package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
  159. package/runtime/scripts/hfs/slot-classify.mjs +65 -0
  160. package/runtime/scripts/hfs/slot-errors.mjs +11 -0
  161. package/runtime/scripts/hfs/slot-imports.mjs +76 -0
  162. package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
  163. package/runtime/scripts/hfs/slot-match.mjs +153 -0
  164. package/runtime/scripts/hfs/slot-path.mjs +8 -0
  165. package/runtime/scripts/hfs/slot-required.mjs +76 -0
  166. package/runtime/scripts/hfs/slot-semantic-problems.mjs +148 -0
  167. package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
  168. package/runtime/scripts/hfs/slots.mjs +74 -695
  169. package/runtime/scripts/hfs/sql/pg-parse.mjs +5 -2
  170. package/runtime/scripts/hfs/trailing-slashes.mjs +8 -0
  171. package/runtime/scripts/hfs/tree.mjs +19 -14
  172. package/runtime/scripts/hfs/typescript-programs.mjs +6 -4
  173. package/runtime/scripts/hfs/view.mjs +1 -1
  174. package/runtime/scripts/lib/dockerfile.mjs +56 -33
  175. package/runtime/scripts/lib/env.mjs +10 -0
  176. package/runtime/scripts/lib/event-contract.mjs +17 -13
  177. package/runtime/scripts/lib/git.mjs +2 -2
  178. package/runtime/scripts/lib/graphql-contract.mjs +158 -322
  179. package/runtime/scripts/lib/graphql-sdl.mjs +262 -0
  180. package/runtime/scripts/lib/i18n.mjs +3 -2
  181. package/runtime/scripts/lib/in-order.mjs +71 -0
  182. package/runtime/scripts/lib/language.mjs +3 -3
  183. package/runtime/scripts/lib/list.mjs +8 -1
  184. package/runtime/scripts/lib/path-key.mjs +38 -5
  185. package/runtime/scripts/lib/pid-alive.mjs +7 -0
  186. package/runtime/scripts/lib/regex.mjs +1 -1
  187. package/runtime/scripts/lib/same-text.mjs +1 -1
  188. package/runtime/scripts/lib/secret-patterns.mjs +6 -2
  189. package/runtime/scripts/lib/sleep-sync.mjs +2 -2
  190. package/runtime/scripts/lib/sops-envelope.mjs +95 -48
  191. package/runtime/scripts/lib/stack-services.mjs +1 -2
  192. package/runtime/scripts/lib/ts-ast.mjs +9 -0
  193. package/runtime/scripts/lib/walk.mjs +6 -1
  194. package/runtime/scripts/lib/yaml-cached.mjs +14 -0
  195. package/scaffold/add-cli-lite.mjs +2 -1
  196. package/scaffold/add-table.mjs +2 -2
  197. package/scaffold/add.mjs +3 -2
  198. package/scaffold/app.mjs +4 -2
  199. package/scaffold/edition-gate.mjs +25 -25
  200. package/scaffold/lite-exports.mjs +2 -1
  201. package/scaffold/service.mjs +5 -4
  202. package/sync/hygiene.mjs +15 -9
  203. package/sync/index.mjs +7 -3
  204. package/templates/app/ci-workflows/github/workflows/ci.yml +4 -4
  205. package/templates/app/ci-workflows/github/workflows/e2e.yml +3 -3
  206. package/templates/app/ci-workflows/github/workflows/images.yml +3 -3
  207. package/templates/app/ci-workflows/sonar-steps.yml +2 -2
  208. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +6 -6
  209. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +3 -3
  210. package/templates/app/ci-workflows-lite/github/workflows/images.yml +3 -3
  211. package/templates/app/starciwork.gitignore +1 -1
  212. package/templates/be/image/api/Dockerfile +1 -1
  213. package/templates/be/image/cli/Dockerfile +1 -1
  214. package/templates/be/image/worker/Dockerfile +1 -1
  215. package/templates/be/patterns/cli/group.cli.spec.ts.tpl +1 -1
  216. package/templates/be/patterns/cli/group.cli.ts.tpl +2 -2
  217. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +13 -0
  218. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +7 -2
  219. package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +4 -1
  220. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +2 -1
  221. package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +24 -9
  222. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +3 -2
  223. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +8 -5
  224. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +1 -1
  225. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +2 -2
  226. package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +1 -1
  227. package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +2 -2
  228. package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +3 -2
  229. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +5 -4
  230. package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
  231. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +47 -0
  232. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.ts +16 -0
  233. package/templates/be/skeleton-lite/apps/api/Dockerfile +1 -0
  234. package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
  235. package/templates/fe/image/next/Dockerfile +2 -1
  236. package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +2 -2
  237. package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +2 -2
  238. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +1 -2
  239. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +1 -2
  240. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +2 -2
  241. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +7 -1
  242. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +1 -1
  243. package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +10 -2
  244. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +10 -3
  245. package/upgrade/index.mjs +9 -2
  246. package/runtime/engine/admission.mjs +0 -308
  247. package/runtime/engine/canonical-json.mjs +0 -10
  248. package/runtime/engine/db/blob.mjs +0 -315
  249. package/runtime/engine/db/ledger-paths.mjs +0 -83
  250. package/runtime/engine/db/ledger.mjs +0 -1118
  251. package/runtime/engine/db/machine-connection.mjs +0 -135
  252. package/runtime/engine/db/machine-schema.mjs +0 -84
  253. package/runtime/engine/db/machine.mjs +0 -1367
  254. package/runtime/engine/db/migrations/machine/0001-init.sql +0 -924
  255. package/runtime/engine/db/migrations/runtime/0001-init.sql +0 -1108
  256. package/runtime/engine/db/provider-reservations.mjs +0 -101
  257. package/runtime/engine/digest.mjs +0 -16
  258. package/runtime/engine/refuse.mjs +0 -11
  259. package/runtime/modules/ops/_labels.yaml +0 -53
  260. package/runtime/scripts/api/node/lib.mjs +0 -14
  261. package/runtime/scripts/api/node/spawn-node.mjs +0 -6
  262. package/runtime/scripts/api/process/lib.mjs +0 -111
  263. package/runtime/scripts/api/process/owned-process.mjs +0 -78
  264. package/runtime/scripts/api/process/stop-owned-process.mjs +0 -9
  265. package/runtime/scripts/connectors/lib.mjs +0 -488
  266. package/runtime/scripts/lib/clip.mjs +0 -19
  267. package/runtime/scripts/lib/display-names.mjs +0 -259
  268. package/runtime/scripts/lib/example-refs.mjs +0 -158
  269. package/runtime/scripts/lib/json-schema.mjs +0 -52
  270. package/runtime/scripts/lib/process-identity.mjs +0 -6
  271. package/runtime/scripts/lib/read-yaml.mjs +0 -18
  272. package/runtime/scripts/lib/redact.mjs +0 -161
  273. package/runtime/scripts/lib/sleep.mjs +0 -7
  274. package/runtime/scripts/lib/source-phrases.mjs +0 -34
  275. 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 TEST_FILE = /(?:^|[.-])(?:spec|test)\.[cm]?[jt]sx?$/i;
14
- const ASSET_EXTENSION = /\.(?:css|scss|sass|less|svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|eot|ya?ml|json)$/i;
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
- const clause = node.importClause;
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
- function assetContextSpecifiers(ts, sourceFile, argument) {
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
- const directory = path.resolve(path.dirname(sourceFile.fileName), head);
116
- let stat;
117
- try { stat = fs.statSync(directory); } catch { return null; }
118
- if (!stat.isDirectory()) return null;
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.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier && ts.isStringLiteralLike(node.moduleSpecifier)) {
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
- let candidates = [];
213
- if (typeof declaration === 'string' || Array.isArray(declaration)) {
214
- if (request !== '.') return [];
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
- let candidates = exportCandidates(workspace, specifier);
269
- if (specifier === workspace.name && !workspace.exports) candidates = [workspace.manifest?.types, workspace.manifest?.typings, workspace.manifest?.module, workspace.manifest?.main].filter(item => typeof item === 'string');
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
- if (specifier === workspace.name) {
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
- const { ts } = loaded;
332
- const errors = [];
333
- const projects = [];
334
- const parsedProjects = new Map();
335
- const invalidProjects = new Set();
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
-