@intentius/chant 0.101.0 → 0.102.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 (427) hide show
  1. package/dist/agents/checks.d.ts.map +1 -1
  2. package/dist/attrref.d.ts.map +1 -1
  3. package/dist/audit/discover.d.ts.map +1 -1
  4. package/dist/audit/nginx.d.ts.map +1 -1
  5. package/dist/audit/prior-art.d.ts +48 -0
  6. package/dist/audit/prior-art.d.ts.map +1 -1
  7. package/dist/behaviour-kinds.d.ts.map +1 -1
  8. package/dist/builder.d.ts.map +1 -1
  9. package/dist/cli/commands/carve-emit.d.ts.map +1 -1
  10. package/dist/cli/commands/check-lexicon.d.ts +16 -0
  11. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  12. package/dist/cli/commands/import.d.ts +8 -0
  13. package/dist/cli/commands/import.d.ts.map +1 -1
  14. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  15. package/dist/cli/commands/init.d.ts +19 -0
  16. package/dist/cli/commands/init.d.ts.map +1 -1
  17. package/dist/cli/commands/onboard.d.ts +9 -0
  18. package/dist/cli/commands/onboard.d.ts.map +1 -1
  19. package/dist/cli/commands/pinned-upgrade.d.ts +19 -5
  20. package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -1
  21. package/dist/cli/handlers/dev.d.ts.map +1 -1
  22. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  23. package/dist/cli/handlers/misc.d.ts.map +1 -1
  24. package/dist/cli/handlers/operator.d.ts +9 -0
  25. package/dist/cli/handlers/operator.d.ts.map +1 -1
  26. package/dist/cli/handlers/run.d.ts.map +1 -1
  27. package/dist/cli/lsp/server.d.ts.map +1 -1
  28. package/dist/cli/main.d.ts.map +1 -1
  29. package/dist/cli/mcp/server.d.ts.map +1 -1
  30. package/dist/cli/mcp/tools/build.d.ts.map +1 -1
  31. package/dist/cli/mcp/tools/composites.d.ts.map +1 -1
  32. package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
  33. package/dist/cli/mcp/tools/import.d.ts.map +1 -1
  34. package/dist/cli/mcp/tools/lint.d.ts.map +1 -1
  35. package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -1
  36. package/dist/cli/mcp/tools/search.d.ts.map +1 -1
  37. package/dist/cli/mcp/workspace-tools.d.ts +1 -1
  38. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  39. package/dist/cli/registry.d.ts +11 -2
  40. package/dist/cli/registry.d.ts.map +1 -1
  41. package/dist/codegen/fetch.d.ts.map +1 -1
  42. package/dist/codegen/naming.d.ts.map +1 -1
  43. package/dist/codegen/pinned-upgrade.d.ts +67 -3
  44. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  45. package/dist/codegen/registry.d.ts.map +1 -1
  46. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  47. package/dist/components/capability.d.ts.map +1 -1
  48. package/dist/components/driver.d.ts.map +1 -1
  49. package/dist/components/fan-out.d.ts.map +1 -1
  50. package/dist/components/starter-plugin.d.ts.map +1 -1
  51. package/dist/components/verbs/process-runner.d.ts.map +1 -1
  52. package/dist/components/verbs/r2-sync.d.ts.map +1 -1
  53. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  54. package/dist/components/verbs/sbom-generator.d.ts.map +1 -1
  55. package/dist/components/verbs/sign.d.ts.map +1 -1
  56. package/dist/components/verbs/verify.d.ts.map +1 -1
  57. package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
  58. package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
  59. package/dist/components/verbs/wrangler.d.ts.map +1 -1
  60. package/dist/composite.d.ts.map +1 -1
  61. package/dist/config.d.ts +19 -7
  62. package/dist/config.d.ts.map +1 -1
  63. package/dist/declarable.d.ts +16 -0
  64. package/dist/declarable.d.ts.map +1 -1
  65. package/dist/discovery/cache.d.ts.map +1 -1
  66. package/dist/discovery/fold-import.d.ts +15 -5
  67. package/dist/discovery/fold-import.d.ts.map +1 -1
  68. package/dist/discovery/param-deps.d.ts +16 -0
  69. package/dist/discovery/param-deps.d.ts.map +1 -1
  70. package/dist/discovery/sandbox/policy-wire.d.ts.map +1 -1
  71. package/dist/discovery/specifier-resolve.d.ts +37 -0
  72. package/dist/discovery/specifier-resolve.d.ts.map +1 -0
  73. package/dist/errors.d.ts.map +1 -1
  74. package/dist/fold/fold.d.ts +3 -1
  75. package/dist/fold/fold.d.ts.map +1 -1
  76. package/dist/graph-detail.d.ts.map +1 -1
  77. package/dist/graph-ir.d.ts.map +1 -1
  78. package/dist/graph-layout.d.ts.map +1 -1
  79. package/dist/import/base-parser.d.ts.map +1 -1
  80. package/dist/import/embedded.d.ts +7 -0
  81. package/dist/import/embedded.d.ts.map +1 -1
  82. package/dist/import/parser-options.d.ts +14 -0
  83. package/dist/import/parser-options.d.ts.map +1 -0
  84. package/dist/import/parser.d.ts +17 -0
  85. package/dist/import/parser.d.ts.map +1 -1
  86. package/dist/kubectl-context.d.ts.map +1 -1
  87. package/dist/lexicon-output.d.ts.map +1 -1
  88. package/dist/lexicon-schema.d.ts +3 -3
  89. package/dist/lexicon.d.ts +55 -3
  90. package/dist/lexicon.d.ts.map +1 -1
  91. package/dist/lifecycle/assert-live.d.ts.map +1 -1
  92. package/dist/lifecycle/gate-ledger.d.ts +15 -0
  93. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  94. package/dist/lifecycle/git.d.ts.map +1 -1
  95. package/dist/lifecycle/member-ledger.d.ts.map +1 -1
  96. package/dist/lifecycle/observe.d.ts.map +1 -1
  97. package/dist/lifecycle/plan-ledger.d.ts.map +1 -1
  98. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  99. package/dist/lifecycle/scenario.d.ts.map +1 -1
  100. package/dist/lifecycle/status.d.ts.map +1 -1
  101. package/dist/lifecycle/work-lease.d.ts +61 -2
  102. package/dist/lifecycle/work-lease.d.ts.map +1 -1
  103. package/dist/lint/config.d.ts +8 -8
  104. package/dist/lint/engine.d.ts +6 -0
  105. package/dist/lint/engine.d.ts.map +1 -1
  106. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  107. package/dist/op/activities/activity-contracts.d.ts +11 -11
  108. package/dist/op/activities/apply.d.ts +79 -9
  109. package/dist/op/activities/apply.d.ts.map +1 -1
  110. package/dist/op/activities/decide.d.ts.map +1 -1
  111. package/dist/op/activities/index.d.ts +1 -1
  112. package/dist/op/activities/index.d.ts.map +1 -1
  113. package/dist/op/activities/lexicon-upgrade.d.ts +44 -12
  114. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -1
  115. package/dist/op/activity-contract.d.ts +1 -1
  116. package/dist/op/activity-failure.d.ts.map +1 -1
  117. package/dist/op/activity-profiles.d.ts.map +1 -1
  118. package/dist/op/builders.d.ts.map +1 -1
  119. package/dist/op/composites/apply-op.d.ts +12 -5
  120. package/dist/op/composites/apply-op.d.ts.map +1 -1
  121. package/dist/op/composites/lexicon-upgrade-op.d.ts +22 -6
  122. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -1
  123. package/dist/op/decide-backend.d.ts.map +1 -1
  124. package/dist/op/decide-read-inputs.d.ts.map +1 -1
  125. package/dist/op/emulator-lifecycle.d.ts +21 -2
  126. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  127. package/dist/op/gate-approval.d.ts +5 -1
  128. package/dist/op/gate-approval.d.ts.map +1 -1
  129. package/dist/op/gate-wait.d.ts.map +1 -1
  130. package/dist/op/gate.d.ts +19 -3
  131. package/dist/op/gate.d.ts.map +1 -1
  132. package/dist/op/index.d.ts +2 -0
  133. package/dist/op/index.d.ts.map +1 -1
  134. package/dist/op/local-executor.d.ts +6 -0
  135. package/dist/op/local-executor.d.ts.map +1 -1
  136. package/dist/op/operator.d.ts.map +1 -1
  137. package/dist/op/run-context.d.ts +39 -0
  138. package/dist/op/run-context.d.ts.map +1 -0
  139. package/dist/op/run-live.d.ts.map +1 -1
  140. package/dist/op/runtimes/local.d.ts.map +1 -1
  141. package/dist/op/source-archive.d.ts.map +1 -1
  142. package/dist/op/steward-beside.d.ts.map +1 -1
  143. package/dist/op/steward-points.d.ts.map +1 -1
  144. package/dist/op/steward.d.ts.map +1 -1
  145. package/dist/op/types.d.ts +8 -3
  146. package/dist/op/types.d.ts.map +1 -1
  147. package/dist/op/work-lease-run.d.ts +24 -4
  148. package/dist/op/work-lease-run.d.ts.map +1 -1
  149. package/dist/project-root.d.ts.map +1 -1
  150. package/dist/provenance.d.ts +21 -0
  151. package/dist/provenance.d.ts.map +1 -1
  152. package/dist/pseudo-parameter.d.ts.map +1 -1
  153. package/dist/reconcile.d.ts.map +1 -1
  154. package/dist/secret-materialization.d.ts.map +1 -1
  155. package/dist/terraform/emit-source.d.ts.map +1 -1
  156. package/dist/terraform/parse.d.ts.map +1 -1
  157. package/dist/testing.d.ts.map +1 -1
  158. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  159. package/dist/workspace/__fixtures__/decide-workspace.d.ts +1 -1
  160. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -1
  161. package/dist/workspace/agent-cli.d.ts +5 -0
  162. package/dist/workspace/agent-cli.d.ts.map +1 -1
  163. package/dist/workspace/box-factory.d.ts +73 -0
  164. package/dist/workspace/box-factory.d.ts.map +1 -0
  165. package/dist/workspace/changes.d.ts.map +1 -1
  166. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -1
  167. package/dist/workspace/checks/boxes.d.ts.map +1 -1
  168. package/dist/workspace/checks.d.ts +5 -0
  169. package/dist/workspace/checks.d.ts.map +1 -1
  170. package/dist/workspace/compose-graph.d.ts +10 -0
  171. package/dist/workspace/compose-graph.d.ts.map +1 -1
  172. package/dist/workspace/conformance/index.d.ts +67 -6
  173. package/dist/workspace/conformance/index.d.ts.map +1 -1
  174. package/dist/workspace/conformance/vitest.d.ts +12 -3
  175. package/dist/workspace/conformance/vitest.d.ts.map +1 -1
  176. package/dist/workspace/conformance/writer.d.ts +368 -0
  177. package/dist/workspace/conformance/writer.d.ts.map +1 -0
  178. package/dist/workspace/decide.d.ts +1 -1
  179. package/dist/workspace/decide.d.ts.map +1 -1
  180. package/dist/workspace/declaration.d.ts +91 -4
  181. package/dist/workspace/declaration.d.ts.map +1 -1
  182. package/dist/workspace/declaration.schema.json +234 -5
  183. package/dist/workspace/generated-files.d.ts.map +1 -1
  184. package/dist/workspace/graph-cache.d.ts.map +1 -1
  185. package/dist/workspace/graph-cli.d.ts +2 -0
  186. package/dist/workspace/graph-cli.d.ts.map +1 -1
  187. package/dist/workspace/identity.d.ts +111 -0
  188. package/dist/workspace/identity.d.ts.map +1 -0
  189. package/dist/workspace/intent-cli.d.ts +7 -4
  190. package/dist/workspace/intent-cli.d.ts.map +1 -1
  191. package/dist/workspace/intent-record.d.ts +8 -1
  192. package/dist/workspace/intent-record.d.ts.map +1 -1
  193. package/dist/workspace/intent.d.ts +149 -5
  194. package/dist/workspace/intent.d.ts.map +1 -1
  195. package/dist/workspace/kinds.d.ts +18 -4
  196. package/dist/workspace/kinds.d.ts.map +1 -1
  197. package/dist/workspace/lineage-hash-index.d.ts.map +1 -1
  198. package/dist/workspace/lineage-lock.d.ts +2 -2
  199. package/dist/workspace/lineage-lock.d.ts.map +1 -1
  200. package/dist/workspace/links.d.ts.map +1 -1
  201. package/dist/workspace/points.d.ts.map +1 -1
  202. package/dist/workspace/principal-classes.d.ts +114 -0
  203. package/dist/workspace/principal-classes.d.ts.map +1 -0
  204. package/dist/workspace/reason-codes.d.ts +21 -1
  205. package/dist/workspace/reason-codes.d.ts.map +1 -1
  206. package/dist/workspace/record-assets.d.ts.map +1 -1
  207. package/dist/workspace/records-checkout.d.ts +54 -0
  208. package/dist/workspace/records-checkout.d.ts.map +1 -0
  209. package/dist/workspace/records-cli.d.ts +27 -1
  210. package/dist/workspace/records-cli.d.ts.map +1 -1
  211. package/dist/workspace/records-close.d.ts +1 -1
  212. package/dist/workspace/records-close.d.ts.map +1 -1
  213. package/dist/workspace/records-since.d.ts.map +1 -1
  214. package/dist/workspace/records-write.d.ts +5 -4
  215. package/dist/workspace/records-write.d.ts.map +1 -1
  216. package/dist/workspace/records.d.ts +15 -0
  217. package/dist/workspace/records.d.ts.map +1 -1
  218. package/dist/workspace/runs-cli.d.ts +164 -0
  219. package/dist/workspace/runs-cli.d.ts.map +1 -0
  220. package/dist/workspace/runs.d.ts +423 -0
  221. package/dist/workspace/runs.d.ts.map +1 -0
  222. package/dist/workspace/source-block.d.ts +1 -1
  223. package/dist/workspace/source-block.d.ts.map +1 -1
  224. package/dist/workspace/status-gates.d.ts +15 -3
  225. package/dist/workspace/status-gates.d.ts.map +1 -1
  226. package/dist/workspace/status.d.ts +10 -0
  227. package/dist/workspace/status.d.ts.map +1 -1
  228. package/dist/workspace/symbols.d.ts +52 -0
  229. package/dist/workspace/symbols.d.ts.map +1 -0
  230. package/dist/workspace/trailer-joins.d.ts +46 -0
  231. package/dist/workspace/trailer-joins.d.ts.map +1 -0
  232. package/dist/workspace/trailers.d.ts +89 -0
  233. package/dist/workspace/trailers.d.ts.map +1 -0
  234. package/dist/workspace/trust/attestor.d.ts.map +1 -1
  235. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  236. package/dist/workspace/trust/seal.d.ts +33 -0
  237. package/dist/workspace/trust/seal.d.ts.map +1 -1
  238. package/dist/workspace/trust/test-repo.d.ts.map +1 -1
  239. package/dist/workspace/work-cli.d.ts +39 -3
  240. package/dist/workspace/work-cli.d.ts.map +1 -1
  241. package/dist/workspace/work-evidence.d.ts +1 -1
  242. package/dist/workspace/work-evidence.d.ts.map +1 -1
  243. package/dist/workspace/work.d.ts +26 -1
  244. package/dist/workspace/work.d.ts.map +1 -1
  245. package/dist/workspace/workspace-principals.schema.json +60 -0
  246. package/dist/workspace/write-scope.d.ts +79 -19
  247. package/dist/workspace/write-scope.d.ts.map +1 -1
  248. package/dist/yaml.d.ts +21 -39
  249. package/dist/yaml.d.ts.map +1 -1
  250. package/package.json +2 -2
  251. package/src/audit/prior-art.ts +15 -0
  252. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
  253. package/src/cli/commands/carve-emit-state.test.ts +4 -0
  254. package/src/cli/commands/carve-emit.ts +5 -19
  255. package/src/cli/commands/check-lexicon.test.ts +62 -1
  256. package/src/cli/commands/check-lexicon.ts +36 -6
  257. package/src/cli/commands/import-live.test.ts +18 -0
  258. package/src/cli/commands/import-parser-options.test.ts +133 -0
  259. package/src/cli/commands/import.ts +21 -3
  260. package/src/cli/commands/init-lexicon/templates/project.ts +1 -1
  261. package/src/cli/commands/init.test.ts +8 -1
  262. package/src/cli/commands/init.ts +23 -8
  263. package/src/cli/commands/lint.test.ts +37 -0
  264. package/src/cli/commands/onboard.test.ts +53 -3
  265. package/src/cli/commands/onboard.ts +52 -4
  266. package/src/cli/commands/pinned-upgrade.ts +73 -33
  267. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +2 -2
  268. package/src/cli/handlers/components.ts +2 -2
  269. package/src/cli/handlers/dev.ts +8 -6
  270. package/src/cli/handlers/lifecycle.test.ts +67 -0
  271. package/src/cli/handlers/lifecycle.ts +6 -5
  272. package/src/cli/handlers/misc.ts +2 -0
  273. package/src/cli/handlers/operator.ts +87 -3
  274. package/src/cli/handlers/run.test.ts +111 -0
  275. package/src/cli/handlers/run.ts +85 -24
  276. package/src/cli/handlers/scenario.ts +2 -2
  277. package/src/cli/main.test.ts +1 -0
  278. package/src/cli/main.ts +65 -11
  279. package/src/cli/mcp/workspace-tools.test.ts +5 -2
  280. package/src/cli/mcp/workspace-tools.ts +29 -2
  281. package/src/cli/registry.ts +11 -2
  282. package/src/codegen/pinned-upgrade-multi.test.ts +157 -0
  283. package/src/codegen/pinned-upgrade-tags.test.ts +112 -0
  284. package/src/codegen/pinned-upgrade.test.ts +104 -0
  285. package/src/codegen/pinned-upgrade.ts +250 -56
  286. package/src/codegen/testdata/postgres-tags.json +242 -0
  287. package/src/config.test.ts +21 -0
  288. package/src/config.ts +15 -0
  289. package/src/declarable.test.ts +21 -0
  290. package/src/declarable.ts +19 -0
  291. package/src/discovery/fold-composite.test.ts +41 -5
  292. package/src/discovery/fold-import.test.ts +129 -0
  293. package/src/discovery/fold-import.ts +643 -131
  294. package/src/discovery/fold-package-composite.test.ts +264 -0
  295. package/src/discovery/fold-specifiers.test.ts +191 -0
  296. package/src/discovery/param-deps.test.ts +66 -1
  297. package/src/discovery/param-deps.ts +45 -0
  298. package/src/discovery/specifier-resolve.ts +344 -0
  299. package/src/fold/fold.test.ts +39 -1
  300. package/src/fold/fold.ts +83 -9
  301. package/src/fold-provenance.test.ts +140 -0
  302. package/src/graph-ir.test.ts +83 -0
  303. package/src/graph-ir.ts +29 -0
  304. package/src/import/embedded.test.ts +20 -0
  305. package/src/import/embedded.ts +13 -0
  306. package/src/import/parser-options.ts +46 -0
  307. package/src/import/parser.ts +20 -0
  308. package/src/lexicon.ts +55 -3
  309. package/src/lifecycle/gate-ledger.ts +16 -0
  310. package/src/lifecycle/observe.test.ts +18 -0
  311. package/src/lifecycle/observe.ts +6 -4
  312. package/src/lifecycle/snapshot.ts +2 -2
  313. package/src/lifecycle/work-lease.test.ts +1 -1
  314. package/src/lifecycle/work-lease.ts +93 -2
  315. package/src/lint/engine.test.ts +55 -0
  316. package/src/lint/engine.ts +30 -4
  317. package/src/lint/output-docs.test.ts +59 -56
  318. package/src/op/activities/apply.test.ts +189 -5
  319. package/src/op/activities/apply.ts +230 -8
  320. package/src/op/activities/index.ts +3 -0
  321. package/src/op/activities/lexicon-upgrade.test.ts +160 -6
  322. package/src/op/activities/lexicon-upgrade.ts +154 -51
  323. package/src/op/composites/apply-op.ts +12 -5
  324. package/src/op/composites/composites.test.ts +30 -3
  325. package/src/op/composites/lexicon-upgrade-op.test.ts +7 -2
  326. package/src/op/composites/lexicon-upgrade-op.ts +29 -8
  327. package/src/op/emulator-lifecycle.test.ts +23 -0
  328. package/src/op/emulator-lifecycle.ts +52 -14
  329. package/src/op/gate-approval.test.ts +74 -0
  330. package/src/op/gate-approval.ts +5 -1
  331. package/src/op/gate.ts +31 -5
  332. package/src/op/index.ts +2 -0
  333. package/src/op/local-executor.test.ts +15 -0
  334. package/src/op/local-executor.ts +40 -12
  335. package/src/op/local-output.ts +1 -1
  336. package/src/op/operator.ts +4 -0
  337. package/src/op/run-context.test.ts +141 -0
  338. package/src/op/run-context.ts +51 -0
  339. package/src/op/runtimes/local.test.ts +14 -0
  340. package/src/op/runtimes/local.ts +2 -0
  341. package/src/op/steward-beside.ts +2 -0
  342. package/src/op/types.ts +8 -3
  343. package/src/op/work-lease-run.test.ts +47 -0
  344. package/src/op/work-lease-run.ts +73 -11
  345. package/src/provenance.ts +35 -0
  346. package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
  347. package/src/workspace/agent-cli.ts +7 -3
  348. package/src/workspace/agent.schema.json +22 -0
  349. package/src/workspace/box-factory.test.ts +320 -0
  350. package/src/workspace/box-factory.ts +103 -0
  351. package/src/workspace/changes.schema.json +12 -20
  352. package/src/workspace/changes.ts +4 -1
  353. package/src/workspace/checks/boxes.test.ts +2 -0
  354. package/src/workspace/checks.ts +29 -5
  355. package/src/workspace/compose-graph.ts +7 -0
  356. package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +27 -0
  357. package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +95 -0
  358. package/src/workspace/conformance/__writer_fixture__/decisions/points.json +132 -0
  359. package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +48 -0
  360. package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +108 -0
  361. package/src/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
  362. package/src/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
  363. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +578 -0
  364. package/src/workspace/conformance/conformance.e2e.test.ts +35 -3
  365. package/src/workspace/conformance/index.mjs +30 -3
  366. package/src/workspace/conformance/index.ts +103 -15
  367. package/src/workspace/conformance/vitest.ts +44 -2
  368. package/src/workspace/conformance/writer-conformance.e2e.test.ts +131 -0
  369. package/src/workspace/conformance/writer.test.ts +156 -0
  370. package/src/workspace/conformance/writer.ts +880 -0
  371. package/src/workspace/decide.ts +7 -1
  372. package/src/workspace/declaration.schema.json +234 -5
  373. package/src/workspace/declaration.ts +174 -10
  374. package/src/workspace/graph-cli.ts +5 -2
  375. package/src/workspace/graph.schema.json +247 -0
  376. package/src/workspace/identity.test.ts +196 -0
  377. package/src/workspace/identity.ts +186 -0
  378. package/src/workspace/intent-cli.ts +64 -10
  379. package/src/workspace/intent-record.schema.json +208 -0
  380. package/src/workspace/intent-record.ts +35 -1
  381. package/src/workspace/intent-why.test.ts +274 -0
  382. package/src/workspace/intent.schema.json +718 -7
  383. package/src/workspace/intent.test.ts +11 -2
  384. package/src/workspace/intent.ts +495 -32
  385. package/src/workspace/kinds.ts +44 -37
  386. package/src/workspace/points-write.schema.json +1 -0
  387. package/src/workspace/principal-classes.test.ts +175 -0
  388. package/src/workspace/principal-classes.ts +307 -0
  389. package/src/workspace/reason-codes.test.ts +11 -3
  390. package/src/workspace/reason-codes.ts +24 -1
  391. package/src/workspace/records-amend.schema.json +2 -0
  392. package/src/workspace/records-checkout.ts +106 -0
  393. package/src/workspace/records-cli.ts +103 -11
  394. package/src/workspace/records-close.schema.json +1 -0
  395. package/src/workspace/records-new.schema.json +2 -0
  396. package/src/workspace/records-review.schema.json +2 -0
  397. package/src/workspace/records-uncommitted.test.ts +171 -0
  398. package/src/workspace/records-write.ts +28 -6
  399. package/src/workspace/records.schema.json +72 -1
  400. package/src/workspace/records.ts +27 -0
  401. package/src/workspace/runs-cli.ts +381 -0
  402. package/src/workspace/runs-write.schema.json +583 -0
  403. package/src/workspace/runs.schema.json +810 -0
  404. package/src/workspace/runs.test.ts +242 -0
  405. package/src/workspace/runs.ts +709 -0
  406. package/src/workspace/status-contract.test.ts +5 -0
  407. package/src/workspace/status-gates.ts +22 -8
  408. package/src/workspace/status.schema.json +127 -2
  409. package/src/workspace/status.ts +21 -1
  410. package/src/workspace/symbols.ts +180 -0
  411. package/src/workspace/trailer-joins.test.ts +171 -0
  412. package/src/workspace/trailer-joins.ts +82 -0
  413. package/src/workspace/trailers.ts +148 -0
  414. package/src/workspace/trust/seal.ts +52 -1
  415. package/src/workspace/work-cli.e2e.test.ts +28 -1
  416. package/src/workspace/work-cli.ts +133 -8
  417. package/src/workspace/work-evidence.schema.json +2 -0
  418. package/src/workspace/work-history.schema.json +73 -1
  419. package/src/workspace/work-history.test.ts +48 -5
  420. package/src/workspace/work-kind-fields.test.ts +154 -0
  421. package/src/workspace/work-lease.schema.json +14 -1
  422. package/src/workspace/work.ts +82 -0
  423. package/src/workspace/workspace-principals.schema.json +60 -0
  424. package/src/workspace/write-scope.test.ts +128 -2
  425. package/src/workspace/write-scope.ts +170 -45
  426. package/src/yaml.test.ts +111 -1
  427. package/src/yaml.ts +141 -549
