@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
@@ -22,33 +22,50 @@ export function custodyInputType(file, override) {
22
22
  return type;
23
23
  }
24
24
 
25
+ const sopsNames = (env, win, pathext) => {
26
+ if (!win) return ['sops'];
27
+ if (!pathext) return ['sops.exe', 'sops'];
28
+ return String(env.PATHEXT || '.EXE;.CMD;.BAT').split(';').filter(Boolean).map((ext) => `sops${ext}`);
29
+ };
30
+
31
+ const nestedPackageDirs = (filesystem, paths, directory) => {
32
+ const dirs = [];
33
+ try {
34
+ for (const nested of filesystem.readdirSync(directory, { withFileTypes: true })) {
35
+ if (nested.isDirectory()) dirs.push(paths.join(directory, nested.name));
36
+ }
37
+ } catch { /* unreadable package */ }
38
+ return dirs;
39
+ };
40
+
41
+ const wingetPackageDirs = (filesystem, paths, packages, wingetPackageTree) => {
42
+ const dirs = [];
43
+ try {
44
+ for (const entry of filesystem.readdirSync(packages)) {
45
+ if (!wingetPackageTree && !/sops/i.test(entry)) continue;
46
+ const directory = paths.join(packages, entry);
47
+ dirs.push(directory);
48
+ if (wingetPackageTree) dirs.push(...nestedPackageDirs(filesystem, paths, directory));
49
+ }
50
+ } catch { /* no WinGet packages */ }
51
+ return dirs;
52
+ };
53
+
54
+ const wingetDirs = (filesystem, paths, env, wingetPackageTree) => {
55
+ const dirs = [];
56
+ if (!env.LOCALAPPDATA) return dirs;
57
+ const winget = paths.join(env.LOCALAPPDATA, 'Microsoft', 'WinGet');
58
+ dirs.push(paths.join(winget, 'Links'), ...wingetPackageDirs(filesystem, paths, paths.join(winget, 'Packages'), wingetPackageTree));
59
+ return dirs;
60
+ };
61
+
25
62
  /** Resolve SOPS from PATH and WinGet. Consumers select PATHEXT names and the full package tree for their launcher. */
