@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
@@ -0,0 +1,282 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ function appendProjectReferences(relative, parsed, config, ts, helpers, queue, errors) {
5
+ for (const reference of parsed.projectReferences ?? []) {
6
+ const referencedFile = helpers.typeScriptProjectReferencePath(ts, reference);
7
+ const absoluteReference = path.resolve(referencedFile);
8
+ if (!helpers.isInside(config.root, absoluteReference)) {
9
+ errors.push({ ruleId: 'ARCH_TSCONFIG_REFERENCE_OUTSIDE', project: relative, message: `Project reference leaves the repository: ${helpers.slash(path.relative(config.root, absoluteReference))}.` });
10
+ } else {
11
+ queue.push(helpers.slash(path.relative(config.root, absoluteReference)));
12
+ }
13
+ }
14
+ }
15
+
16
+ function readProject(relative, config, ts, helpers, state) {
17
+ const configFile = path.join(config.root, ...relative.split('/'));
18
+ if (!fs.existsSync(configFile)) {
19
+ state.errors.push({ ruleId: 'ARCH_TSCONFIG_MISSING', project: relative, message: `${relative} does not exist.` });
20
+ state.invalidProjects.add(relative);
21
+ return;
22
+ }
23
+ const { read, parsed } = helpers.readTypeScriptProject(ts, configFile);
24
+ if (read.error) {
25
+ state.errors.push(helpers.compilerError(ts, config.root, read.error, relative));
26
+ state.invalidProjects.add(relative);
27
+ return;
28
+ }
29
+ if (parsed.errors.length) {
30
+ state.errors.push(...parsed.errors.map(item => helpers.compilerError(ts, config.root, item, relative)));
31
+ state.invalidProjects.add(relative);
32
+ return;
33
+ }
34
+ appendProjectReferences(relative, parsed, config, ts, helpers, state.queue, state.errors);
35
+ state.parsedProjects.set(relative, parsed);
36
+ }
37
+
38
+ function loadProjectConfigurations(config, ts, helpers, errors) {
39
+ const state = { errors, parsedProjects: new Map(), invalidProjects: new Set(), queue: [...config.projects] };
40
+ const seenProjects = new Set();
41
+ while (state.queue.length) {
42
+ const relative = state.queue.shift();
43
+ if (seenProjects.has(relative)) continue;
44
+ seenProjects.add(relative);
45
+ readProject(relative, config, ts, helpers, state);
46
+ }
47
+ return state;
48
+ }
49
+
50
+ function pathMatches(config, paths, file, helpers) {
51
+ const relative = helpers.slash(path.relative(config.root, file));
52
+ const normalized = helpers.slash(paths).replace(/\/$/, '');
53
+ return helpers.sameOrUnder(relative, normalized);
54
+ }
55
+
56
+ function visitProjectReferences(relative, selected, parsedProjects, config, ts, helpers) {
57
+ if (selected.has(relative)) return;
58
+ selected.add(relative);
59
+ for (const reference of parsedProjects.get(relative)?.projectReferences ?? []) {
60
+ const referencedFile = helpers.typeScriptProjectReferencePath(ts, reference);
61
+ const referenced = helpers.slash(path.relative(config.root, path.resolve(referencedFile)));
62
+ if (parsedProjects.has(referenced)) visitProjectReferences(referenced, selected, parsedProjects, config, ts, helpers);
63
+ }
64
+ }
65
+
66
+ function invalidProjectMatches(config, paths, relative, helpers) {
67
+ const directory = path.dirname(path.join(config.root, ...relative.split('/')));
68
+ return paths.some(prefix => {
69
+ const absolute = path.join(config.root, ...helpers.slash(prefix).split('/'));
70
+ return helpers.isInside(directory, absolute);
71
+ });
72
+ }
73
+
74
+ function selectProjects(config, paths, ts, state, helpers) {
75
+ const selected = new Set();
76
+ const direct = new Set();
77
+ if (!paths.length) return { selected, direct };
78
+ for (const [relative, parsed] of state.parsedProjects) {
79
+ if (parsed.fileNames.some(file => paths.some(prefix => pathMatches(config, prefix, file, helpers)))) {
80
+ direct.add(relative);
81
+ visitProjectReferences(relative, selected, state.parsedProjects, config, ts, helpers);
82
+ }
83
+ }
84
+ for (const relative of state.invalidProjects) {
85
+ if (invalidProjectMatches(config, paths, relative, helpers)) selected.add(relative);
86
+ }
87
+ state.errors.splice(0, state.errors.length, ...state.errors.filter(error => !error.project || selected.has(error.project)));
88
+ return { selected, direct };
89
+ }
90
+
91
+ function createProjects(config, paths, state, selection, ts, helpers) {
92
+ const projects = [];
93
+ for (const [relative, parsed] of state.parsedProjects) {
94
+ if (paths.length && !selection.selected.has(relative)) continue;
95
+ const rootNames = paths.length && selection.direct.has(relative)
96
+ ? parsed.fileNames.filter(file => paths.some(prefix => pathMatches(config, prefix, file, helpers)))
97
+ : parsed.fileNames;
98
+ const program = helpers.createTypeScriptProgram(ts, { rootNames, options: parsed.options, projectReferences: parsed.projectReferences });
99
+ state.errors.push(...program.getSyntacticDiagnostics().filter(item => !item.file || helpers.isInside(config.root, item.file.fileName))
100
+ .map(item => helpers.compilerError(ts, config.root, item, relative, 'ARCH_SYNTAX_INVALID')));
101
+ projects.push({ relative, program, options: parsed.options });
102
+ }
103
+ return projects;
104
+ }
105
+
106
+ function collectProjectFiles(config, projects, helpers) {
107
+ const occurrences = new Map();
108
+ const sourceIn = new Map();
109
+ for (const project of projects) {
110
+ for (const sourceFile of project.program.getSourceFiles().filter(file => helpers.isProductionSource(config.root, file))) {
111
+ const name = helpers.canonical(sourceFile.fileName);
112
+ if (!occurrences.has(name)) occurrences.set(name, []);
113
+ occurrences.get(name).push(project);
114
+ sourceIn.set(`${project.relative}|${name}`, sourceFile);
115
+ }
116
+ }
117
+ return { occurrences, sourceIn };
118
+ }
119
+
120
+ function owningProject(config, candidates, file, helpers) {
121
+ return candidates.filter(item => helpers.isInside(path.dirname(path.join(config.root, ...item.relative.split('/'))), file))
122
+ .sort((x, y) => y.relative.split('/').length - x.relative.split('/').length)[0] ?? candidates[0];
123
+ }
124
+
125
+ function makeFileGraph(config, projects, helpers) {
126
+ const { occurrences, sourceIn } = collectProjectFiles(config, projects, helpers);
127
+ const fileMap = new Map();
128
+ const checkerByFile = new Map();
129
+ const ownerByFile = new Map();
130
+ for (const [name, candidates] of occurrences) {
131
+ const owner = owningProject(config, candidates, name, helpers);
132
+ ownerByFile.set(name, owner);
133
+ fileMap.set(name, sourceIn.get(`${owner.relative}|${name}`));
134
+ checkerByFile.set(name, owner.program.getTypeChecker());
135
+ }
136
+ const files = [...fileMap.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([, file]) => file);
137
+ const edges = new Map([...fileMap.keys()].map(file => [file, []]));
138
+ for (const [file, sourceFile] of fileMap) {
139
+ const sourceName = path.resolve(sourceFile.fileName);
140
+ if (sourceName !== file) edges.set(sourceName, edges.get(file));
141
+ }
142
+ return { fileMap, checkerByFile, ownerByFile, files, edges };
143
+ }
144
+
145
+ function addUnprovenFindings(sourceFile, project, references, errors, config, helpers) {
146
+ for (const item of references.unproven) errors.push({
147
+ ruleId: 'ARCH_DYNAMIC_DEPENDENCY_UNPROVEN',
148
+ project: project.relative,
149
+ path: helpers.relativePath(config.root, sourceFile.fileName),
150
+ ...helpers.sourceLocation(sourceFile, item.node),
151
+ message: `${item.kind} must use a string-literal module name so architecture coverage can resolve its dependency.`,
152
+ });
153
+ }
154
+
155
+ function unresolvedReferenceFinding(reference, sourceFile, project, config, workspaceNames, errors, helpers) {
156
+ const codeLike = !helpers.assetExtension.test(reference.specifier);
157
+ const workspaceImport = [...workspaceNames].some(name => helpers.sameOrUnder(reference.specifier, name));
158
+ const internal = reference.specifier.startsWith('.') || helpers.pathAliasMatches(reference.specifier, project.options.paths) || workspaceImport;
159
+ if (codeLike && internal) errors.push({
160
+ ruleId: 'ARCH_INTERNAL_IMPORT_UNRESOLVED',
161
+ project: project.relative,
162
+ path: helpers.relativePath(config.root, sourceFile.fileName),
163
+ ...helpers.sourceLocation(sourceFile, reference.node),
164
+ specifier: reference.specifier,
165
+ message: `Internal import ${reference.specifier} is not resolvable with ${project.relative}.`,
166
+ });
167
+ }
168
+
169
+ function internalReference(reference, project, workspaceNames, helpers) {
170
+ const workspaceImport = [...workspaceNames].some(name => reference.specifier === name || reference.specifier.startsWith(`${name}/`));
171
+ return reference.specifier.startsWith('.') || helpers.pathAliasMatches(reference.specifier, project.options.paths) || workspaceImport;
172
+ }
173
+
174
+ function appendBoundaryFinding(state, reference, sourceFile, project, actualTarget, { internal, reviewable }) {
175
+ const { config, errors, helpers } = state;
176
+ if (internal && helpers.crossesSide(config, actualTarget)) {
177
+ errors.push({
178
+ ruleId: 'ARCH_INTERNAL_IMPORT_OUTSIDE',
179
+ project: project.relative,
180
+ path: helpers.relativePath(config.root, sourceFile.fileName),
181
+ ...helpers.sourceLocation(sourceFile, reference.node),
182
+ specifier: reference.specifier,
183
+ message: `Internal import ${reference.specifier} leaves the ${path.basename(config.root)} side for ${helpers.slash(path.relative(config.packageRoot, actualTarget))}; nothing crosses the sides of an app.`,
184
+ });
185
+ return true;
186
+ }
187
+ if (internal && !reviewable) {
188
+ errors.push({
189
+ ruleId: 'ARCH_INTERNAL_IMPORT_OUTSIDE',
190
+ project: project.relative,
191
+ path: helpers.relativePath(config.root, sourceFile.fileName),
192
+ ...helpers.sourceLocation(sourceFile, reference.node),
193
+ specifier: reference.specifier,
194
+ message: `Internal import ${reference.specifier} resolves outside the checked repository.`,
195
+ });
196
+ return true;
197
+ }
198
+ return false;
199
+ }
200
+
201
+ function appendEdge(from, sourceFile, project, owningWorkspace, reference, actualTarget, state) {
202
+ if (!state.fileMap.has(actualTarget)) return;
203
+ const { config, ts, edges, edgeKeys, errors, helpers, workspaces } = state;
204
+ const edge = {
205
+ from,
206
+ to: actualTarget,
207
+ runtime: reference.runtime,
208
+ specifier: reference.specifier,
209
+ node: reference.node,
210
+ declaration: reference.declaration,
211
+ reexport: ts.isExportDeclaration(reference.declaration),
212
+ sourceFile,
213
+ project: project.relative,
214
+ ...helpers.sourceLocation(sourceFile, reference.node),
215
+ };
216
+ const key = `${from}\0${actualTarget}\0${reference.node.getStart(sourceFile)}\0${reference.specifier}`;
217
+ if (!edgeKeys.has(key)) { edges.get(from).push(edge); edgeKeys.add(key); }
218
+ const targetWorkspace = helpers.workspaceOf(workspaces, actualTarget);
219
+ if (owningWorkspace && targetWorkspace && owningWorkspace !== targetWorkspace) {
220
+ if (!owningWorkspace.app && targetWorkspace.app) errors.push(helpers.boundaryViolation(config.root, edge, owningWorkspace, targetWorkspace, 'ARCH_PACKAGE_IMPORTS_APP', 'A reusable workspace package cannot depend on an application workspace.'));
221
+ if (!helpers.packageExported(ts, targetWorkspace, reference.specifier, actualTarget)) errors.push(helpers.boundaryViolation(config.root, edge, owningWorkspace, targetWorkspace, 'ARCH_PACKAGE_EXPORT_BYPASS', 'Cross-package imports must use the target package name and a declared package export.'));
222
+ }
223
+ }
224
+
225
+ function processReference(from, sourceFile, project, owningWorkspace, reference, state) {
226
+ const { config, ts, workspaceNames, workspaces, host, errors, helpers } = state;
227
+ // A workspace package is read at its source, never at its build: the same import resolves whether or not dist exists.
228
+ const workspaceTarget = workspaces.filter(item => item.root !== helpers.canonical(config.root)).map(item => helpers.workspaceSourceEntry(ts, item, reference.specifier)).find(Boolean);
229
+ const resolvedName = workspaceTarget ?? helpers.resolveTypeScriptModule(ts, reference.specifier, sourceFile.fileName, project.options, host);
230
+ if (!resolvedName) {
231
+ unresolvedReferenceFinding(reference, sourceFile, project, config, workspaceNames, errors, helpers);
232
+ return;
233
+ }
234
+ const actualTarget = helpers.canonical(resolvedName);
235
+ const internal = internalReference(reference, project, workspaceNames, helpers);
236
+ // The boundary is the checkout, not the checked project: a path alias that lands in a sibling
237
+ // package of the same repository (`@fe-kit/*` -> packages/fe-kit) is still source a reviewer can
238
+ // open, while anything past the repository, or anything inside an installed dependency tree, is
239
+ // not. `config.repository` is null when there is no git checkout around the project, and the
240
+ // project is then the boundary it always was.
241
+ const reviewable = helpers.isInside(config.root, actualTarget)
242
+ || (config.repository && helpers.isInside(config.repository, actualTarget) && !helpers.slash(actualTarget).includes('/node_modules/'));
243
+ // 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
244
+ // side is another program, and a declared read (sides.fe.reads, be/contracts/) is codegen input, never an import.
245
+ if (appendBoundaryFinding(state, reference, sourceFile, project, actualTarget, { internal, reviewable })) return;
246
+ appendEdge(from, sourceFile, project, owningWorkspace, reference, actualTarget, state);
247
+ }
248
+
249
+ function processSourceFile(from, sourceFile, state) {
250
+ const { config, ts, workspaces, ownerByFile, helpers } = state;
251
+ const owningWorkspace = helpers.workspaceOf(workspaces, from);
252
+ const project = ownerByFile.get(from);
253
+ if (!project) return;
254
+ const references = helpers.moduleReferences(ts, sourceFile, project.program.getTypeChecker());
255
+ addUnprovenFindings(sourceFile, project, references, state.errors, config, helpers);
256
+ for (const reference of references.found) processReference(from, sourceFile, project, owningWorkspace, reference, state);
257
+ }
258
+
259
+ function dependencyGraph(config, loaded, fileGraph, ownerByFile, errors, helpers) {
260
+ const { ts } = loaded;
261
+ const edgeKeys = new Set();
262
+ const host = { ...ts.sys, fileExists: ts.sys.fileExists, readFile: ts.sys.readFile, realpath: ts.sys.realpath };
263
+ const workspaces = helpers.workspaceMetadata(config);
264
+ const workspaceNames = new Set(workspaces.map(item => item.name).filter(Boolean));
265
+ const state = { config, ts, fileMap: fileGraph.fileMap, edges: fileGraph.edges, edgeKeys, errors, workspaces, workspaceNames, host, ownerByFile, helpers };
266
+ for (const [from, sourceFile] of fileGraph.fileMap) processSourceFile(from, sourceFile, state);
267
+ return { edges: fileGraph.edges, workspaces };
268
+ }
269
+
270
+ export function contextFromProjects(config, loaded, paths, helpers) {
271
+ const { ts } = loaded;
272
+ const errors = [];
273
+ const projectState = loadProjectConfigurations(config, ts, helpers, errors);
274
+ const selection = selectProjects(config, paths, ts, projectState, helpers);
275
+ const projects = createProjects(config, paths, projectState, selection, ts, helpers);
276
+ const fileGraph = makeFileGraph(config, projects, helpers);
277
+ const { edges, workspaces } = dependencyGraph(config, loaded, fileGraph, fileGraph.ownerByFile, errors, helpers);
278
+ if (projects.length && fileGraph.files.length === 0) errors.push({ ruleId: 'ARCH_NO_SOURCE', message: 'The configured TypeScript projects contain no production TypeScript or JavaScript source.' });
279
+ return { loaded, errors, files: fileGraph.files, edges, programs: projects.map(item => item.program), program: projects[0]?.program ?? null, projects, ts, workspaces,
280
+ checkerFor: file => fileGraph.checkerByFile.get(helpers.canonical(file)) ?? null,
281
+ workspaceOf: file => helpers.workspaceOf(workspaces, helpers.canonical(file)) };
282
+ }