package/src/yaml.ts CHANGED
@@ -1,14 +1,15 @@
1
1
  /**
2
- * Lightweight YAML emitter and parser.
2
+ * YAML emitter and parser.
3
3
  *
4
- * Covers the subset of YAML used by Chant lexicons (scalars, block arrays,
5
- * nested objects, block scalars, tagged values, anchors, aliases and merge
6
- * keys). Not a full YAML implementation: flow collections are read only when
7
- * they are JSON. `splitYAMLDocuments` splits a multi-document stream. The
8
- * parser throws a `YAMLParseError` on a line it cannot place instead of
9
- * dropping it (#2991).
4
+ * The emitter writes the block-style YAML chant's serializers produce. The
5
+ * parser is js-yaml with chant's schema (#3006): YAML 1.2 core types,
6
+ * merge keys, `yes`/`no` booleans, and local tags such as `!reference` read
7
+ * as strings. `splitYAMLDocuments` splits a multi-document stream. Input the
8
+ * parser cannot read throws a `YAMLParseError` naming the line.
10
9
  */
11
10
 
11
+ import yaml from "js-yaml";
12
+
12
13
  // ---------------------------------------------------------------------------
13
14
  // Emitter
14
15
  // ---------------------------------------------------------------------------
@@ -79,7 +80,13 @@ export function emitYAML(value: unknown, indent: number): string {
79
80
  value.startsWith("'") ||
80
81
  value.startsWith('"') ||
81
82
  value.startsWith("$") ||
82
- /^\d/.test(value)
83
+ /^\d/.test(value) ||
84
+ // What a YAML parser reads as something other than this string: an
85
+ // indicator that cannot start a plain scalar, a trailing colon, edge
86
+ // whitespace, a signed or dotted number, and the other spellings of
87
+ // booleans, null and the float constants (#3006).
88
+ /^[-?:](?:\s|$)|^[,\]}|>%@`]|:$|^\s|\s$|^[-+.]\d/.test(value) ||
89
+ /^(?:true|false|null|~|yes|no|[-+]?\.(?:inf|nan))$/i.test(value)
83
90
  ) {
84
91
  // Use single quotes, escaping internal single quotes
85
92
  return `'${value.replace(/'/g, "''")}'`;