26
63
  export function resolveSops(env = process.env, { platform = process.platform, pathext = false, wingetPackageTree = false, filesystem = fs } = {}) {
27
64
  const win = platform === 'win32';
28
65
  const paths = win ? path.win32 : path.posix;
29
- const names = win
30
- ? (pathext ? String(env.PATHEXT || '.EXE;.CMD;.BAT').split(';').filter(Boolean).map((ext) => `sops${ext}`) : ['sops.exe', 'sops'])
31
- : ['sops'];
66
+ const names = sopsNames(env, win, pathext);
32
67
  const dirs = String(env.PATH ?? '').split(win ? ';' : ':').filter(Boolean);
33
- if (win && env.LOCALAPPDATA) {
34
- const winget = paths.join(env.LOCALAPPDATA, 'Microsoft', 'WinGet');
35
- dirs.push(paths.join(winget, 'Links'));
36
- const packages = paths.join(winget, 'Packages');
37
- try {
38
- for (const entry of filesystem.readdirSync(packages)) {
39
- if (!wingetPackageTree && !/sops/i.test(entry)) continue;
40
- const directory = paths.join(packages, entry);
41
- dirs.push(directory);
42
- if (wingetPackageTree) {
43
- try {
44
- for (const nested of filesystem.readdirSync(directory, { withFileTypes: true })) {
45
- if (nested.isDirectory()) dirs.push(paths.join(directory, nested.name));
46
- }
47
- } catch { /* unreadable package */ }
48
- }
49
- }
50
- } catch { /* no WinGet packages */ }
51
- }
68
+ if (win) dirs.push(...wingetDirs(filesystem, paths, env, wingetPackageTree));
52
69
  for (const directory of dirs) for (const name of names) {
53
70
  const file = paths.join(directory, name);
54
71
  try { if (filesystem.statSync(file).isFile()) return file; } catch { /* next candidate */ }
@@ -83,7 +100,7 @@ export function runSopsFile(bin, args, operation, options = {}) {
83
100
  }
84
101
 
85
102
  const SELECTED_SOPS_VERSIONS = new Set(['3.13.2', '3.13.3']);
86
- const SELECTED_AGE_VERSIONS = new Set(['1.2.1', '1.3.1']);
103
+ export const SELECTED_AGE_VERSIONS = new Set(['1.2.1', '1.3.1']);
87
104
  const IDENTITY_TIMEOUT_MS = 20_000;
88
105
  const CONTROLLED_ENV = new Set(['SOPS_AGE_KEY', 'SOPS_AGE_KEY_FILE', 'SOPS_AGE_KEY_CMD', 'SOPS_AGE_SSH_PRIVATE_KEY_FILE', 'SOPS_AGE_SSH_PRIVATE_KEY_CMD', 'SOPS_AGE_RECIPIENT', 'SOPS_KEYSERVICE', 'SOPS_ENABLE_LOCAL_KEYSERVICE', 'SOPS_DECRYPTION_ORDER', 'SOPS_KMS_ARN', 'SOPS_GCP_KMS_IDS', 'SOPS_HUAWEICLOUD_KMS_IDS', 'SOPS_AZURE_KEYVAULT_URLS', 'SOPS_VAULT_URIS', 'SOPS_PGP_FP', 'SOPS_AGE_RECIPIENTS', 'HOME', 'USERPROFILE', 'APPDATA', 'XDG_CONFIG_HOME']);
89
106
 
@@ -119,7 +136,7 @@ const auditAbsent = cwd => {
119
136
  try { fs.lstatSync(path.resolve(cwd ?? process.cwd(), '/etc/sops/audit.yaml')); return false; }
120
137
  catch (error) { return error.code === 'ENOENT'; }
121
138
  };
122
- const captured = r => r && r.status === 0 && r.signal == null && !r.error && Buffer.isBuffer(r.stdout) && Buffer.isBuffer(r.stderr);
139
+ const captured = r => r?.status === 0 && r.signal == null && !r.error && Buffer.isBuffer(r.stdout) && Buffer.isBuffer(r.stderr);
123
140
  const decoded = bytes => new TextDecoder('utf-8', { fatal: true }).decode(bytes);
124
141
  const ownedRequest = request => {
125
142
  if (request.operation === 'seal') {
@@ -141,115 +158,176 @@ const ownedRequest = request => {
141
158
  return { inputType: flags['--input-type'], outputType: flags['--output-type'], file: args.at(-1), recipient: flags['--age'] };
142
159
  };
143
160
 
161
+ /** The admitted identity key, or the unchanged refusal/failure result (admitted.key is absent then). */
162
+ const selectedAdmission = (selection, spec, invocation, env) => {
163
+ const fail = () => ({ status: null, stdout: '', stderr: '', error: selection.error });
164
+ if (!spec || !['binary', 'json', 'yaml', 'dotenv'].includes(spec.inputType) || !['binary', 'json', 'yaml', 'dotenv'].includes(spec.outputType)) return fail();
165
+ if (!invocation || typeof invocation.runProgram !== 'function' || typeof invocation.resolveRealTool !== 'function') return fail();
166
+ if (!['win32', 'linux', 'darwin'].includes(process.platform)) return held('unsupported-platform');
167
+ const key = env[selection.inlineName];
168
+ if (typeof key !== 'string' || !/^AGE-SECRET-KEY-1[0-9A-Z]+$/.test(key.trim())) return fail();
169
+ return { key };
170
+ };
171
+
172
+ const decryptArgs = (file, inputType, outputType) => ['decrypt', '--input-type', inputType, '--output-type', outputType, '--filename-override', file, '--enable-local-keyservice=true', '--decryption-order', 'age'];
173
+
174
+ const decryptSelected = (run, exe, file, input, spec, recipient) => {
175
+ const envelope = selectedAgeEnvelope(decoded(input), spec.inputType, recipient);
176
+ if (!envelope.ok) return held(envelope.reason);
177
+ const result = run(exe, decryptArgs(file, spec.inputType, spec.outputType), input);
178
+ if (!captured(result)) return held('selected-decryption-failed');
179
+ return { status: 0, stdout: decoded(result.stdout), stderr: '', error: null };
180
+ };
181
+
182
+ const encryptSelected = (run, exe, file, input, spec, recipient) => {
183
+ if (spec.recipient !== recipient) return held('recipient-mismatch');
184
+ const args = ['encrypt', '--input-type', spec.inputType, '--output-type', spec.outputType, '--filename-override', file, '--enable-local-keyservice=true', '--age', recipient];
185
+ const result = run(exe, args, input);
186
+ if (!captured(result)) return held('selected-encryption-failed');
187
+ if (result.stdout.length > CREDENTIAL_FILE_MAX_BYTES) return held('document-over-budget');
188
+ const text = decoded(result.stdout);
189
+ const envelope = selectedAgeEnvelope(text, spec.outputType, recipient);
190
+ if (!envelope.ok) return held(envelope.reason);
191
+ const readback = run(exe, decryptArgs(file, spec.outputType, spec.inputType), result.stdout);
192
+ if (!captured(readback)) return held('selected-readback-failed');
193
+ return { status: 0, stdout: text, stderr: '', error: null };
194
+ };
195
+
196
+ const wipeCaptures = (captures) => {
197
+ for (const result of captures) {
198
+ if (Buffer.isBuffer(result?.stdout)) result.stdout.fill(0);
199
+ if (Buffer.isBuffer(result?.stderr)) result.stderr.fill(0);
200
+ }
201
+ };
202
+
203
+ // The one recipient an `age -y` capture names, or null.
204
+ const recipientOf = (stdout) => {
205
+ const recipients = decoded(stdout).trim().split(/\r?\n/);
206
+ return recipients.length === 1 && /^age1[ac-hj-np-z02-9]+$/.test(recipients[0]) ? recipients[0] : null;
207
+ };
208
+
209
+ const toolProfileSupported = (version, ageVersion) => captured(version) && captured(ageVersion)
210
+ && SELECTED_SOPS_VERSIONS.has(/^sops\s+(\d+\.\d+\.\d+)/.exec(decoded(version.stdout))?.[1])
211
+ && SELECTED_AGE_VERSIONS.has(/^v?(\d+\.\d+\.\d+)/.exec(decoded(ageVersion.stdout))?.[1]);
212
+
213
+ const invalidCaptureBudget = (deadline, maxBuffer) => !Number.isInteger(deadline) || deadline < 1 || deadline > 2_147_483_647 || !Number.isInteger(maxBuffer) || maxBuffer < 1;
214
+
215
+ // The document bytes into `state.input`; the refusal when they are unavailable or over budget, else null.
216
+ const readSelectedInput = (state, request, spec, file) => {
217
+ try { state.input = request.operation === 'seal' ? Buffer.from(spec.plaintext) : readSecretBytes(file); } catch { return held('actual-document-unavailable'); }
218
+ return state.input.length > CREDENTIAL_FILE_MAX_BYTES ? held('document-over-budget') : null;
219
+ };
220
+
221
+ const selectedRunner = (invocation, options, captures, selectedEnv) => (program, args, bytes, childEnv = selectedEnv) => {
222
+ const result = invocation.runProgram(program, args, { ...options, env: childEnv, input: bytes });
223
+ captures.push(result); return result;
224
+ };
225
+
226
+ const performSelected = (state, { bin, request, spec, key, selection, invocation, env, cwd, maxBuffer, timeout }) => {
227
+ const file = path.resolve(cwd ?? process.cwd(), spec.file);
228
+ const unavailable = readSelectedInput(state, request, spec, file);
229
+ if (unavailable) return unavailable;
230
+ const exe = bin ?? resolveSops(env), age = invocation.resolveRealTool('age-keygen', { env });
231
+ if (!nativeFile(exe) || !nativeFile(age)) return held('native-tool-unavailable');
232
+ if (!auditAbsent(cwd)) return held('unsupported-audit-context');
233
+ const deadline = timeout ?? IDENTITY_TIMEOUT_MS;
234
+ if (invalidCaptureBudget(deadline, maxBuffer)) return held('invalid-capture-budget');
235
+ state.selectedEnv = isolatedSopsEnv(env, selection.inlineName);
236
+ const publicEnv = isolatedSopsEnv(env, null);
237
+ const options = { cwd, env: state.selectedEnv, shell: false, encoding: 'buffer', windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'], timeout: deadline, maxBuffer };
238
+ const run = selectedRunner(invocation, options, state.captures, state.selectedEnv);
239
+ const version = run(exe, ['--disable-version-check', '--version'], undefined, publicEnv);
240
+ const ageVersion = run(age, ['--version'], undefined, publicEnv);
241
+ if (!toolProfileSupported(version, ageVersion)) return held('unsupported-tool-profile');
242
+ state.identity = Buffer.from(key.trim(), 'utf8');
243
+ const derived = run(age, ['-y'], state.identity, publicEnv);
244
+ if (!captured(derived)) return held('recipient-unproven');
245
+ const recipient = recipientOf(derived.stdout);
246
+ if (recipient === null) return held('recipient-unproven');
247
+ if (request.operation === 'decrypt') return decryptSelected(run, exe, file, state.input, spec, recipient);
248
+ return encryptSelected(run, exe, file, state.input, spec, recipient);
249
+ };
250
+
144
251
  /** The domain composes the existing process owner: invocation={runProgram(file,args,options),resolveRealTool(program,{env})}. No API imports another system. */
145
252
  export function runSelectedSops(bin, request, { selection, invocation, env, cwd, maxBuffer, timeout } = {}) {
146
253
  if (selection?.error?.identityRefusal !== 'inline-context-unqualified' || typeof selection.inlineName !== 'string' || !env || typeof env !== 'object') return held('selected-context-missing');
147
254
  const spec = ownedRequest(request);
148
- if (!spec || !['binary', 'json', 'yaml', 'dotenv'].includes(spec.inputType) || !['binary', 'json', 'yaml', 'dotenv'].includes(spec.outputType)) return { status: null, stdout: '', stderr: '', error: selection.error };
149
- if (!invocation || typeof invocation.runProgram !== 'function' || typeof invocation.resolveRealTool !== 'function') return { status: null, stdout: '', stderr: '', error: selection.error };
150
- if (!['win32', 'linux', 'darwin'].includes(process.platform)) return held('unsupported-platform');
151
- const key = env[selection.inlineName];
152
- if (typeof key !== 'string' || !/^AGE-SECRET-KEY-1[0-9A-Z]+$/.test(key.trim())) return { status: null, stdout: '', stderr: '', error: selection.error };
153
- let input, identity, selectedEnv;
154
- const captures = [];
255
+ const admitted = selectedAdmission(selection, spec, invocation, env);
256
+ if (!admitted.key) return admitted;
257
+ const state = { input: undefined, identity: undefined, selectedEnv: undefined, captures: [] };
155
258
  try {
156
- const file = path.resolve(cwd ?? process.cwd(), spec.file);
157
- try { input = request.operation === 'seal' ? Buffer.from(spec.plaintext) : readSecretBytes(file); } catch { return held('actual-document-unavailable'); }
158
- if (input.length > CREDENTIAL_FILE_MAX_BYTES) return held('document-over-budget');
159
- const exe = bin ?? resolveSops(env), age = invocation.resolveRealTool('age-keygen', { env });
160
- if (!nativeFile(exe) || !nativeFile(age)) return held('native-tool-unavailable');
161
- if (!auditAbsent(cwd)) return held('unsupported-audit-context');
162
- const deadline = timeout ?? IDENTITY_TIMEOUT_MS;
163
- if (!Number.isInteger(deadline) || deadline < 1 || deadline > 2_147_483_647 || !Number.isInteger(maxBuffer) || maxBuffer < 1) return held('invalid-capture-budget');
164
- selectedEnv = isolatedSopsEnv(env, selection.inlineName);
165
- const publicEnv = isolatedSopsEnv(env, null);
166
- const options = { cwd, env: selectedEnv, shell: false, encoding: 'buffer', windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'], timeout: deadline, maxBuffer };
167
- const run = (program, args, bytes, childEnv = selectedEnv) => {
168
- const result = invocation.runProgram(program, args, { ...options, env: childEnv, input: bytes });
169
- captures.push(result); return result;
170
- };
171
- const version = run(exe, ['--disable-version-check', '--version'], undefined, publicEnv);
172
- const ageVersion = run(age, ['--version'], undefined, publicEnv);
173
- if (!captured(version) || !captured(ageVersion) || !SELECTED_SOPS_VERSIONS.has(/^sops\s+([0-9]+\.[0-9]+\.[0-9]+)/.exec(decoded(version.stdout))?.[1]) || !SELECTED_AGE_VERSIONS.has(/^v?([0-9]+\.[0-9]+\.[0-9]+)/.exec(decoded(ageVersion.stdout))?.[1])) return held('unsupported-tool-profile');
174
- identity = Buffer.from(key.trim(), 'utf8');
175
- const derived = run(age, ['-y'], identity, publicEnv);
176
- if (!captured(derived)) return held('recipient-unproven');
177
- const recipients = decoded(derived.stdout).trim().split(/\r?\n/);
178
- if (recipients.length !== 1 || !/^age1[ac-hj-np-z02-9]+$/.test(recipients[0])) return held('recipient-unproven');
179
- const recipient = recipients[0];
180
- const decryptArgs = (inputType, outputType) => ['decrypt', '--input-type', inputType, '--output-type', outputType, '--filename-override', file, '--enable-local-keyservice=true', '--decryption-order', 'age'];
181
- if (request.operation === 'decrypt') {
182
- const envelope = selectedAgeEnvelope(decoded(input), spec.inputType, recipient);
183
- if (!envelope.ok) return held(envelope.reason);
184
- const result = run(exe, decryptArgs(spec.inputType, spec.outputType), input);
185
- if (!captured(result)) return held('selected-decryption-failed');
186
- return { status: 0, stdout: decoded(result.stdout), stderr: '', error: null };
187
- }
188
- if (spec.recipient !== recipient) return held('recipient-mismatch');
189
- const args = ['encrypt', '--input-type', spec.inputType, '--output-type', spec.outputType, '--filename-override', file, '--enable-local-keyservice=true', '--age', recipient];
190
- const result = run(exe, args, input);
191
- if (!captured(result)) return held('selected-encryption-failed');
192
- if (result.stdout.length > CREDENTIAL_FILE_MAX_BYTES) return held('document-over-budget');
193
- const text = decoded(result.stdout);
194
- const envelope = selectedAgeEnvelope(text, spec.outputType, recipient);
195
- if (!envelope.ok) return held(envelope.reason);
196
- const readback = run(exe, decryptArgs(spec.outputType, spec.inputType), result.stdout);
197
- if (!captured(readback)) return held('selected-readback-failed');
198
- return { status: 0, stdout: text, stderr: '', error: null };
259
+ return performSelected(state, { bin, request, spec, key: admitted.key, selection, invocation, env, cwd, maxBuffer, timeout });
199
260
  } catch { return held('selected-native-refused'); }
200
261
  finally {
201
- identity?.fill(0); input?.fill(0);
202
- for (const result of captures) { if (Buffer.isBuffer(result?.stdout)) result.stdout.fill(0); if (Buffer.isBuffer(result?.stderr)) result.stderr.fill(0); }
203
- if (selectedEnv) delete selectedEnv.SOPS_AGE_KEY;
262
+ state.identity?.fill(0); state.input?.fill(0);
263
+ wipeCaptures(state.captures);
264
+ if (state.selectedEnv) delete state.selectedEnv.SOPS_AGE_KEY;
204
265
  }
205
266
  }
206
267
 
268
+ const identityRequestValid = ({ env, cwd, invocation, assertLease, consume }) =>
269
+ env && typeof env === 'object' && path.isAbsolute(cwd ?? '') && typeof consume === 'function'
270
+ && typeof assertLease === 'function' && typeof invocation?.runProgram === 'function'
271
+ && typeof invocation?.resolveRealTool === 'function';
272
+ const identityLinesValid = (lines, identities) => identities.length === 1 && /^AGE-SECRET-KEY-1[0-9A-Z]+$/.test(identities[0])
273
+ && !lines.some(line => line.startsWith('#') && !/^# (?:created:|public key:)/.test(line));
274
+ const declaredRecipientMatches = (lines, recipient) => {
275
+ const declared = lines.filter(line => line.startsWith('# public key:'));
276
+ return declared.length === 1 && declared[0].slice('# public key:'.length).trim() === recipient;
277
+ };
278
+ const publicationShaped = (publication) => publication && typeof publication.ok === 'boolean' && ['none', 'unknown', 'complete'].includes(publication.effectState);
279
+ const publicationDurable = (publication) => [true, false].includes(publication.created) && ['file-fsync', 'file-and-parent-fsync', 'file-fsync-namespace-unqualified'].includes(publication.durability);
280
+
281
+ const identityFailure = (reason, generated) => ({ ok: false, reason, captureState: generated ? 'unknown' : 'none', effectState: generated ? 'unknown' : 'none' });
282
+
283
+ // The outcome of handing the captured identity to the installer's publication.
284
+ const publicationOutcome = (publication, recipient) => {
285
+ if (!publicationShaped(publication)) return identityFailure('publication-unknown', true);
286
+ if (!publication.ok || publication.effectState !== 'complete') return { ok: false, captureState: 'generated', effectState: publication.effectState,
287
+ reason: 'publication-held' };
288
+ if (!publicationDurable(publication)) return identityFailure('publication-unknown', true);
289
+ return { ok: true, captureState: 'generated', effectState: 'complete', created: publication.created,
290
+ durability: publication.durability, publicRecipient: recipient };
291
+ };
292
+
293
+ // One age-keygen run under the caller's lease, its capture kept for wiping.
294
+ const leasedAgeRunner = (invocation, age, options, captures, assertLease) => (args, input) => {
295
+ if (assertLease() !== true) throw new Error('initial age lease lost');
296
+ const result = invocation.runProgram(age, args, { ...options, input });
297
+ captures.push(result);
298
+ if (!captured(result) || result.stdout.length > options.maxBuffer || result.stderr.length > options.maxBuffer) throw new Error('initial age capture incomplete');
299
+ return result;
300
+ };
301
+
302
+ const generateAndPublish = (state, { env, cwd, invocation, assertLease, consume }) => {
303
+ const age = invocation.resolveRealTool('age-keygen', { env });
304
+ if (!nativeFile(age)) return identityFailure('native-tool-unavailable', false);
305
+ const options = { cwd, env: isolatedSopsEnv(env, null), shell: false, encoding: 'buffer', windowsHide: true,
306
+ stdio: ['pipe', 'pipe', 'pipe'], timeout: IDENTITY_TIMEOUT_MS, maxBuffer: CREDENTIAL_FILE_MAX_BYTES };
307
+ const run = leasedAgeRunner(invocation, age, options, state.captures, assertLease);
308
+ const version = run(['--version']);
309
+ if (!SELECTED_AGE_VERSIONS.has(/^v?(\d+\.\d+\.\d+)/.exec(decoded(version.stdout))?.[1])) return identityFailure('unsupported-tool-profile', false);
310
+ if (assertLease() !== true) return identityFailure('lease-lost', false);
311
+ state.generated = true;
312
+ const made = run([]), lines = decoded(made.stdout).trim().split(/\r?\n/);
313
+ const identities = lines.filter(line => line && !line.startsWith('#'));
314
+ if (!identityLinesValid(lines, identities)) return identityFailure('capture-incomplete', true);
315
+ state.identity = Buffer.from(identities[0], 'utf8');
316
+ const recipient = recipientOf(run(['-y'], state.identity).stdout);
317
+ if (recipient === null) return identityFailure('recipient-unproven', true);
318
+ if (!declaredRecipientMatches(lines, recipient) || assertLease() !== true) return identityFailure('recipient-unproven', true);
319
+ return publicationOutcome(consume(state.identity, recipient), recipient);
320
+ };
321
+
207
322
  /** One real private capture for an admitted initial request; the installer owns its durable attempt. */
208
323
  export function withGeneratedAgeIdentity({ env, cwd, invocation, assertLease, consume } = {}) {
209
- const failure = (reason, generated) => ({ ok: false, reason, captureState: generated ? 'unknown' : 'none', effectState: generated ? 'unknown' : 'none' });
210
- if (!env || typeof env !== 'object' || !path.isAbsolute(cwd ?? '') || typeof consume !== 'function'
211
- || typeof assertLease !== 'function' || typeof invocation?.runProgram !== 'function'
212
- || typeof invocation?.resolveRealTool !== 'function') return failure('invalid-request', false);
213
- const captures = [];
214
- let generated = false, identity;
324
+ if (!identityRequestValid({ env, cwd, invocation, assertLease, consume })) return identityFailure('invalid-request', false);
325
+ const state = { captures: [], generated: false, identity: undefined };
215
326
  try {
216
- const age = invocation.resolveRealTool('age-keygen', { env });
217
- if (!nativeFile(age)) return failure('native-tool-unavailable', false);
218
- const options = { cwd, env: isolatedSopsEnv(env, null), shell: false, encoding: 'buffer', windowsHide: true,
219
- stdio: ['pipe', 'pipe', 'pipe'], timeout: IDENTITY_TIMEOUT_MS, maxBuffer: CREDENTIAL_FILE_MAX_BYTES };
220
- const run = (args, input) => {
221
- if (assertLease() !== true) throw new Error('initial age lease lost');
222
- const result = invocation.runProgram(age, args, { ...options, input });
223
- captures.push(result);
224
- if (!captured(result) || result.stdout.length > options.maxBuffer || result.stderr.length > options.maxBuffer) throw new Error('initial age capture incomplete');
225
- return result;
226
- };
227
- const version = run(['--version']);
228
- if (!SELECTED_AGE_VERSIONS.has(/^v?([0-9]+\.[0-9]+\.[0-9]+)/.exec(decoded(version.stdout))?.[1])) return failure('unsupported-tool-profile', false);
229
- if (assertLease() !== true) return failure('lease-lost', false);
230
- generated = true;
231
- const made = run([]), lines = decoded(made.stdout).trim().split(/\r?\n/);
232
- const identities = lines.filter(line => line && !line.startsWith('#'));
233
- if (identities.length !== 1 || !/^AGE-SECRET-KEY-1[0-9A-Z]+$/.test(identities[0])
234
- || lines.some(line => line.startsWith('#') && !/^# (?:created:|public key:)/.test(line))) return failure('capture-incomplete', true);
235
- identity = Buffer.from(identities[0], 'utf8');
236
- const derived = run(['-y'], identity), recipients = decoded(derived.stdout).trim().split(/\r?\n/);
237
- if (recipients.length !== 1 || !/^age1[ac-hj-np-z02-9]+$/.test(recipients[0])) return failure('recipient-unproven', true);
238
- const recipient = recipients[0], declared = lines.filter(line => line.startsWith('# public key:'));
239
- if (declared.length !== 1 || declared[0].slice('# public key:'.length).trim() !== recipient || assertLease() !== true) return failure('recipient-unproven', true);
240
- const publication = consume(identity, recipient);
241
- if (!publication || typeof publication.ok !== 'boolean' || !['none', 'unknown', 'complete'].includes(publication.effectState)) return failure('publication-unknown', true);
242
- if (!publication.ok || publication.effectState !== 'complete') return { ok: false, captureState: 'generated', effectState: publication.effectState,
243
- reason: 'publication-held' };
244
- if (![true, false].includes(publication.created) || !['file-fsync', 'file-and-parent-fsync', 'file-fsync-namespace-unqualified'].includes(publication.durability)) return failure('publication-unknown', true);
245
- return { ok: true, captureState: 'generated', effectState: 'complete', created: publication.created,
246
- durability: publication.durability, publicRecipient: recipient };
247
- } catch { return failure('capture-or-publication-unknown', generated); }
327
+ return generateAndPublish(state, { env, cwd, invocation, assertLease, consume });
328
+ } catch { return identityFailure('capture-or-publication-unknown', state.generated); }
248
329
  finally {
249
- identity?.fill(0);
250
- for (const result of captures) {
251
- if (Buffer.isBuffer(result?.stdout)) result.stdout.fill(0);
252
- if (Buffer.isBuffer(result?.stderr)) result.stderr.fill(0);
253
- }
330
+ state.identity?.fill(0);
331
+ wipeCaptures(state.captures);
254
332
  }
255
333
  }
@@ -36,9 +36,9 @@ export function slotAdmitsFile(resolver, file) {
36
36
  if (classified.status === 'no-slot' || classified.status === 'ambiguous' || !classified.slot) return null;
37
37
  const slot = resolver.slot(classified.slot);
38
38
  const relative = relativeToRoot(classified.path, classified.root);
39
- const hit = matchersOf([...(slot.requires ?? []), ...(slot.allows ?? [])], classified.bindings).find(matcher => matcher.expression.test(relative));
40
- const forbidden = matchersOf(slot.forbids, classified.bindings).find(matcher => matcher.expression.test(relative) || matcher.expression.test(`${relative}/`));
41
- return Boolean(hit) && !forbidden;
39
+ const hit = matchersOf([...(slot.requires ?? []), ...(slot.allows ?? [])], classified.bindings).some(matcher => matcher.expression.test(relative));
40
+ const forbidden = matchersOf(slot.forbids, classified.bindings).some(matcher => matcher.expression.test(relative) || matcher.expression.test(`${relative}/`));
41
+ return hit && !forbidden;
42
42
  }
43
43
 
44
44
  /**
@@ -1,19 +1,20 @@
1
1
  // ast-walks.mjs - AST walks the architecture checkers share (moved out of typescript.mjs, which keeps the program loading and module resolution).
2
2
  import { canonical, isInside } from './config.mjs';
3
- import { sourceLocation } from '../../lib/ts-ast.mjs';
3
+ import { hasAnyFlag, isConstVariable, sourceLocation } from '../../lib/ts-ast.mjs';
4
4
  import { UNPROVEN_FRAMEWORK, isUnshadowedCommonJsRequire, relativePath, unwrapExpression } from './typescript.mjs';
5
5
 
6
6
  /** `expression` unwrapped by the caller's own predicate set (a predicate absent on an older ts reads as never). */
7
7
  export function unwrapEach(ts, expression, predicates) {
8
- while (expression && predicates.some((predicate) => predicate?.(expression))) expression = expression.expression;
9
- return expression;
8
+ let current = expression;
9
+ while (current && predicates.some((predicate) => predicate?.(current))) current = current.expression;
10
+ return current;
10
11
  }
11
12
 
12
13
  /** `value` with every TS alias hop resolved (a re-exported import reads as its target symbol). */
13
- export function normalizedSymbolValue(ts, checker, value) {
14
- let symbol = value ?? null;
14
+ export function normalizedSymbolValue(ts, checker, value = null) {
15
+ let symbol = value;
15
16
  const seen = new Set();
16
- while (symbol && (symbol.flags & ts.SymbolFlags.Alias) && !seen.has(symbol)) {
17
+ while (symbol && hasAnyFlag(symbol.flags, ts.SymbolFlags.Alias) && !seen.has(symbol)) {
17
18
  seen.add(symbol);
18
19
  const target = checker.getAliasedSymbol(symbol);
19
20
  if (!target || target === symbol) break;
@@ -48,7 +49,7 @@ export function valueSymbol(ts, checker, node, seen = new Set(), { unselected =
48
49
  seen.add(symbol);
49
50
  const declarations = symbol.getDeclarations?.() ?? [];
50
51
  if (declarations.length === 1 && ts.isVariableDeclaration(declarations[0]) && declarations[0].initializer
51
- && (ts.getCombinedNodeFlags(declarations[0].parent) & ts.NodeFlags.Const)) {
52
+ && isConstVariable(ts, declarations[0])) {
52
53
  return valueSymbol(ts, checker, declarations[0].initializer, seen, { unselected });
53
54
  }
54
55
  return symbol;
@@ -102,50 +103,59 @@ export function anyDescendant(ts, node, match) {
102
103
  * selected node's symbols to a kind (default `targets.get(symbol)`); `shorthandOf` (when set) also resolves
103
104
  * shorthand-property declarations and descends property assignments; `newExpression` descends `new X()` callees.
104
105
  */
106
+ const addKinds = (target, source) => { for (const kind of source) target.add(kind); };
107
+
108
+ const kindsFromReturns = (ts, checker, declaration, targets, options) => {
109
+ const kinds = new Set();
110
+ for (const returned of returnedExpressions(ts, declaration)) addKinds(kinds, tracedFrameworkKinds(ts, checker, returned, targets, options));
111
+ return kinds;
112
+ };
113
+
114
+ const kindsFromDeclaration = (ts, checker, declaration, targets, options, shorthandOf) => {
115
+ const kinds = new Set();
116
+ if (ts.isVariableDeclaration(declaration) && declaration.initializer)
117
+ addKinds(kinds, tracedFrameworkKinds(ts, checker, declaration.initializer, targets, options));
118
+ if (shorthandOf && ts.isShorthandPropertyAssignment(declaration)) {
119
+ const target = normalizedSymbolValue(ts, checker, checker.getShorthandAssignmentValueSymbol?.(declaration));
120
+ const kind = shorthandOf(ts, checker, target, targets);
121
+ if (kind) kinds.add(kind);
122
+ }
123
+ if (shorthandOf && ts.isPropertyAssignment(declaration))
124
+ addKinds(kinds, tracedFrameworkKinds(ts, checker, declaration.initializer, targets, options));
125
+ addKinds(kinds, kindsFromReturns(ts, checker, declaration, targets, options));
126
+ return kinds;
127
+ };
128
+
129
+ const kindsFromSymbol = (ts, checker, symbol, targets, seen, options) => {
130
+ const nextSeen = new Set(seen).add(symbol);
131
+ const declarationOptions = { ...options, seen: nextSeen };
132
+ const kinds = new Set();
133
+ for (const declaration of symbol.getDeclarations?.() ?? [])
134
+ addKinds(kinds, kindsFromDeclaration(ts, checker, declaration, targets, declarationOptions, options.shorthandOf));
135
+ return kinds;
136
+ };
137
+
138
+ const defaultDirectOf = (ts, checker, { symbol, targets }) => targets.get(symbol) ?? null;
139
+
105
140
  export function tracedFrameworkKinds(ts, checker, expression, targets,
106
141
  { seen = new Set(), depth = 0, directOf = null, shorthandOf = null, newExpression = false } = {}) {
107
142
  if (!expression) return new Set();
108
143
  if (depth > 10) return new Set([UNPROVEN_FRAMEWORK]);
109
- expression = unwrapExpression(ts, expression);
110
- const selected = selectedNode(ts, expression);
144
+ const unwrapped = unwrapExpression(ts, expression);
145
+ const selected = selectedNode(ts, unwrapped);
111
146
  const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
112
- const direct = symbol ? (directOf ?? ((ts2, c, { symbol: s, targets: t }) => t.get(s) ?? null))(ts, checker, { symbol, selected, targets }) : null;
147
+ const direct = symbol ? (directOf ?? defaultDirectOf)(ts, checker, { symbol, selected, targets }) : null;
113
148
  if (direct) return new Set([direct]);
114
- const opts = { depth: depth + 1, directOf, shorthandOf, newExpression };
115
- if (ts.isArrowFunction(expression) || ts.isFunctionExpression(expression)) {
116
- const kinds = new Set();
117
- for (const returned of returnedExpressions(ts, expression)) {
118
- for (const kind of tracedFrameworkKinds(ts, checker, returned, targets, { ...opts, seen })) kinds.add(kind);
119
- }
120
- return kinds;
121
- }
122
- if (ts.isCallExpression(expression)) {
123
- const kinds = tracedFrameworkKinds(ts, checker, expression.expression, targets, { ...opts, seen });
149
+ const options = { depth: depth + 1, directOf, shorthandOf, newExpression };
150
+ if (ts.isArrowFunction(unwrapped) || ts.isFunctionExpression(unwrapped)) return kindsFromReturns(ts, checker, unwrapped, targets, { ...options, seen });
151
+ if (ts.isCallExpression(unwrapped)) {
152
+ const kinds = tracedFrameworkKinds(ts, checker, unwrapped.expression, targets, { ...options, seen });
124
153
  if (kinds.size) return kinds;
125
154
  }
126
- if (newExpression && ts.isNewExpression(expression)) {
127
- return tracedFrameworkKinds(ts, checker, expression.expression, targets, { ...opts, seen });
128
- }
155
+ if (newExpression && ts.isNewExpression(unwrapped))
156
+ return tracedFrameworkKinds(ts, checker, unwrapped.expression, targets, { ...options, seen });
129
157
  if (!symbol || seen.has(symbol)) return new Set();
130
- const nextSeen = new Set(seen).add(symbol);
131
- const kinds = new Set();
132
- for (const declaration of symbol.getDeclarations?.() ?? []) {
133
- if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
134
- for (const kind of tracedFrameworkKinds(ts, checker, declaration.initializer, targets, { ...opts, seen: nextSeen })) kinds.add(kind);
135
- }
136
- if (shorthandOf && ts.isShorthandPropertyAssignment(declaration)) {
137
- const target = normalizedSymbolValue(ts, checker, checker.getShorthandAssignmentValueSymbol?.(declaration));
138
- const kind = shorthandOf(ts, checker, target, targets);
139
- if (kind) kinds.add(kind);
140
- }
141
- if (shorthandOf && ts.isPropertyAssignment(declaration)) {
142
- for (const kind of tracedFrameworkKinds(ts, checker, declaration.initializer, targets, { ...opts, seen: nextSeen })) kinds.add(kind);
143
- }
144
- for (const returned of returnedExpressions(ts, declaration)) {
145
- for (const kind of tracedFrameworkKinds(ts, checker, returned, targets, { ...opts, seen: nextSeen })) kinds.add(kind);
146
- }
147
- }
148
- return kinds;
158
+ return kindsFromSymbol(ts, checker, symbol, targets, seen, options);
149
159
  }
150
160
 
151
161
  /** 'unproven framework' | 'multiple framework' | the one kind | null, for a decorator whose expression constructs. */
@@ -153,7 +163,8 @@ export function constructedDecoratorKind(ts, checker, decorator, targets, traceO
153
163
  const callee = ts.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
154
164
  const kinds = tracedFrameworkKinds(ts, checker, callee, targets, traceOpts);
155
165
  if (kinds.has(UNPROVEN_FRAMEWORK)) return 'unproven framework';
156
- return kinds.size === 1 ? [...kinds][0] : kinds.size ? 'multiple framework' : null;
166
+ if (kinds.size === 1) return [...kinds][0];
167
+ return kinds.size ? 'multiple framework' : null;
157
168
  }
158
169
 
159
170
  /** The target a decorator resolves to through a `let`/`var` alias's single initializer, or null. */
@@ -163,7 +174,7 @@ export function mutableDecoratorKind(ts, checker, decorator, targets) {
163
174
  const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
164
175
  const declarations = symbol?.getDeclarations?.() ?? [];
165
176
  if (declarations.length !== 1 || !ts.isVariableDeclaration(declarations[0]) || !declarations[0].initializer
166
- || (ts.getCombinedNodeFlags(declarations[0].parent) & ts.NodeFlags.Const)) return null;
177
+ || isConstVariable(ts, declarations[0])) return null;
167
178
  return targets.get(valueSymbol(ts, checker, declarations[0].initializer)) ?? null;
168
179
  }
169
180
 
@@ -178,11 +189,12 @@ export function decoratorCallee(ts, decorator) {
178
189
  * not resolve. Null when the statement names no static specifier.
179
190
  */
180
191
  export function moduleExportsOf(ts, checker, statement) {
181
- const moduleSpecifier = (ts.isImportDeclaration(statement) || ts.isExportDeclaration(statement))
182
- && statement.moduleSpecifier && ts.isStringLiteralLike(statement.moduleSpecifier) ? statement.moduleSpecifier : null;
183
- const importEquals = ts.isImportEqualsDeclaration(statement) && ts.isExternalModuleReference(statement.moduleReference)
184
- && statement.moduleReference.expression && ts.isStringLiteralLike(statement.moduleReference.expression)
185
- ? statement.moduleReference.expression : null;
192
+ let moduleSpecifier = null;
193
+ if ((ts.isImportDeclaration(statement) || ts.isExportDeclaration(statement))
194
+ && statement.moduleSpecifier && ts.isStringLiteralLike(statement.moduleSpecifier)) moduleSpecifier = statement.moduleSpecifier;
195
+ let importEquals = null;
196
+ if (ts.isImportEqualsDeclaration(statement) && ts.isExternalModuleReference(statement.moduleReference)
197
+ && statement.moduleReference.expression && ts.isStringLiteralLike(statement.moduleReference.expression)) importEquals = statement.moduleReference.expression;
186
198
  const specifierNode = moduleSpecifier ?? importEquals;
187
199
  const specifier = specifierNode?.text;
188
200
  if (specifier == null) return null;
@@ -208,7 +220,8 @@ export function programSourcesOf(context, checker, localFiles, { root = null } =
208
220
  * The CommonJS require() caveat every framework scan reports: each `require('<specifier>')` `accepted` pushes
209
221
  * `${filePath} uses a CommonJS <label ?? specifier> <detail>` (the import-shape reasoning cannot prove that binding).
210
222
  */
211
- export function commonJsRequireReasons(ts, checker, sourceFile, accepted, reasons, filePath, detail, label = null) {
223
+ export function commonJsRequireReasons(ts, checker, sourceFile, accepted, reasons, filePath, detail) {
224
+ const label = arguments[7] ?? null;
212
225
  const visit = node => {
213
226
  if (ts.isCallExpression(node) && isUnshadowedCommonJsRequire(ts, checker, node.expression)
214
227
  && node.arguments.length === 1 && ts.isStringLiteralLike(node.arguments[0]) && accepted(node.arguments[0].text)) {