@@ -147,21 +154,24 @@ export function emitYAML(value: unknown, indent: number): string {
147
154
  return String(value);
148
155
  }
149
156
 
157
+ /**
158
+ * `key: value` at column `indent * 2`, for a serializer that writes a
159
+ * document key by key. A block value follows the colon on the next line; an
160
+ * inline one (a scalar, `{}`, `[]`) needs a space after the colon, or
161
+ * `jobs:{}` is the plain scalar "jobs:{}" rather than a key (#3006).
162
+ */
163
+ export function emitYAMLEntry(key: string, value: unknown, indent = 0): string {
164
+ const emitted = emitYAML(value, indent + 1);
165
+ return `${" ".repeat(indent)}${key}:${emitted.startsWith("\n") ? "" : " "}${emitted}`;
166
+ }
167
+
150
168
  // ---------------------------------------------------------------------------
151
169
  // Parser
152
170
  // ---------------------------------------------------------------------------
153
171
 
154
- /** Result of parsing a YAML block. */
155
- export interface ParseResult {
156
- value: unknown;
157
- endIndex: number;
158
- }
159
-
160
172
  /**
161
- * Input the parser cannot place (#2991). The parser used to skip such a line,
162
- * or read it as a key of whatever mapping was open at the top level, so a
163
- * mis-indented or unsupported construct came back as a quietly different
164
- * document. Now it is an error naming the line.
173
+ * A document the parser cannot read. Its message and `line` name the line,
174
+ * so `chant import` can report "YAML line N: ..." instead of a stack trace.
165
175
  */
166
176
  export class YAMLParseError extends Error {
167
177
  /** 1-based line number within the text handed to the parser. */
@@ -174,157 +184,141 @@ export class YAMLParseError extends Error {
174
184
  }
175
185
  }
176
186
 
177
- /**
178
- * A mapping key: a double-quoted or single-quoted scalar, which may hold a
179
- * colon, or a plain run of text up to the first colon.
180
- */
181
- const KEY = String.raw`("(?:[^"\\]|\\.)*"|'(?:[^']|'')*'|[^\s:"'][^:]*?)`;
187
+ /** A document start (`---`) or end (`...`) marker, optionally followed by a comment. */
188
+ const DOCUMENT_MARKER = /^(?:---|\.\.\.)(?:[ \t]+#.*)?[ \t]*$/;
182
189
 
183
190
  /**
184
- * `key: value` on a line of its own (`KEY_LINE`, capturing the indent) and the
185
- * same after a sequence item's `- ` (`ITEM_KEY`).
186
- *
187
- * The lookahead is the rule both patterns used to miss: a `:` opens a mapping
188
- * only when what follows it is whitespace or the end of the line, and a plain
189
- * scalar may carry any other colon (YAML 1.2 §7.4.2). Written as `:\s*`, which
190
- * matches zero whitespace, EVERY colon opened one — so a bare URL in a
191
- * sequence,
192
- *
193
- * sourceRepos:
194
- * - https://github.com/INTENTIUS/behold
195
- *
196
- * came back as `{ https: "//github.com/INTENTIUS/behold" }`, and the CRD-schema
197
- * post-synth check (#1372) re-reading emitted YAML reported the string list
198
- * this file's own emitter had just written as a list of objects (#2013). By the
199
- * same rule `- key:value` is the scalar `"key:value"`, not a mapping.
191
+ * Booleans as YAML 1.2's core schema reads them, plus `yes` and `no`, which
192
+ * this parser has always read as booleans and its callers expect.
200
193
  */
201
- const KEY_LINE = new RegExp(String.raw`^(\s*)${KEY}:(?=\s|$)\s*(.*)$`);
202
- const ITEM_KEY = new RegExp(String.raw`^${KEY}:(?=\s|$)\s*(.*)$`);
194
+ const BOOL = new yaml.Type("tag:yaml.org,2002:bool", {
195
+ kind: "scalar",
196
+ resolve: (data: string | null) => data !== null && /^(?:true|True|TRUE|false|False|FALSE|yes|no)$/.test(data),
197
+ construct: (data: string) => /^(?:true|True|TRUE|yes)$/.test(data),
198
+ predicate: (value: unknown) => typeof value === "boolean",
199
+ represent: { lowercase: (value: object) => (value ? "true" : "false") },
200
+ defaultStyle: "lowercase",
201
+ });
202
+
203
+ /** A tagged value written back as text, the way it appeared: `!reference [.base, script]`. */
204
+ function flowText(value: unknown): string {
205
+ if (Array.isArray(value)) return `[${value.map(flowText).join(", ")}]`;
206
+ if (typeof value === "object" && value !== null) {
207
+ return `{${Object.entries(value).map(([k, v]) => `${k}: ${flowText(v)}`).join(", ")}}`;
208
+ }
209
+ return String(value);
210
+ }
203
211
 
204
212
  /**
205
- * A block sequence entry: `- value`, or a dash alone on its line whose value
206
- * is the block below it. `-1` and `---` are not entries.
213
+ * Local tags (`!reference`, `!Ref`, `!GetAtt`) on a scalar or a sequence read
214
+ * as the string `"<tag> <value>"`, as they always have: GitLab CI's
215
+ * `!reference [.base, script]` is the string `"!reference [.base, script]"`.
216
+ * A tag on a mapping has no such form and is an error.
207
217
  */
208
- const SEQ_ENTRY = /^(\s*)-(?:([ \t]+)(.*))?$/;
209
-
210
- /** A document start (`---`) or end (`...`) marker, optionally followed by a comment. */
211
- const DOCUMENT_MARKER = /^(?:---|\.\.\.)(?:[ \t]+#.*)?[ \t]*$/;
212
-
213
- function isBlankOrComment(line: string): boolean {
214
- const t = line.trim();
215
- return t === "" || t.startsWith("#");
216
- }
217
-
218
- /** The first line at or after `from` that holds content, or `lines.length`. */
219
- function nextContentLine(lines: string[], from: number): number {
220
- let i = from;
221
- while (i < lines.length && isBlankOrComment(lines[i])) i++;
222
- return i;
223
- }
224
-
225
- function indentOf(line: string): number {
226
- return line.search(/\S/);
227
- }
228
-
229
- function isSeqEntry(line: string): boolean {
230
- return SEQ_ENTRY.test(line);
231
- }
232
-
233
- /** A key as written, with the quotes and escapes of a quoted key removed. */
234
- function keyName(raw: string): string {
235
- const key = raw.trim();
236
- return key.startsWith('"') || key.startsWith("'") ? String(parseScalar(key)) : key;
237
- }
218
+ const TAGGED_SCALAR = new yaml.Type("!", {
219
+ kind: "scalar",
220
+ multi: true,
221
+ construct: (data: string | null, tag?: string) => (data === null || data === "" ? tag : `${tag} ${data}`),
222
+ });
223
+ const TAGGED_SEQUENCE = new yaml.Type("!", {
224
+ kind: "sequence",
225
+ multi: true,
226
+ construct: (data: unknown[] | null, tag?: string) => `${tag} ${flowText(data ?? [])}`,
227
+ });
228
+
229
+ /** The merge key `<<`, as js-yaml's own merge type reads it (its typings omit `types`). */
230
+ const MERGE = new yaml.Type("tag:yaml.org,2002:merge", {
231
+ kind: "scalar",
232
+ resolve: (data: string | null) => data === "<<" || data === null,
233
+ });
238
234
 
239
235
  /**
240
- * Split text into lines for the parser: `\r\n` and `\r` become `\n`, and a
241
- * document marker becomes a blank line. `parseYAML` has always read a
242
- * `---`-led file (a compose file, a GitLab CI file) by skipping the marker;
243
- * that is kept, now that a line the parser cannot place is an error.
236
+ * YAML 1.2's core schema (no timestamps, so a date stays a string) with merge
237
+ * keys, `yes`/`no` booleans and local tags read as strings.
244
238
  */
245
- function toLines(content: string): string[] {
246
- return content
247
- .replace(/\r\n?/g, "\n")
248
- .split("\n")
249
- .map((line) => (DOCUMENT_MARKER.test(line) ? "" : line));
250
- }
239
+ const SCHEMA = yaml.CORE_SCHEMA.extend({
240
+ implicit: [BOOL, MERGE],
241
+ explicit: [TAGGED_SCALAR, TAGGED_SEQUENCE],
242
+ });
251
243
 
252
244
  /**
253
- * Parse a whole document with `parse`, starting at its first content line
254
- * `first`, and require it to account for every line (#2991). The recursive
255
- * parsers stop at a line they cannot place and hand it back to their caller;
256
- * one that reaches the top is unplaceable, and throws.
245
+ * Give every alias its own copy of the anchored value. js-yaml shares one
246
+ * object between an anchor and its aliases, so a caller editing the alias
247
+ * would edit the anchor too.
257
248
  */
258
- function parseWhole(
259
- lines: string[],
260
- first: number,
261
- parse: (lines: string[], startIndex: number, baseIndent: number) => ParseResult,
262
- ): unknown {
263
- const outer = anchors;
264
- anchors = new Map();
265
- try {
266
- const result = parse(lines, first, indentOf(lines[first]));
267
- const rest = nextContentLine(lines, result.endIndex);
268
- if (rest < lines.length) {
269
- throw new YAMLParseError(
270
- rest + 1,
271
- `cannot place ${JSON.stringify(lines[rest].trim())} at column ${indentOf(lines[rest]) + 1}; ` +
272
- "check its indentation, or the construct is one this parser does not read",
273
- );
274
- }
275
- return result.value;
276
- } finally {
277
- anchors = outer;
249
+ function unshareAliases(value: unknown, seen = new Set<object>()): unknown {
250
+ if (typeof value !== "object" || value === null) return value;
251
+ if (seen.has(value)) return unshareAliases(structuredClone(value), seen);
252
+ seen.add(value);
253
+ if (Array.isArray(value)) {
254
+ for (let i = 0; i < value.length; i++) value[i] = unshareAliases(value[i], seen);
255
+ } else {
256
+ const obj = value as Record<string, unknown>;
257
+ for (const key of Object.keys(obj)) obj[key] = unshareAliases(obj[key], seen);
278
258
  }
259
+ return value;
279
260
  }
280
261
 
281
262
  /**
282
- * The anchors (`&name`) seen so far in the document being parsed, for its
283
- * aliases (`*name`) and merge keys (`<<: *name`). GitLab CI templates and
284
- * Alertmanager configs use all three; before #2991 an anchored block was
285
- * hoisted into its parent and an alias read as the string `"*name"`.
286
- * `parseWhole` gives each document its own map.
263
+ * The 1-based line to report for a js-yaml error. js-yaml marks where it
264
+ * gave up, which for a stray line of text above a key is the key's line:
265
+ * `a: 1\njust text\nb: 2` fails at `b: 2`, reading `just text b` as one
266
+ * multiline key. Report the line where that key starts instead, the text
267
+ * that is not a key.
287
268
  */
288
- let anchors = new Map<string, unknown>();
289
-
290
- const ANCHOR = /^&([^\s,[\]{}]+)(?:[ \t]+(.*))?$/;
291
- const ALIAS = /^\*([^\s,[\]{}]+)(?:[ \t]+#.*)?$/;
292
- /** A flow sequence of aliases, the usual way to merge several anchors: `<<: [*a, *b]`. */
293
- const ALIAS_LIST = /^\[\s*\*[^\s,[\]{}]+(?:\s*,\s*\*[^\s,[\]{}]+)*\s*\](?:[ \t]+#.*)?$/;
294
-
295
- function resolveAlias(name: string, line: number): unknown {
296
- if (!anchors.has(name)) throw new YAMLParseError(line, `the alias *${name} has no anchor &${name} before it`);
297
- return structuredClone(anchors.get(name));
269
+ function errorLine(lines: string[], err: yaml.YAMLException): number {
270
+ let line = err.mark?.line ?? 0;
271
+ if (/multiline key may not be an implicit key/.test(err.reason)) {
272
+ const indent = lines[line]?.search(/\S/) ?? -1;
273
+ for (let k = line - 1; k >= 0; k--) {
274
+ const prev = lines[k];
275
+ if (prev.trim() === "" || prev.trim().startsWith("#")) continue;
276
+ if (prev.search(/\S/) !== indent || /:(?:\s|$)/.test(prev) || /^\s*-(?:\s|$)/.test(prev)) break;
277
+ line = k;
278
+ }
279
+ }
280
+ return line + 1;
298
281
  }
299
282
 
300
- /** The merge key: its value's mappings fill in the keys a mapping does not set itself. */
301
- const MERGE_KEY = "<<";
302
-
303
283
  /**
304
- * Set `key` on a mapping under construction. A merge key's sources are held
305
- * in `merges` and applied by {@link applyMerges} once the mapping is
306
- * complete, because a key the mapping sets itself wins wherever it appears.
284
+ * Read one YAML document with js-yaml (#3006).
285
+ *
286
+ * Kept from the line parser this replaced: `---` and `...` lines are skipped
287
+ * (so a stream reads as one document, as it always has), duplicate keys take
288
+ * the last value, and a document that is not a mapping or a sequence is a
289
+ * {@link YAMLParseError}, as is anything js-yaml rejects. An empty document is
290
+ * `{}`.
307
291
  */
308
- function setKey(obj: Record<string, unknown>, merges: unknown[], key: string, value: unknown): void {
309
- if (key === MERGE_KEY) merges.push(...(Array.isArray(value) ? value : [value]));
310
- else obj[key] = value;
311
- }
312
-
313
- function applyMerges(obj: Record<string, unknown>, merges: unknown[], line: number): void {
314
- for (const source of merges) {
315
- if (typeof source !== "object" || source === null || Array.isArray(source)) {
316
- throw new YAMLParseError(line, "a merge key (<<) takes a mapping or a list of mappings");
317
- }
318
- for (const [k, v] of Object.entries(source)) if (!(k in obj)) obj[k] = v;
292
+ function loadDocument(content: string): unknown {
293
+ const text = content
294
+ .replace(/\r\n?/g, "\n")
295
+ .split("\n")
296
+ .map((line) => (DOCUMENT_MARKER.test(line) ? "" : line))
297
+ .join("\n");
298
+ let value: unknown;
299
+ try {
300
+ value = yaml.load(text, { schema: SCHEMA, json: true });
301
+ } catch (err) {
302
+ if (!(err instanceof yaml.YAMLException)) throw err;
303
+ const line = errorLine(text.split("\n"), err);
304
+ const tag = err.reason.match(/^unknown tag !<(![^>]*)>/)?.[1];
305
+ throw new YAMLParseError(line, tag ? `the tag ${JSON.stringify(tag)} on a mapping is not supported` : err.reason);
306
+ }
307
+ if (value === undefined || value === null) return {};
308
+ if (typeof value !== "object") {
309
+ const line = text.split("\n").findIndex((l) => l.trim() !== "" && !l.trim().startsWith("#")) + 1;
310
+ throw new YAMLParseError(line, `expected a mapping or a sequence, found the scalar ${JSON.stringify(value)}`);
319
311
  }
312
+ return /[&]/.test(text) ? unshareAliases(value) : value;
320
313
  }
321
314
 
322
315
  /**
323
316
  * Parse a YAML document (or JSON document) into a plain object.
324
317
  *
325
- * Tries `JSON.parse` first; falls back to a line-based YAML parser that
326
- * handles the subset of YAML commonly found in CI configuration files. Throws
327
- * {@link YAMLParseError} on a line it cannot place rather than dropping it.
318
+ * Tries `JSON.parse` first, then js-yaml (#3006). A top-level sequence reads
319
+ * as `{}`, since callers rely on getting a mapping back; use
320
+ * {@link parseYAMLDocument} for the list. Throws {@link YAMLParseError} with
321
+ * the line number on input it cannot read.
328
322
  */
329
323
  export function parseYAML(content: string): Record<string, unknown> {
330
324
  try {
@@ -332,21 +326,14 @@ export function parseYAML(content: string): Record<string, unknown> {
332
326
  } catch {
333
327
  // Fall through to YAML parsing
334
328
  }
335
-
336
- const lines = toLines(content);
337
- const first = nextContentLine(lines, 0);
338
- // A document with no content, or (see parseYAMLDocument) a top-level list,
339
- // reads as an empty mapping; the callers rely on getting a mapping back.
340
- if (first === lines.length || isSeqEntry(lines[first])) return {};
341
- return parseWhole(lines, first, parseYAMLLines) as Record<string, unknown>;
329
+ const value = loadDocument(content);
330
+ return Array.isArray(value) ? {} : (value as Record<string, unknown>);
342
331
  }
343
332
 
344
333
  /**
345
334
  * Parse one YAML (or JSON) document whose top level may be a sequence
346
- * (#2965). {@link parseYAML} reads only a top-level mapping and returns `{}`
347
- * for a file like `- a\n- b`; its callers rely on getting a mapping back, so
348
- * it keeps doing that. This returns the list instead, and otherwise defers to
349
- * `parseYAML`.
335
+ * (#2965). {@link parseYAML} returns `{}` for a file like `- a\n- b`; this
336
+ * returns the list.
350
337
  */
351
338
  export function parseYAMLDocument(content: string): unknown {
352
339
  try {
@@ -354,11 +341,7 @@ export function parseYAMLDocument(content: string): unknown {
354
341
  } catch {
355
342
  // Fall through to YAML parsing
356
343
  }
357
-
358
- const lines = toLines(content);
359
- const first = nextContentLine(lines, 0);
360
- if (first === lines.length) return {};
361
- return parseWhole(lines, first, isSeqEntry(lines[first]) ? parseYAMLArray : parseYAMLLines);
344
+ return loadDocument(content);
362
345
  }
363
346
 
364
347
  /**
@@ -384,397 +367,6 @@ export function splitYAMLDocuments(content: string): string[] {
384
367
  return documents;
385
368
  }
386
369
 
387
- /**
388
- * A block-scalar header: `|` (literal, keep newlines) or `>` (folded, newlines →
389
- * spaces), with a chomping indicator (`-` strip trailing newlines, `+` keep them,
390
- * default clip to a single one). Returns null when `inline` isn't a block header.
391
- */
392
- interface BlockScalarHeader {
393
- style: "literal" | "folded";
394
- chomp: "clip" | "strip" | "keep";
395
- }
396
- function blockScalarHeader(inline: string): BlockScalarHeader | null {
397
- const m = inline.match(/^([|>])([+-]?)(?:\s+#.*)?$/);
398
- if (!m) return null;
399
- return {
400
- style: m[1] === "|" ? "literal" : "folded",
401
- chomp: m[2] === "-" ? "strip" : m[2] === "+" ? "keep" : "clip",
402
- };
403
- }
404
-
405
- /**
406
- * Parse a block scalar's body — the lines indented past `parentIndent`, dedented
407
- * by the block's own indent (the first content line's). Handles literal/folded
408
- * styles and clip/strip/keep chomping, matching js-yaml. Returns the string value
409
- * and the index of the first line that is NOT part of the block.
410
- */
411
- function parseBlockScalar(
412
- lines: string[],
413
- startIndex: number,
414
- parentIndent: number,
415
- header: BlockScalarHeader,
416
- ): { value: string; endIndex: number } {
417
- const raw: string[] = [];
418
- let blockIndent = -1;
419
- let i = startIndex;
420
- for (; i < lines.length; i++) {
421
- const line = lines[i];
422
- if (line.trim() === "") {
423
- raw.push(""); // a blank line is part of the block (kept/chomped later)
424
- continue;
425
- }
426
- const ni = line.search(/\S/);
427
- if (ni <= parentIndent) break; // dedented out of the block
428
- if (blockIndent === -1) blockIndent = ni;
429
- if (ni < blockIndent) break;
430
- raw.push(line.slice(blockIndent));
431
- }
432
- // Trailing blank lines belong to the block (chomping decides their fate).
433
- let text: string;
434
- if (header.style === "folded") {
435
- // Fold each run of non-empty lines into a space-joined paragraph; a run of N
436
- // blank lines between paragraphs becomes N newlines (a single blank → one
437
- // newline), matching YAML folded semantics.
438
- text = "";
439
- let buf: string[] = [];
440
- let blankRun = 0;
441
- let wrote = false;
442
- const flush = (): void => {
443
- if (buf.length === 0) return;
444
- if (wrote) text += "\n".repeat(blankRun);
445
- text += buf.join(" ");
446
- buf = [];
447
- blankRun = 0;
448
- wrote = true;
449
- };
450
- for (const l of raw) {
451
- if (l === "") { flush(); blankRun++; }
452
- else buf.push(l);
453
- }
454
- flush();
455
- } else {
456
- text = raw.join("\n");
457
- }
458
- const trailing = text.match(/\n*$/)?.[0].length ?? 0;
459
- const stripped = text.replace(/\n+$/, "");
460
- if (header.chomp === "strip") text = stripped;
461
- else if (header.chomp === "keep") text = stripped + "\n".repeat(Math.max(trailing, raw.length > 0 ? 1 : 0));
462
- else text = stripped === "" ? "" : stripped + "\n"; // clip
463
- return { value: text, endIndex: i };
464
- }
465
-
466
- /**
467
- * Parse indentation-based YAML lines into a key-value object: the mapping
468
- * whose keys sit at column `baseIndent`, starting at `startIndex`.
469
- *
470
- * It ends at the first line that is not one of its keys: a line at another
471
- * column, a sequence entry, or text that is not `key:`. That line goes back
472
- * to the caller in `endIndex`; if no caller owns it, `parseYAML` reports it
473
- * (#2991). This function never skips a content line.
474
- */
475
- export function parseYAMLLines(
476
- lines: string[],
477
- startIndex: number,
478
- baseIndent: number,
479
- ): ParseResult {
480
- const result: Record<string, unknown> = {};
481
- const merges: unknown[] = [];
482
- let i = startIndex;
483
-
484
- while (i < lines.length) {
485
- const line = lines[i];
486
- if (isBlankOrComment(line)) {
487
- i++;
488
- continue;
489
- }
490
- if (indentOf(line) !== baseIndent || isSeqEntry(line)) break;
491
- const keyMatch = line.match(KEY_LINE);
492
- if (!keyMatch) break;
493
- const value = parseValue(keyMatch[3].trim(), lines, i, baseIndent);
494
- setKey(result, merges, keyName(keyMatch[2]), value.value);
495
- i = value.endIndex;
496
- }
497
- applyMerges(result, merges, startIndex + 1);
498
-
499
- return { value: result, endIndex: i };
500
- }
501
-
502
- /**
503
- * Parse the value of the key on line `at`, whose text after the colon is
504
- * `inline` and whose own column is `keyIndent`. Used for mapping keys and for
505
- * the keys of a sequence item alike.
506
- *
507
- * An empty inline value (or only a comment) means the value is the block on
508
- * the next content line. Blank, whitespace-only and comment lines between the
509
- * key and that block do not end it (#2991): Helm renders an empty `{{ if }}`
510
- * as exactly such a line, and treating it as the end of the value turned
511
- *
512
- * spec:
513
- *
514
- * serviceAccountName: collector
515
- *
516
- * into `spec: null` with `serviceAccountName` hoisted to the parent.
517
- *
518
- * The two nested shapes do NOT share a threshold (#1311), except below a
519
- * sequence entry (`sequenceAtKeyColumn` false), where a `-` at the entry's
520
- * own column is the next entry:
521
- *
522
- * - a SEQUENCE may sit at the key's own column (valid YAML, and what
523
- * kubectl and Kubernetes manifests emit);
524
- * - a MAPPING must be indented past it, otherwise the next line is a
525
- * sibling key and this key's value is null:
526
- *
527
- * - name: a
528
- * meta: <- no value
529
- * other: b <- a sibling, NOT meta's content
530
- */
531
- function parseValue(
532
- inline: string,
533
- lines: string[],
534
- at: number,
535
- keyIndent: number,
536
- sequenceAtKeyColumn = true,
537
- ): ParseResult {
538
- const anchor = inline.match(ANCHOR);
539
- if (anchor) {
540
- const value = parseValue((anchor[2] ?? "").trim(), lines, at, keyIndent, sequenceAtKeyColumn);
541
- anchors.set(anchor[1], structuredClone(value.value));
542
- return value;
543
- }
544
- const alias = inline.match(ALIAS);
545
- if (alias) return { value: resolveAlias(alias[1], at + 1), endIndex: at + 1 };
546
- if (ALIAS_LIST.test(inline)) {
547
- const names = [...inline.matchAll(/\*([^\s,[\]{}]+)/g)].map((m) => m[1]);
548
- return { value: names.map((name) => resolveAlias(name, at + 1)), endIndex: at + 1 };
549
- }
550
- if (inline === "" || inline.startsWith("#")) {
551
- const k = nextContentLine(lines, at + 1);
552
- if (k < lines.length) {
553
- const ni = indentOf(lines[k]);
554
- const nested = isSeqEntry(lines[k]) && sequenceAtKeyColumn ? ni >= keyIndent : ni > keyIndent;
555
- if (nested) return parseNode(lines, k, keyIndent);
556
- }
557
- return { value: null, endIndex: at + 1 };
558
- }
559
- if (inline.startsWith("[") || inline.startsWith("{")) {
560
- // Inline array or object
561
- try {
562
- return { value: JSON.parse(inline), endIndex: at + 1 };
563
- } catch {
564
- return { value: inline, endIndex: at + 1 };
565
- }
566
- }
567
- const header = blockScalarHeader(inline);
568
- if (header) return parseBlockScalar(lines, at + 1, keyIndent, header);
569
- return parseFlowScalar(inline, lines, at + 1, keyIndent);
570
- }
571
-
572
- /**
573
- * Parse the node that starts on content line `k`, below a key or a bare `-`
574
- * at column `parentIndent`: a sequence, a mapping, or a scalar written on the
575
- * line after its key.
576
- */
577
- function parseNode(lines: string[], k: number, parentIndent: number): ParseResult {
578
- const line = lines[k];
579
- if (isSeqEntry(line)) return parseYAMLArray(lines, k, indentOf(line));
580
- if (KEY_LINE.test(line)) return parseYAMLLines(lines, k, indentOf(line));
581
- return parseValue(line.trim(), lines, k, parentIndent);
582
- }
583
-
584
- /**
585
- * The index of the quote that closes a quoted scalar opened by `quote`,
586
- * scanning `text` from `from`, or -1. A double-quoted scalar escapes with a
587
- * backslash, a single-quoted one by doubling the quote.
588
- */
589
- function closingQuote(text: string, quote: string, from: number): number {
590
- for (let i = from; i < text.length; i++) {
591
- const ch = text[i];
592
- if (quote === '"' && ch === "\\") {
593
- i++;
594
- continue;
595
- }
596
- if (ch === quote) {
597
- if (quote === "'" && text[i + 1] === "'") {
598
- i++;
599
- continue;
600
- }
601
- return i;
602
- }
603
- }
604
- return -1;
605
- }
606
-
607
- /** Whether `text` is exactly one quoted scalar, e.g. `"80:80"`. */
608
- function isWholeQuotedScalar(text: string): boolean {
609
- const quote = text[0];
610
- if (quote !== '"' && quote !== "'") return false;
611
- return closingQuote(text, quote, 1) === text.length - 1;
612
- }
613
-
614
- /**
615
- * Parse a plain or quoted scalar whose first line is `first` and whose
616
- * continuation lines, if any, start at `from` and are indented past
617
- * `parentIndent`. YAML lets both kinds span lines, and go-yaml (so Helm's
618
- * `toYaml`) wraps long strings that way. Lines fold into one: a line break
619
- * becomes a space, and each blank line in between becomes a newline.
620
- *
621
- * These continuation lines used to be skipped, keeping only the first line.
622
- */
623
- function parseFlowScalar(first: string, lines: string[], from: number, parentIndent: number): ParseResult {
624
- const quote = first[0];
625
- if (quote === '"' || quote === "'") {
626
- if (closingQuote(first, quote, 1) !== -1) return { value: parseScalar(first), endIndex: from };
627
- return parseMultilineQuoted(first, lines, from, parentIndent, quote);
628
- }
629
-
630
- let text = first;
631
- let endIndex = from;
632
- let blanks = 0;
633
- for (let j = from; j < lines.length; j++) {
634
- const line = lines[j];
635
- const t = line.trim();
636
- if (t === "") {
637
- blanks++;
638
- continue;
639
- }
640
- // A comment ends a plain scalar; so does a line back at the parent's column.
641
- if (t.startsWith("#") || indentOf(line) <= parentIndent) break;
642
- if (KEY_LINE.test(line)) {
643
- throw new YAMLParseError(
644
- j + 1,
645
- /^!\S*$/.test(first)
646
- ? `the tag ${JSON.stringify(first)} before a nested block is not supported`
647
- : `${JSON.stringify(t)} is indented under the value ${JSON.stringify(first)}, ` +
648
- "and a scalar cannot hold a mapping; check its indentation",
649
- );
650
- }
651
- text += blanks > 0 ? "\n".repeat(blanks) : " ";
652
- text += t;
653
- blanks = 0;
654
- endIndex = j + 1;
655
- }
656
- return { value: parseScalar(text), endIndex };
657
- }
658
-
659
- /**
660
- * The rest of a quoted scalar left open on its first line: lines up to the
661
- * closing quote, folded as {@link parseFlowScalar} describes. For a
662
- * double-quoted scalar a line ending in `\` is an escaped line break that
663
- * joins the next line with nothing between; it is kept as `\` + newline for
664
- * `unescapeDoubleQuoted`, which removes it with the indentation after it.
665
- */
666
- function parseMultilineQuoted(
667
- first: string,
668
- lines: string[],
669
- from: number,
670
- parentIndent: number,
671
- quote: string,
672
- ): ParseResult {
673
- const segments = [first.slice(1)];
674
- for (let j = from; j < lines.length; j++) {
675
- const line = lines[j];
676
- if (line.trim() !== "" && indentOf(line) <= parentIndent) break;
677
- const close = closingQuote(line, quote, 0);
678
- if (close === -1) {
679
- segments.push(line);
680
- continue;
681
- }
682
- segments.push(line.slice(0, close));
683
- let body = "";
684
- let blanks = 0;
685
- let escapedBreak = false;
686
- segments.forEach((raw, n) => {
687
- const isFirst = n === 0;
688
- const isLast = n === segments.length - 1;
689
- let s = raw;
690
- if (!isFirst && !escapedBreak) s = s.replace(/^[ \t]+/, "");
691
- const endsEscaped = !isLast && quote === '"' && /(?:^|[^\\])(?:\\\\)*\\$/.test(s);
692
- if (!isLast && !endsEscaped) s = s.replace(/[ \t]+$/, "");
693
- if (!isFirst && !isLast && s === "" && !escapedBreak) {
694
- blanks++;
695
- return;
696
- }
697
- if (!isFirst) body += escapedBreak ? "\n" : blanks > 0 ? "\n".repeat(blanks) : " ";
698
- body += s;
699
- blanks = 0;
700
- escapedBreak = endsEscaped;
701
- });
702
- return { value: parseScalar(quote + body + quote), endIndex: j + 1 };
703
- }
704
- throw new YAMLParseError(from, `unterminated ${quote === '"' ? "double" : "single"}-quoted scalar`);
705
- }
706
-
707
- /**
708
- * Parse a block array: the `- ` entries at column `baseIndent`, starting at
709
- * `startIndex`. Like {@link parseYAMLLines} it ends at the first line that is
710
- * not one of its entries and hands that line back in `endIndex`.
711
- */
712
- export function parseYAMLArray(
713
- lines: string[],
714
- startIndex: number,
715
- baseIndent: number,
716
- ): ParseResult {
717
- const result: unknown[] = [];
718
- let i = startIndex;
719
-
720
- while (i < lines.length) {
721
- const line = lines[i];
722
- if (isBlankOrComment(line)) {
723
- i++;
724
- continue;
725
- }
726
- const itemMatch = line.match(SEQ_ENTRY);
727
- if (!itemMatch || indentOf(line) !== baseIndent) break;
728
- const itemValue = (itemMatch[3] ?? "").trim();
729
-
730
- // The column the entry's content starts at, which is where the keys of a
731
- // mapping entry sit: two past the dash for `- key`, more for `- key`.
732
- const keyIndent = baseIndent + 1 + (itemMatch[2]?.length ?? 1);
733
- // A quoted scalar holding a colon (e.g. "80:80") is not a key-value pair.
734
- const kvMatch = isWholeQuotedScalar(itemValue) ? null : itemValue.match(ITEM_KEY);
735
- if (kvMatch) {
736
- const obj: Record<string, unknown> = {};
737
- const merges: unknown[] = [];
738
- const firstValue = parseValue(kvMatch[2].trim(), lines, i, keyIndent);
739
- setKey(obj, merges, keyName(kvMatch[1]), firstValue.value);
740
- // The entry's other keys, at the first key's column. Each value's
741
- // parse reports where it ended, nested block, same-column sequence
742
- // (#1311) and block scalar (#910) alike.
743
- let j = firstValue.endIndex;
744
- while (j < lines.length) {
745
- const nextLine = lines[j];
746
- if (isBlankOrComment(nextLine)) {
747
- j++;
748
- continue;
749
- }
750
- if (indentOf(nextLine) !== keyIndent || isSeqEntry(nextLine)) break;
751
- const nextKV = nextLine.match(KEY_LINE);
752
- if (!nextKV) break;
753
- const value = parseValue(nextKV[3].trim(), lines, j, keyIndent);
754
- setKey(obj, merges, keyName(nextKV[2]), value.value);
755
- j = value.endIndex;
756
- }
757
- applyMerges(obj, merges, i + 1);
758
- result.push(obj);
759
- i = j;
760
- continue;
761
- }
762
-
763
- // Anything else is read the way a mapping value is: a scalar, an inline
764
- // list or object, an anchor or alias, a block scalar, or, for a dash alone
765
- // on its line (#1286), the block below it after any blank or comment
766
- // lines, or null. A block scalar's body (`- |`) is indented past the
767
- // dash's column; before #1482 the header parsed as the literal string "|"
768
- // and the body lines leaked into whatever came next, hoisting a
769
- // container's keys after `args:` to the document root.
770
- const item = parseValue(itemValue, lines, i, baseIndent, false);
771
- result.push(item.value);
772
- i = item.endIndex;
773
- }
774
-
775
- return { value: result, endIndex: i };
776
- }
777
-
778
370
  /**
779
371
  * Coerce a scalar string to a typed value.
780
372
  */