@intentius/chant 0.6.0 → 0.8.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 (433) hide show
  1. package/dist/attrref.d.ts +35 -0
  2. package/dist/attrref.d.ts.map +1 -0
  3. package/dist/audit/catalog.d.ts +65 -0
  4. package/dist/audit/catalog.d.ts.map +1 -0
  5. package/dist/audit/core.d.ts +76 -0
  6. package/dist/audit/core.d.ts.map +1 -0
  7. package/dist/audit/discover.d.ts +76 -0
  8. package/dist/audit/discover.d.ts.map +1 -0
  9. package/dist/audit/fetch.d.ts +90 -0
  10. package/dist/audit/fetch.d.ts.map +1 -0
  11. package/dist/audit/proof.d.ts +53 -0
  12. package/dist/audit/proof.d.ts.map +1 -0
  13. package/dist/audit/report-html.d.ts +32 -0
  14. package/dist/audit/report-html.d.ts.map +1 -0
  15. package/dist/audit/report-model.d.ts +119 -0
  16. package/dist/audit/report-model.d.ts.map +1 -0
  17. package/dist/audit/report.d.ts +31 -0
  18. package/dist/audit/report.d.ts.map +1 -0
  19. package/dist/audit/rules-doc.d.ts +11 -0
  20. package/dist/audit/rules-doc.d.ts.map +1 -0
  21. package/dist/build.d.ts +120 -0
  22. package/dist/build.d.ts.map +1 -0
  23. package/dist/builder.d.ts +56 -0
  24. package/dist/builder.d.ts.map +1 -0
  25. package/dist/child-project.d.ts +34 -0
  26. package/dist/child-project.d.ts.map +1 -0
  27. package/dist/cli/commands/audit.d.ts +60 -0
  28. package/dist/cli/commands/audit.d.ts.map +1 -0
  29. package/dist/cli/commands/build.d.ts +70 -0
  30. package/dist/cli/commands/build.d.ts.map +1 -0
  31. package/dist/cli/commands/check-lexicon.d.ts +19 -0
  32. package/dist/cli/commands/check-lexicon.d.ts.map +1 -0
  33. package/dist/cli/commands/describe.d.ts +39 -0
  34. package/dist/cli/commands/describe.d.ts.map +1 -0
  35. package/dist/cli/commands/diff.d.ts +32 -0
  36. package/dist/cli/commands/diff.d.ts.map +1 -0
  37. package/dist/cli/commands/doctor.d.ts +11 -0
  38. package/dist/cli/commands/doctor.d.ts.map +1 -0
  39. package/dist/cli/commands/import.d.ts +68 -0
  40. package/dist/cli/commands/import.d.ts.map +1 -0
  41. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts +9 -0
  42. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -0
  43. package/dist/cli/commands/init-lexicon/templates/docs.d.ts +9 -0
  44. package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -0
  45. package/dist/cli/commands/init-lexicon/templates/examples.d.ts +8 -0
  46. package/dist/cli/commands/init-lexicon/templates/examples.d.ts.map +1 -0
  47. package/dist/cli/commands/init-lexicon/templates/lint.d.ts +8 -0
  48. package/dist/cli/commands/init-lexicon/templates/lint.d.ts.map +1 -0
  49. package/dist/cli/commands/init-lexicon/templates/lsp.d.ts +6 -0
  50. package/dist/cli/commands/init-lexicon/templates/lsp.d.ts.map +1 -0
  51. package/dist/cli/commands/init-lexicon/templates/plugin.d.ts +12 -0
  52. package/dist/cli/commands/init-lexicon/templates/plugin.d.ts.map +1 -0
  53. package/dist/cli/commands/init-lexicon/templates/project.d.ts +19 -0
  54. package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -0
  55. package/dist/cli/commands/init-lexicon/templates/spec.d.ts +6 -0
  56. package/dist/cli/commands/init-lexicon/templates/spec.d.ts.map +1 -0
  57. package/dist/cli/commands/init-lexicon/templates/tests.d.ts +12 -0
  58. package/dist/cli/commands/init-lexicon/templates/tests.d.ts.map +1 -0
  59. package/dist/cli/commands/init-lexicon.d.ts +37 -0
  60. package/dist/cli/commands/init-lexicon.d.ts.map +1 -0
  61. package/dist/cli/commands/init.d.ts +48 -0
  62. package/dist/cli/commands/init.d.ts.map +1 -0
  63. package/dist/cli/commands/licenses.d.ts +23 -0
  64. package/dist/cli/commands/licenses.d.ts.map +1 -0
  65. package/dist/cli/commands/lint.d.ts +52 -0
  66. package/dist/cli/commands/lint.d.ts.map +1 -0
  67. package/dist/cli/commands/list.d.ts +38 -0
  68. package/dist/cli/commands/list.d.ts.map +1 -0
  69. package/dist/cli/commands/migrate.d.ts +51 -0
  70. package/dist/cli/commands/migrate.d.ts.map +1 -0
  71. package/dist/cli/commands/onboard.d.ts +19 -0
  72. package/dist/cli/commands/onboard.d.ts.map +1 -0
  73. package/dist/cli/commands/update.d.ts +29 -0
  74. package/dist/cli/commands/update.d.ts.map +1 -0
  75. package/dist/cli/commands/vendor.d.ts +84 -0
  76. package/dist/cli/commands/vendor.d.ts.map +1 -0
  77. package/dist/cli/conflict-check.d.ts +18 -0
  78. package/dist/cli/conflict-check.d.ts.map +1 -0
  79. package/dist/cli/debug.d.ts +5 -0
  80. package/dist/cli/debug.d.ts.map +1 -0
  81. package/dist/cli/format.d.ts +36 -0
  82. package/dist/cli/format.d.ts.map +1 -0
  83. package/dist/cli/handlers/build.d.ts +3 -0
  84. package/dist/cli/handlers/build.d.ts.map +1 -0
  85. package/dist/cli/handlers/dev.d.ts +7 -0
  86. package/dist/cli/handlers/dev.d.ts.map +1 -0
  87. package/dist/cli/handlers/graph.d.ts +8 -0
  88. package/dist/cli/handlers/graph.d.ts.map +1 -0
  89. package/dist/cli/handlers/init.d.ts +4 -0
  90. package/dist/cli/handlers/init.d.ts.map +1 -0
  91. package/dist/cli/handlers/lifecycle.d.ts +39 -0
  92. package/dist/cli/handlers/lifecycle.d.ts.map +1 -0
  93. package/dist/cli/handlers/lint.d.ts +3 -0
  94. package/dist/cli/handlers/lint.d.ts.map +1 -0
  95. package/dist/cli/handlers/migrate.d.ts +3 -0
  96. package/dist/cli/handlers/migrate.d.ts.map +1 -0
  97. package/dist/cli/handlers/misc.d.ts +8 -0
  98. package/dist/cli/handlers/misc.d.ts.map +1 -0
  99. package/dist/cli/handlers/run-client.d.ts +107 -0
  100. package/dist/cli/handlers/run-client.d.ts.map +1 -0
  101. package/dist/cli/handlers/run-report.d.ts +5 -0
  102. package/dist/cli/handlers/run-report.d.ts.map +1 -0
  103. package/dist/cli/handlers/run.d.ts +27 -0
  104. package/dist/cli/handlers/run.d.ts.map +1 -0
  105. package/dist/cli/handlers/serve.d.ts +5 -0
  106. package/dist/cli/handlers/serve.d.ts.map +1 -0
  107. package/dist/cli/handlers/vendor.d.ts +9 -0
  108. package/dist/cli/handlers/vendor.d.ts.map +1 -0
  109. package/dist/cli/index.d.ts +4 -0
  110. package/dist/cli/index.d.ts.map +1 -0
  111. package/dist/cli/lsp/capabilities.d.ts +21 -0
  112. package/dist/cli/lsp/capabilities.d.ts.map +1 -0
  113. package/dist/cli/lsp/diagnostics.d.ts +30 -0
  114. package/dist/cli/lsp/diagnostics.d.ts.map +1 -0
  115. package/dist/cli/lsp/server.d.ts +106 -0
  116. package/dist/cli/lsp/server.d.ts.map +1 -0
  117. package/dist/cli/main.d.ts +7 -0
  118. package/dist/cli/main.d.ts.map +1 -0
  119. package/dist/cli/mcp/lifecycle-tools.d.ts +15 -0
  120. package/dist/cli/mcp/lifecycle-tools.d.ts.map +1 -0
  121. package/dist/cli/mcp/op-tools.d.ts +7 -0
  122. package/dist/cli/mcp/op-tools.d.ts.map +1 -0
  123. package/dist/cli/mcp/resource-handlers.d.ts +28 -0
  124. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -0
  125. package/dist/cli/mcp/resources/context.d.ts +6 -0
  126. package/dist/cli/mcp/resources/context.d.ts.map +1 -0
  127. package/dist/cli/mcp/server.d.ts +44 -0
  128. package/dist/cli/mcp/server.d.ts.map +1 -0
  129. package/dist/cli/mcp/tools/build.d.ts +31 -0
  130. package/dist/cli/mcp/tools/build.d.ts.map +1 -0
  131. package/dist/cli/mcp/tools/explain.d.ts +27 -0
  132. package/dist/cli/mcp/tools/explain.d.ts.map +1 -0
  133. package/dist/cli/mcp/tools/import.d.ts +26 -0
  134. package/dist/cli/mcp/tools/import.d.ts.map +1 -0
  135. package/dist/cli/mcp/tools/lint.d.ts +26 -0
  136. package/dist/cli/mcp/tools/lint.d.ts.map +1 -0
  137. package/dist/cli/mcp/tools/scaffold.d.ts +31 -0
  138. package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -0
  139. package/dist/cli/mcp/tools/search.d.ts +31 -0
  140. package/dist/cli/mcp/tools/search.d.ts.map +1 -0
  141. package/dist/cli/mcp/types.d.ts +42 -0
  142. package/dist/cli/mcp/types.d.ts.map +1 -0
  143. package/dist/cli/plugins.d.ts +21 -0
  144. package/dist/cli/plugins.d.ts.map +1 -0
  145. package/dist/cli/registry.d.ts +100 -0
  146. package/dist/cli/registry.d.ts.map +1 -0
  147. package/dist/cli/reporters/stylish.d.ts +26 -0
  148. package/dist/cli/reporters/stylish.d.ts.map +1 -0
  149. package/dist/cli/watch.d.ts +29 -0
  150. package/dist/cli/watch.d.ts.map +1 -0
  151. package/dist/codegen/bound-property-types.d.ts +38 -0
  152. package/dist/codegen/bound-property-types.d.ts.map +1 -0
  153. package/dist/codegen/coverage.d.ts +57 -0
  154. package/dist/codegen/coverage.d.ts.map +1 -0
  155. package/dist/codegen/docs-file-markers.d.ts +16 -0
  156. package/dist/codegen/docs-file-markers.d.ts.map +1 -0
  157. package/dist/codegen/docs-rule-scanning.d.ts +14 -0
  158. package/dist/codegen/docs-rule-scanning.d.ts.map +1 -0
  159. package/dist/codegen/docs-rules.d.ts +17 -0
  160. package/dist/codegen/docs-rules.d.ts.map +1 -0
  161. package/dist/codegen/docs-sections.d.ts +11 -0
  162. package/dist/codegen/docs-sections.d.ts.map +1 -0
  163. package/dist/codegen/docs-sidebar.d.ts +6 -0
  164. package/dist/codegen/docs-sidebar.d.ts.map +1 -0
  165. package/dist/codegen/docs-types.d.ts +84 -0
  166. package/dist/codegen/docs-types.d.ts.map +1 -0
  167. package/dist/codegen/docs.d.ts +28 -0
  168. package/dist/codegen/docs.d.ts.map +1 -0
  169. package/dist/codegen/fetch.d.ts +76 -0
  170. package/dist/codegen/fetch.d.ts.map +1 -0
  171. package/dist/codegen/generate-post-synth-barrel.d.ts +32 -0
  172. package/dist/codegen/generate-post-synth-barrel.d.ts.map +1 -0
  173. package/dist/codegen/generate-registry.d.ts +40 -0
  174. package/dist/codegen/generate-registry.d.ts.map +1 -0
  175. package/dist/codegen/generate-runtime-index.d.ts +40 -0
  176. package/dist/codegen/generate-runtime-index.d.ts.map +1 -0
  177. package/dist/codegen/generate-typescript.d.ts +48 -0
  178. package/dist/codegen/generate-typescript.d.ts.map +1 -0
  179. package/dist/codegen/generate.d.ts +92 -0
  180. package/dist/codegen/generate.d.ts.map +1 -0
  181. package/dist/codegen/json-patch.d.ts +12 -0
  182. package/dist/codegen/json-patch.d.ts.map +1 -0
  183. package/dist/codegen/json-schema.d.ts +82 -0
  184. package/dist/codegen/json-schema.d.ts.map +1 -0
  185. package/dist/codegen/naming.d.ts +64 -0
  186. package/dist/codegen/naming.d.ts.map +1 -0
  187. package/dist/codegen/package.d.ts +64 -0
  188. package/dist/codegen/package.d.ts.map +1 -0
  189. package/dist/codegen/topo-sort.d.ts +13 -0
  190. package/dist/codegen/topo-sort.d.ts.map +1 -0
  191. package/dist/codegen/typecheck.d.ts +10 -0
  192. package/dist/codegen/typecheck.d.ts.map +1 -0
  193. package/dist/codegen/validate.d.ts +45 -0
  194. package/dist/codegen/validate.d.ts.map +1 -0
  195. package/dist/composite.d.ts +116 -0
  196. package/dist/composite.d.ts.map +1 -0
  197. package/dist/config.d.ts +78 -0
  198. package/dist/config.d.ts.map +1 -0
  199. package/dist/declarable.d.ts +34 -0
  200. package/dist/declarable.d.ts.map +1 -0
  201. package/dist/detectLexicon.d.ts +10 -0
  202. package/dist/detectLexicon.d.ts.map +1 -0
  203. package/dist/discovery/cache.d.ts +36 -0
  204. package/dist/discovery/cache.d.ts.map +1 -0
  205. package/dist/discovery/collect.d.ts +17 -0
  206. package/dist/discovery/collect.d.ts.map +1 -0
  207. package/dist/discovery/cycles.d.ts +23 -0
  208. package/dist/discovery/cycles.d.ts.map +1 -0
  209. package/dist/discovery/files.d.ts +7 -0
  210. package/dist/discovery/files.d.ts.map +1 -0
  211. package/dist/discovery/graph.d.ts +8 -0
  212. package/dist/discovery/graph.d.ts.map +1 -0
  213. package/dist/discovery/import.d.ts +9 -0
  214. package/dist/discovery/import.d.ts.map +1 -0
  215. package/dist/discovery/index.d.ts +25 -0
  216. package/dist/discovery/index.d.ts.map +1 -0
  217. package/dist/discovery/resolve.d.ts +10 -0
  218. package/dist/discovery/resolve.d.ts.map +1 -0
  219. package/dist/errors.d.ts +49 -0
  220. package/dist/errors.d.ts.map +1 -0
  221. package/dist/import/base-parser.d.ts +19 -0
  222. package/dist/import/base-parser.d.ts.map +1 -0
  223. package/dist/import/generator.d.ts +20 -0
  224. package/dist/import/generator.d.ts.map +1 -0
  225. package/dist/import/ir-utils.d.ts +25 -0
  226. package/dist/import/ir-utils.d.ts.map +1 -0
  227. package/dist/import/parser.d.ts +39 -0
  228. package/dist/import/parser.d.ts.map +1 -0
  229. package/dist/index.d.ts +66 -0
  230. package/dist/index.d.ts.map +1 -0
  231. package/dist/intrinsic-interpolation.d.ts +24 -0
  232. package/dist/intrinsic-interpolation.d.ts.map +1 -0
  233. package/dist/intrinsic.d.ts +24 -0
  234. package/dist/intrinsic.d.ts.map +1 -0
  235. package/dist/lexicon-integrity.d.ts +25 -0
  236. package/dist/lexicon-integrity.d.ts.map +1 -0
  237. package/dist/lexicon-manifest.d.ts +9 -0
  238. package/dist/lexicon-manifest.d.ts.map +1 -0
  239. package/dist/lexicon-output.d.ts +66 -0
  240. package/dist/lexicon-output.d.ts.map +1 -0
  241. package/dist/lexicon-plugin-helpers.d.ts +59 -0
  242. package/dist/lexicon-plugin-helpers.d.ts.map +1 -0
  243. package/dist/lexicon-schema.d.ts +82 -0
  244. package/dist/lexicon-schema.d.ts.map +1 -0
  245. package/dist/lexicon.d.ts +383 -0
  246. package/dist/lexicon.d.ts.map +1 -0
  247. package/dist/lifecycle/affected.d.ts +57 -0
  248. package/dist/lifecycle/affected.d.ts.map +1 -0
  249. package/dist/lifecycle/change-set.d.ts +90 -0
  250. package/dist/lifecycle/change-set.d.ts.map +1 -0
  251. package/dist/lifecycle/digest.d.ts +21 -0
  252. package/dist/lifecycle/digest.d.ts.map +1 -0
  253. package/dist/lifecycle/git.d.ts +72 -0
  254. package/dist/lifecycle/git.d.ts.map +1 -0
  255. package/dist/lifecycle/index.d.ts +8 -0
  256. package/dist/lifecycle/index.d.ts.map +1 -0
  257. package/dist/lifecycle/live-diff.d.ts +67 -0
  258. package/dist/lifecycle/live-diff.d.ts.map +1 -0
  259. package/dist/lifecycle/snapshot.d.ts +20 -0
  260. package/dist/lifecycle/snapshot.d.ts.map +1 -0
  261. package/dist/lifecycle/types.d.ts +61 -0
  262. package/dist/lifecycle/types.d.ts.map +1 -0
  263. package/dist/lint/config.d.ts +100 -0
  264. package/dist/lint/config.d.ts.map +1 -0
  265. package/dist/lint/declarative.d.ts +56 -0
  266. package/dist/lint/declarative.d.ts.map +1 -0
  267. package/dist/lint/discover.d.ts +35 -0
  268. package/dist/lint/discover.d.ts.map +1 -0
  269. package/dist/lint/engine.d.ts +21 -0
  270. package/dist/lint/engine.d.ts.map +1 -0
  271. package/dist/lint/named-checks.d.ts +19 -0
  272. package/dist/lint/named-checks.d.ts.map +1 -0
  273. package/dist/lint/parser.d.ts +9 -0
  274. package/dist/lint/parser.d.ts.map +1 -0
  275. package/dist/lint/policy.d.ts +22 -0
  276. package/dist/lint/policy.d.ts.map +1 -0
  277. package/dist/lint/post-synth.d.ts +69 -0
  278. package/dist/lint/post-synth.d.ts.map +1 -0
  279. package/dist/lint/presets/strict.json +19 -0
  280. package/dist/lint/rule-loader.d.ts +16 -0
  281. package/dist/lint/rule-loader.d.ts.map +1 -0
  282. package/dist/lint/rule-registry.d.ts +37 -0
  283. package/dist/lint/rule-registry.d.ts.map +1 -0
  284. package/dist/lint/rule.d.ts +82 -0
  285. package/dist/lint/rule.d.ts.map +1 -0
  286. package/dist/lint/rules/composite-scope.d.ts +18 -0
  287. package/dist/lint/rules/composite-scope.d.ts.map +1 -0
  288. package/dist/lint/rules/cor017-composite-name-match.d.ts +3 -0
  289. package/dist/lint/rules/cor017-composite-name-match.d.ts.map +1 -0
  290. package/dist/lint/rules/cor018-composite-prefer-lexicon-type.d.ts +3 -0
  291. package/dist/lint/rules/cor018-composite-prefer-lexicon-type.d.ts.map +1 -0
  292. package/dist/lint/rules/declarable-naming-convention.d.ts +3 -0
  293. package/dist/lint/rules/declarable-naming-convention.d.ts.map +1 -0
  294. package/dist/lint/rules/evl001-non-literal-expression.d.ts +3 -0
  295. package/dist/lint/rules/evl001-non-literal-expression.d.ts.map +1 -0
  296. package/dist/lint/rules/evl002-control-flow-resource.d.ts +3 -0
  297. package/dist/lint/rules/evl002-control-flow-resource.d.ts.map +1 -0
  298. package/dist/lint/rules/evl003-dynamic-property-access.d.ts +3 -0
  299. package/dist/lint/rules/evl003-dynamic-property-access.d.ts.map +1 -0
  300. package/dist/lint/rules/evl004-spread-non-const.d.ts +3 -0
  301. package/dist/lint/rules/evl004-spread-non-const.d.ts.map +1 -0
  302. package/dist/lint/rules/evl005-resource-block-body.d.ts +3 -0
  303. package/dist/lint/rules/evl005-resource-block-body.d.ts.map +1 -0
  304. package/dist/lint/rules/evl007-invalid-siblings.d.ts +3 -0
  305. package/dist/lint/rules/evl007-invalid-siblings.d.ts.map +1 -0
  306. package/dist/lint/rules/evl009-composite-no-constant.d.ts +3 -0
  307. package/dist/lint/rules/evl009-composite-no-constant.d.ts.map +1 -0
  308. package/dist/lint/rules/evl010-composite-no-transform.d.ts +3 -0
  309. package/dist/lint/rules/evl010-composite-no-transform.d.ts.map +1 -0
  310. package/dist/lint/rules/export-required.d.ts +3 -0
  311. package/dist/lint/rules/export-required.d.ts.map +1 -0
  312. package/dist/lint/rules/file-declarable-limit.d.ts +3 -0
  313. package/dist/lint/rules/file-declarable-limit.d.ts.map +1 -0
  314. package/dist/lint/rules/flat-declarations.d.ts +3 -0
  315. package/dist/lint/rules/flat-declarations.d.ts.map +1 -0
  316. package/dist/lint/rules/index.d.ts +31 -0
  317. package/dist/lint/rules/index.d.ts.map +1 -0
  318. package/dist/lint/rules/no-cyclic-declarable-ref.d.ts +3 -0
  319. package/dist/lint/rules/no-cyclic-declarable-ref.d.ts.map +1 -0
  320. package/dist/lint/rules/no-redundant-type-import.d.ts +10 -0
  321. package/dist/lint/rules/no-redundant-type-import.d.ts.map +1 -0
  322. package/dist/lint/rules/no-redundant-value-cast.d.ts +3 -0
  323. package/dist/lint/rules/no-redundant-value-cast.d.ts.map +1 -0
  324. package/dist/lint/rules/no-string-ref.d.ts +3 -0
  325. package/dist/lint/rules/no-string-ref.d.ts.map +1 -0
  326. package/dist/lint/rules/no-unused-declarable-import.d.ts +3 -0
  327. package/dist/lint/rules/no-unused-declarable-import.d.ts.map +1 -0
  328. package/dist/lint/rules/no-unused-declarable.d.ts +3 -0
  329. package/dist/lint/rules/no-unused-declarable.d.ts.map +1 -0
  330. package/dist/lint/rules/single-concern-file.d.ts +3 -0
  331. package/dist/lint/rules/single-concern-file.d.ts.map +1 -0
  332. package/dist/lint/selectors.d.ts +21 -0
  333. package/dist/lint/selectors.d.ts.map +1 -0
  334. package/dist/lsp/lexicon-providers.d.ts +61 -0
  335. package/dist/lsp/lexicon-providers.d.ts.map +1 -0
  336. package/dist/lsp/types.d.ts +64 -0
  337. package/dist/lsp/types.d.ts.map +1 -0
  338. package/dist/mcp/types.d.ts +21 -0
  339. package/dist/mcp/types.d.ts.map +1 -0
  340. package/dist/op/activity-registry.d.ts +57 -0
  341. package/dist/op/activity-registry.d.ts.map +1 -0
  342. package/dist/op/builders.d.ts +69 -0
  343. package/dist/op/builders.d.ts.map +1 -0
  344. package/dist/op/discover.d.ts +16 -0
  345. package/dist/op/discover.d.ts.map +1 -0
  346. package/dist/op/index.d.ts +11 -0
  347. package/dist/op/index.d.ts.map +1 -0
  348. package/dist/op/local-executor.d.ts +54 -0
  349. package/dist/op/local-executor.d.ts.map +1 -0
  350. package/dist/op/local-output.d.ts +16 -0
  351. package/dist/op/local-output.d.ts.map +1 -0
  352. package/dist/op/resource.d.ts +7 -0
  353. package/dist/op/resource.d.ts.map +1 -0
  354. package/dist/op/types.d.ts +71 -0
  355. package/dist/op/types.d.ts.map +1 -0
  356. package/dist/ownership.d.ts +75 -0
  357. package/dist/ownership.d.ts.map +1 -0
  358. package/dist/project-validation.d.ts +28 -0
  359. package/dist/project-validation.d.ts.map +1 -0
  360. package/dist/provenance.d.ts +26 -0
  361. package/dist/provenance.d.ts.map +1 -0
  362. package/dist/pseudo-parameter.d.ts +32 -0
  363. package/dist/pseudo-parameter.d.ts.map +1 -0
  364. package/dist/resource-attributes.d.ts +17 -0
  365. package/dist/resource-attributes.d.ts.map +1 -0
  366. package/dist/runtime-adapter.d.ts +41 -0
  367. package/dist/runtime-adapter.d.ts.map +1 -0
  368. package/dist/runtime.d.ts +23 -0
  369. package/dist/runtime.d.ts.map +1 -0
  370. package/dist/serializer-walker.d.ts +21 -0
  371. package/dist/serializer-walker.d.ts.map +1 -0
  372. package/dist/serializer.d.ts +64 -0
  373. package/dist/serializer.d.ts.map +1 -0
  374. package/dist/sort.d.ts +8 -0
  375. package/dist/sort.d.ts.map +1 -0
  376. package/dist/stack-output.d.ts +52 -0
  377. package/dist/stack-output.d.ts.map +1 -0
  378. package/dist/toml-emit.d.ts +22 -0
  379. package/dist/toml-emit.d.ts.map +1 -0
  380. package/dist/toml-parse.d.ts +14 -0
  381. package/dist/toml-parse.d.ts.map +1 -0
  382. package/dist/toml-utils.d.ts +20 -0
  383. package/dist/toml-utils.d.ts.map +1 -0
  384. package/dist/toml.d.ts +9 -0
  385. package/dist/toml.d.ts.map +1 -0
  386. package/dist/types.d.ts +47 -0
  387. package/dist/types.d.ts.map +1 -0
  388. package/dist/utils.d.ts +30 -0
  389. package/dist/utils.d.ts.map +1 -0
  390. package/dist/validation.d.ts +42 -0
  391. package/dist/validation.d.ts.map +1 -0
  392. package/dist/yaml.d.ts +42 -0
  393. package/dist/yaml.d.ts.map +1 -0
  394. package/package.json +31 -6
  395. package/src/audit/bundle-decoupling.test.ts +56 -0
  396. package/src/audit/catalog.test.ts +17 -2
  397. package/src/audit/catalog.ts +357 -4
  398. package/src/audit/core.test.ts +15 -0
  399. package/src/audit/core.ts +0 -0
  400. package/src/audit/detect-bundle.test.ts +35 -0
  401. package/src/audit/discover.test.ts +72 -0
  402. package/src/audit/discover.ts +329 -0
  403. package/src/audit/edge-init-safety.test.ts +43 -0
  404. package/src/audit/fetch.test.ts +86 -55
  405. package/src/audit/fetch.ts +118 -87
  406. package/src/audit/remote-discovery.test.ts +80 -0
  407. package/src/audit/report-html.ts +14 -6
  408. package/src/audit/report-model.ts +13 -1
  409. package/src/audit/report.test.ts +3 -1
  410. package/src/audit/report.ts +13 -5
  411. package/src/audit/rules-doc.ts +4 -0
  412. package/src/cli/commands/__fixtures__/audit-aws/stack.yaml +11 -0
  413. package/src/cli/commands/__fixtures__/audit-aws/template.json +7 -0
  414. package/src/cli/commands/__fixtures__/audit-azure/azuredeploy.json +13 -0
  415. package/src/cli/commands/__fixtures__/audit-gcp/bucket.yaml +6 -0
  416. package/src/cli/commands/__fixtures__/audit-gcp/firewall.yaml +9 -0
  417. package/src/cli/commands/__fixtures__/audit-helm/mychart/Chart.yaml +3 -0
  418. package/src/cli/commands/__fixtures__/audit-helm/mychart/templates/deployment.yaml +12 -0
  419. package/src/cli/commands/__fixtures__/audit-helm/mychart/values.yaml +1 -0
  420. package/src/cli/commands/__fixtures__/audit-mixed/.github/workflows/ci.yml +5 -0
  421. package/src/cli/commands/__fixtures__/audit-mixed/Dockerfile +2 -0
  422. package/src/cli/commands/__fixtures__/audit-mixed/cfn/template.json +1 -0
  423. package/src/cli/commands/__fixtures__/audit-mixed/k8s/deploy.yaml +12 -0
  424. package/src/cli/commands/audit.test.ts +80 -14
  425. package/src/cli/commands/audit.ts +28 -126
  426. package/src/codegen/bound-property-types.test.ts +57 -0
  427. package/src/codegen/bound-property-types.ts +110 -0
  428. package/src/codegen/generate-post-synth-barrel.test.ts +37 -0
  429. package/src/codegen/generate-post-synth-barrel.ts +75 -0
  430. package/src/codegen/post-synth-barrel-guards.test.ts +63 -0
  431. package/src/codegen/validate.ts +13 -1
  432. package/src/lint/discover.ts +1 -1
  433. package/src/meta/peer-deps.test.ts +29 -0
@@ -0,0 +1,329 @@
1
+ /**
2
+ * Unified, detectTemplate-driven discovery (prototype for #391).
3
+ *
4
+ * Today `commands/audit.ts` ships six bespoke discoverers — `discoverManifests`,
5
+ * `discoverGcp`, `discoverCloudFormation`, `discoverArm`, `discoverDocker`,
6
+ * `discoverHelm` — each walking the tree and re-implementing a `looksLike*`
7
+ * content heuristic that mostly duplicates the lexicon plugin's own
8
+ * `detectTemplate(parsed)` (already used by `chant import`).
9
+ *
10
+ * This module collapses that into ONE walk plus a single content-detection
11
+ * pass that delegates to each plugin's `detectTemplate`. A new content-detected
12
+ * lexicon then needs only a `detectTemplate` and a precedence entry — no new
13
+ * walk, no new heuristic.
14
+ *
15
+ * It is deliberately a HYBRID, because some classification can't be expressed
16
+ * by content shape alone:
17
+ * - CI (github/forgejo/gitlab) stays a PATH fast-path. github and forgejo
18
+ * workflows are byte-for-byte the same shape (`on:` + `jobs:`), so only the
19
+ * directory (`.github` vs `.forgejo`) disambiguates them.
20
+ * - Dockerfiles are matched by FILENAME — they aren't YAML/JSON, so
21
+ * `detectTemplate` (which only knows Compose's `services:`) can't see them.
22
+ * - Helm charts are a directory BUNDLE keyed by `Chart.yaml`; the helm checks
23
+ * read `output.files`, so the whole chart is one AuditInput.
24
+ * - k8s `detectTemplate` matches any `apiVersion`+`kind`, including GCP Config
25
+ * Connector (`cnrm.cloud.google.com`) resources, so gcp must be tried first.
26
+ * - aws/azure checks `JSON.parse` their input, so YAML/templated content is
27
+ * normalized to a JSON string here.
28
+ */
29
+
30
+ import { readdirSync, readFileSync, statSync } from "fs";
31
+ import { basename, join, relative } from "path";
32
+ import { parseYAML } from "../yaml";
33
+ import type { LexiconPlugin } from "../lexicon";
34
+ import type { AuditInput, AuditLexicon } from "./core";
35
+
36
+ /** Lexicons the auditor knows how to detect and run checks for. */
37
+ export const AUDIT_LEXICONS = ["github", "gitlab", "forgejo", "k8s", "docker", "aws", "azure", "gcp", "helm"] as const;
38
+
39
+ /**
40
+ * Load the plugins used for detection. Each is loaded in isolation (no
41
+ * cross-lexicon conflict aggregation — detection only reads `detectTemplate`),
42
+ * and a lexicon that isn't installed is skipped rather than failing the audit.
43
+ * `init()` is intentionally not called: `detectTemplate` is a pure function of
44
+ * its input and needs no plugin setup.
45
+ */
46
+ export async function loadAuditPlugins(names: readonly string[] = AUDIT_LEXICONS): Promise<LexiconPlugin[]> {
47
+ // Lazy import: a top-level `cli/plugins` import would pull the root barrel
48
+ // (index.ts -> lint/parser -> the TypeScript compiler) into anyone importing
49
+ // `classifyFiles`, crashing edge runtimes. Edge callers build detectors from
50
+ // static `detect` imports and never call this. See #408/#426.
51
+ const { loadPlugin } = await import("../cli/plugins");
52
+ const plugins: LexiconPlugin[] = [];
53
+ for (const name of names) {
54
+ try {
55
+ plugins.push(await loadPlugin(name));
56
+ } catch {
57
+ // lexicon package not installed — its files simply won't be detected
58
+ }
59
+ }
60
+ return plugins;
61
+ }
62
+
63
+ const WALK_SKIP = new Set(["node_modules", ".git", "dist"]);
64
+ /** Dot-directories the walk descends into anyway (CI lives here). */
65
+ const WALK_DOT_DIRS = new Set([".github", ".forgejo"]);
66
+ const MAX_WALK_FILES = 1000;
67
+ /** Skip absurdly large files before parsing — mirrors the fetch layer's caps. */
68
+ const MAX_FILE_BYTES = 2 * 1024 * 1024;
69
+
70
+ /** Recursively collect file paths under a root, skipping noise dirs. */
71
+ function walkFiles(dir: string, out: string[]): void {
72
+ if (out.length >= MAX_WALK_FILES) return;
73
+ let entries;
74
+ try {
75
+ entries = readdirSync(dir, { withFileTypes: true });
76
+ } catch {
77
+ return;
78
+ }
79
+ for (const e of entries.sort((a, b) => (a.name < b.name ? -1 : 1))) {
80
+ if (out.length >= MAX_WALK_FILES) return;
81
+ if (e.name.startsWith(".") && e.isDirectory() && !WALK_DOT_DIRS.has(e.name)) continue;
82
+ if (WALK_SKIP.has(e.name)) continue;
83
+ const full = join(dir, e.name);
84
+ if (e.isDirectory()) walkFiles(full, out);
85
+ else out.push(full);
86
+ }
87
+ }
88
+
89
+ function readSafe(full: string): string | undefined {
90
+ try {
91
+ if (statSync(full).size > MAX_FILE_BYTES) return undefined;
92
+ return readFileSync(full, "utf-8");
93
+ } catch {
94
+ return undefined;
95
+ }
96
+ }
97
+
98
+ function isYaml(name: string): boolean {
99
+ return name.endsWith(".yml") || name.endsWith(".yaml");
100
+ }
101
+
102
+ function isDockerfileName(name: string): boolean {
103
+ return name === "Dockerfile" || name.startsWith("Dockerfile.") || name.endsWith(".Dockerfile") || name.endsWith(".dockerfile");
104
+ }
105
+
106
+ /** Split a possibly multi-document YAML string into per-doc parsed objects. */
107
+ function parseDocs(content: string): Record<string, unknown>[] {
108
+ const docs: Record<string, unknown>[] = [];
109
+ for (const raw of content.split(/\n---\n/)) {
110
+ const t = raw.trim();
111
+ if (!t) continue;
112
+ try {
113
+ const obj = parseYAML(t);
114
+ if (obj && typeof obj === "object") docs.push(obj);
115
+ } catch {
116
+ // a non-parseable doc just doesn't contribute a candidate
117
+ }
118
+ }
119
+ return docs;
120
+ }
121
+
122
+ /** Parse JSON or YAML content into a single object, or undefined. */
123
+ function parseStructured(content: string): Record<string, unknown> | undefined {
124
+ try {
125
+ const j = JSON.parse(content);
126
+ if (j && typeof j === "object") return j as Record<string, unknown>;
127
+ } catch {
128
+ // not JSON — try YAML
129
+ }
130
+ try {
131
+ const y = parseYAML(content);
132
+ if (y && typeof y === "object") return y;
133
+ } catch {
134
+ // not YAML either
135
+ }
136
+ return undefined;
137
+ }
138
+
139
+ /**
140
+ * A content detector for one lexicon. `detect` delegates to the plugin's
141
+ * `detectTemplate`; the surrounding fields encode the bits `detectTemplate`
142
+ * can't: which files to even consider, in what precedence, and how to normalize
143
+ * the content the lexicon's checks ultimately read.
144
+ */
145
+ interface ContentDetector {
146
+ lexicon: AuditLexicon;
147
+ /** Cheap filename gate before reading/parsing. */
148
+ accepts(name: string): boolean;
149
+ /**
150
+ * Decide whether this file belongs to the lexicon, by delegating to the
151
+ * plugin's detectTemplate. Returns the (possibly normalized) content to feed
152
+ * the checks, or null if it doesn't match.
153
+ */
154
+ detect(plugin: DetectPlugin, name: string, content: string): string | null;
155
+ }
156
+
157
+ /** Run a plugin's detectTemplate over each parsed doc; true if any matches. */
158
+ function anyDoc(plugin: DetectPlugin, content: string): boolean {
159
+ return parseDocs(content).some((doc) => plugin.detectTemplate?.(doc) ?? false);
160
+ }
161
+
162
+ /**
163
+ * Precedence-ordered content detectors. Order matters: gcp before k8s (Config
164
+ * Connector resources are also valid k8s manifests).
165
+ */
166
+ const CONTENT_DETECTORS: ContentDetector[] = [
167
+ {
168
+ // GCP Config Connector — must win over k8s for cnrm.cloud.google.com docs.
169
+ lexicon: "gcp",
170
+ accepts: isYaml,
171
+ detect: (plugin, _name, content) => (anyDoc(plugin, content) ? content : null),
172
+ },
173
+ {
174
+ lexicon: "k8s",
175
+ accepts: isYaml,
176
+ detect: (plugin, _name, content) => (anyDoc(plugin, content) ? content : null),
177
+ },
178
+ {
179
+ // CloudFormation — JSON, YAML, or `.template`; normalized to a JSON string
180
+ // because the aws checks JSON.parse their input.
181
+ lexicon: "aws",
182
+ accepts: (name) => /\.(json|ya?ml|template)$/i.test(name),
183
+ detect: (plugin, _name, content) => {
184
+ const parsed = parseStructured(content);
185
+ return parsed && plugin.detectTemplate?.(parsed) ? JSON.stringify(parsed) : null;
186
+ },
187
+ },
188
+ {
189
+ // Azure ARM — JSON only; azure's detectTemplate takes the raw string.
190
+ lexicon: "azure",
191
+ accepts: (name) => /\.json$/i.test(name),
192
+ detect: (plugin, _name, content) => {
193
+ if (!plugin.detectTemplate?.(content)) return null;
194
+ const parsed = parseStructured(content);
195
+ return parsed ? JSON.stringify(parsed) : null;
196
+ },
197
+ },
198
+ {
199
+ // Docker Compose (services:). Dockerfiles are handled separately by name.
200
+ lexicon: "docker",
201
+ accepts: isYaml,
202
+ detect: (plugin, _name, content) => {
203
+ const parsed = parseStructured(content);
204
+ return parsed && plugin.detectTemplate?.(parsed) ? content : null;
205
+ },
206
+ },
207
+ ];
208
+
209
+ /** A repo file (relative POSIX path + content) — the unit both local and remote feed the classifier. */
210
+ export interface RepoFile {
211
+ path: string;
212
+ content: string;
213
+ }
214
+
215
+ /**
216
+ * The minimal shape `classifyFiles` needs from a lexicon: its name and (for
217
+ * content-detected lexicons) its `detectTemplate`. Narrower than `LexiconPlugin`
218
+ * so an edge caller can build detectors from static `@…/detect` imports without
219
+ * loading the full plugin (which drags the TypeScript compiler — see #426).
220
+ */
221
+ export interface DetectPlugin {
222
+ name: string;
223
+ detectTemplate?: (data: unknown) => boolean;
224
+ }
225
+
226
+ /** Which CI lexicon a path belongs to (by location — content can't disambiguate github vs forgejo). */
227
+ function ciLexiconForPath(path: string): AuditLexicon | undefined {
228
+ if (/^\.github\/workflows\/[^/]+\.ya?ml$/.test(path)) return "github";
229
+ if (/^\.forgejo\/workflows\/[^/]+\.ya?ml$/.test(path)) return "forgejo";
230
+ if (path === ".gitlab-ci.yml") return "gitlab";
231
+ return undefined;
232
+ }
233
+
234
+ /**
235
+ * Whether a path is worth reading/fetching at all. Bounds the local walk and,
236
+ * crucially, the remote fetch (so we never pull a whole repo's contents).
237
+ */
238
+ export function isCandidatePath(path: string): boolean {
239
+ const name = basename(path);
240
+ if (ciLexiconForPath(path)) return true;
241
+ if (isDockerfileName(name)) return true;
242
+ if (name === "Chart.yaml") return true;
243
+ return /\.(ya?ml|json|template)$/i.test(name);
244
+ }
245
+
246
+ /** Collect Helm charts as bundles from an in-memory file set; returns inputs + chart path-prefixes. */
247
+ function classifyHelm(files: RepoFile[], plugin: DetectPlugin | undefined): { inputs: AuditInput[]; prefixes: string[] } {
248
+ const inputs: AuditInput[] = [];
249
+ const prefixes: string[] = [];
250
+ if (!plugin) return { inputs, prefixes };
251
+ const charts = files.filter((f) => f.path === "Chart.yaml" || f.path.endsWith("/Chart.yaml"));
252
+ for (const chartFile of charts) {
253
+ const dir = chartFile.path === "Chart.yaml" ? "" : chartFile.path.slice(0, -"/Chart.yaml".length);
254
+ const prefix = dir === "" ? "" : `${dir}/`;
255
+ const bundle: Record<string, string> = {};
256
+ for (const f of files) {
257
+ if (prefix !== "" && !f.path.startsWith(prefix)) continue;
258
+ bundle[prefix === "" ? f.path : f.path.slice(prefix.length)] = f.content;
259
+ }
260
+ const chart = bundle["Chart.yaml"];
261
+ if (chart === undefined) continue;
262
+ const parsed = parseStructured(chart);
263
+ if (!parsed || !plugin.detectTemplate?.(parsed)) continue;
264
+ inputs.push({ path: dir || ".", content: chart, lexicon: "helm", files: bundle });
265
+ prefixes.push(prefix);
266
+ }
267
+ return { inputs, prefixes };
268
+ }
269
+
270
+ /**
271
+ * Classify an in-memory set of repo files into per-lexicon audit inputs. Pure
272
+ * (no fs, no network) so the local walk and the remote tree-fetch share exactly
273
+ * one detection path. Each file maps to at most one lexicon: CI by path,
274
+ * Dockerfiles by name, Helm charts as a bundle, everything else by the
275
+ * precedence-ordered content detectors (delegating to each plugin's
276
+ * `detectTemplate`). Lexicons whose plugin isn't provided are skipped.
277
+ */
278
+ export function classifyFiles(files: RepoFile[], plugins: DetectPlugin[]): AuditInput[] {
279
+ const byName = new Map(plugins.map((p) => [p.name, p]));
280
+
281
+ // Helm claims whole chart directories first; chart-internal files are excluded
282
+ // from the loose-file pass so templates aren't double-audited.
283
+ const helm = classifyHelm(files, byName.get("helm"));
284
+ const underChart = (p: string): boolean => helm.prefixes.some((pre) => (pre === "" ? true : p.startsWith(pre)));
285
+
286
+ const inputs: AuditInput[] = [...helm.inputs];
287
+ for (const { path, content } of files) {
288
+ if (underChart(path)) continue;
289
+ const name = basename(path);
290
+
291
+ const ci = ciLexiconForPath(path);
292
+ if (ci) {
293
+ if (byName.has(ci)) inputs.push({ path, content, lexicon: ci });
294
+ continue;
295
+ }
296
+ if (isDockerfileName(name)) {
297
+ if (byName.has("docker")) inputs.push({ path, content, lexicon: "docker" });
298
+ continue;
299
+ }
300
+ for (const det of CONTENT_DETECTORS) {
301
+ const plugin = byName.get(det.lexicon);
302
+ if (!plugin || !det.accepts(name)) continue;
303
+ const normalized = det.detect(plugin, name, content);
304
+ if (normalized !== null) {
305
+ inputs.push({ path, content: normalized, lexicon: det.lexicon });
306
+ break; // first detector in precedence wins — one lexicon per file
307
+ }
308
+ }
309
+ }
310
+ return inputs;
311
+ }
312
+
313
+ /**
314
+ * Local discovery: one filesystem walk, candidate-filtered, read into memory,
315
+ * then handed to the shared `classifyFiles`. Detection is skipped for any
316
+ * lexicon whose plugin isn't provided, so a caller can scope discovery.
317
+ */
318
+ export function discoverByDetection(root: string, plugins: LexiconPlugin[]): AuditInput[] {
319
+ const all: string[] = [];
320
+ walkFiles(root, all);
321
+ const files: RepoFile[] = [];
322
+ for (const full of all) {
323
+ const path = relative(root, full);
324
+ if (!isCandidatePath(path)) continue;
325
+ const content = readSafe(full);
326
+ if (content !== undefined) files.push({ path, content });
327
+ }
328
+ return classifyFiles(files, plugins);
329
+ }
@@ -0,0 +1,43 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { readdirSync, readFileSync, existsSync } from "fs";
3
+ import { fileURLToPath } from "url";
4
+ import { join } from "path";
5
+
6
+ /**
7
+ * #428: the lexicon modules the hosted service imports on the edge — post-synth
8
+ * checks (+ their local helpers) and the `detect` modules — must not run Node-only
9
+ * APIs at module load. `createRequire(import.meta.url)` at module scope throws on
10
+ * Workers (import.meta.url is undefined), crashing the worker at startup. Anything
11
+ * that genuinely needs it must do so lazily, inside a function, behind a try.
12
+ */
13
+ const lexiconsDir = fileURLToPath(new URL("../../../../lexicons", import.meta.url));
14
+
15
+ function edgeImportedFiles(): string[] {
16
+ const out: string[] = [];
17
+ for (const lex of readdirSync(lexiconsDir)) {
18
+ const detect = join(lexiconsDir, lex, "src", "detect.ts");
19
+ if (existsSync(detect)) out.push(detect);
20
+ const ps = join(lexiconsDir, lex, "src", "lint", "post-synth");
21
+ if (existsSync(ps)) {
22
+ for (const f of readdirSync(ps)) {
23
+ if (f.endsWith(".ts") && !f.endsWith(".test.ts")) out.push(join(ps, f));
24
+ }
25
+ }
26
+ }
27
+ return out;
28
+ }
29
+
30
+ describe("edge-imported lexicon modules are init-safe", () => {
31
+ test("no module-scope createRequire(import.meta.url)", () => {
32
+ const offenders: string[] = [];
33
+ for (const file of edgeImportedFiles()) {
34
+ for (const line of readFileSync(file, "utf-8").split("\n")) {
35
+ // Module scope = no leading whitespace. Inside a function it's indented.
36
+ if (/^(const|let|var|\s*)?\S.*createRequire\(import\.meta\.url\)/.test(line) && !/^\s/.test(line)) {
37
+ offenders.push(`${file.replace(lexiconsDir, "lexicons")}: ${line.trim()}`);
38
+ }
39
+ }
40
+ }
41
+ expect(offenders, "move these into a function (lazy) — they crash edge bundles").toEqual([]);
42
+ });
43
+ });
@@ -1,5 +1,5 @@
1
1
  import { describe, test, expect } from "vitest";
2
- import { fetchCiFiles, parseRepoUrl, resolveActionSha, resolveImageDigest, parseImageRef, FetchError } from "./fetch";
2
+ import { fetchRepoFiles, parseRepoUrl, resolveActionSha, resolveImageDigest, parseImageRef, FetchError } from "./fetch";
3
3
 
4
4
  const b64 = (s: string) => Buffer.from(s, "utf-8").toString("base64");
5
5
  const CI_YAML = "name: CI\non:\n push:\npermissions: write-all\njobs:\n build:\n runs-on: ubuntu-latest\n";
@@ -9,6 +9,7 @@ interface Route {
9
9
  make: () => Response;
10
10
  }
11
11
 
12
+ /** Route-based fetch mock (used by the resolver tests). */
12
13
  function fakeFetch(routes: Route[]) {
13
14
  const calls: Array<{ url: string; init?: RequestInit }> = [];
14
15
  const impl = (async (url: string | URL | Request, init?: RequestInit) => {
@@ -21,16 +22,51 @@ function fakeFetch(routes: Route[]) {
21
22
  return { impl, calls };
22
23
  }
23
24
 
24
- const githubRoutes = (size = 100): Route[] => [
25
- {
26
- match: "/contents/.github/workflows/ci.yml",
27
- make: () => new Response(JSON.stringify({ name: "ci.yml", path: ".github/workflows/ci.yml", type: "file", content: b64(CI_YAML), encoding: "base64" }), { status: 200 }),
28
- },
29
- {
30
- match: "/contents/.github/workflows",
31
- make: () => new Response(JSON.stringify([{ name: "ci.yml", path: ".github/workflows/ci.yml", type: "file", size }]), { status: 200 }),
32
- },
33
- ];
25
+ /**
26
+ * A GitHub/Forgejo-shaped mock: repo-info default_branch, recursive git/trees,
27
+ * base64 contents. `sizes` overrides the reported blob size for cap tests.
28
+ */
29
+ function gitTreeMock(files: Record<string, string>, sizes: Record<string, number> = {}) {
30
+ const calls: Array<{ url: string; init?: RequestInit }> = [];
31
+ const impl = (async (url: string | URL | Request, init?: RequestInit) => {
32
+ const u = String(url);
33
+ calls.push({ url: u, init });
34
+ if (u.includes("/git/trees/")) {
35
+ const tree = Object.keys(files).map((path) => ({ path, type: "blob", size: sizes[path] ?? files[path].length }));
36
+ return new Response(JSON.stringify({ tree }), { status: 200 });
37
+ }
38
+ const cm = u.match(/\/contents\/(.+?)\?/);
39
+ if (cm) {
40
+ const path = decodeURIComponent(cm[1]);
41
+ if (files[path] === undefined) return new Response("not found", { status: 404 });
42
+ return new Response(JSON.stringify({ path, type: "file", content: b64(files[path]), encoding: "base64" }), { status: 200 });
43
+ }
44
+ if (/\/repos\/[^/]+\/[^/]+(\?|$)/.test(u)) return new Response(JSON.stringify({ default_branch: "main" }), { status: 200 });
45
+ return new Response("not found", { status: 404 });
46
+ }) as unknown as typeof fetch;
47
+ return { impl, calls };
48
+ }
49
+
50
+ /** A GitLab-shaped mock: project default_branch, paginated repository/tree, raw files. */
51
+ function gitlabMock(files: Record<string, string>) {
52
+ const impl = (async (url: string | URL | Request) => {
53
+ const u = String(url);
54
+ if (u.includes("/repository/tree")) {
55
+ const page = Number(new URL(u).searchParams.get("page") ?? "1");
56
+ const all = Object.keys(files).map((path) => ({ path, type: "blob" }));
57
+ return new Response(JSON.stringify(page === 1 ? all : []), { status: 200 });
58
+ }
59
+ const rm = u.match(/\/repository\/files\/(.+?)\/raw\?/);
60
+ if (rm) {
61
+ const path = decodeURIComponent(rm[1]);
62
+ if (files[path] === undefined) return new Response("not found", { status: 404 });
63
+ return new Response(files[path], { status: 200 });
64
+ }
65
+ if (/\/projects\/[^/]+(\?|$)/.test(u)) return new Response(JSON.stringify({ default_branch: "main" }), { status: 200 });
66
+ return new Response("not found", { status: 404 });
67
+ }) as unknown as typeof fetch;
68
+ return { impl };
69
+ }
34
70
 
35
71
  describe("parseRepoUrl", () => {
36
72
  test("rejects non-https", () => {
@@ -47,68 +83,63 @@ describe("parseRepoUrl", () => {
47
83
  });
48
84
  });
49
85
 
50
- describe("fetchCiFiles", () => {
51
- test("fetches and decodes github workflow files", async () => {
52
- const { impl } = fakeFetch(githubRoutes());
53
- const files = await fetchCiFiles("https://github.com/acme/widgets", { fetchImpl: impl });
54
- expect(files).toHaveLength(1);
55
- expect(files[0].path).toBe(".github/workflows/ci.yml");
56
- expect(files[0].lexicon).toBe("github");
86
+ describe("fetchRepoFiles", () => {
87
+ test("github: tree-walks and decodes candidate files", async () => {
88
+ const { impl } = gitTreeMock({ ".github/workflows/ci.yml": CI_YAML, "README.md": "# hi" });
89
+ const files = await fetchRepoFiles("https://github.com/acme/widgets", { fetchImpl: impl });
90
+ // README.md is not a candidate path; only the workflow is fetched.
91
+ expect(files.map((f) => f.path)).toEqual([".github/workflows/ci.yml"]);
57
92
  expect(files[0].content).toContain("permissions: write-all");
58
93
  });
59
94
 
60
95
  test("sends an auth token when provided", async () => {
61
- const { impl, calls } = fakeFetch(githubRoutes());
62
- await fetchCiFiles("https://github.com/acme/widgets", { fetchImpl: impl, token: "secret" });
96
+ const { impl, calls } = gitTreeMock({ ".github/workflows/ci.yml": CI_YAML });
97
+ await fetchRepoFiles("https://github.com/acme/widgets", { fetchImpl: impl, token: "secret" });
63
98
  const auth = calls.map((c) => (c.init?.headers as Record<string, string>)?.Authorization);
64
99
  expect(auth).toContain("Bearer secret");
65
100
  });
66
101
 
67
- test("skips a file over the per-file size cap", async () => {
68
- const { impl } = fakeFetch(githubRoutes(10_000_000));
69
- const files = await fetchCiFiles("https://github.com/acme/widgets", { fetchImpl: impl, maxBytesPerFile: 1024 });
102
+ test("skips a file over the per-file size cap (reported by the tree)", async () => {
103
+ const { impl } = gitTreeMock({ ".github/workflows/ci.yml": CI_YAML }, { ".github/workflows/ci.yml": 10_000_000 });
104
+ const files = await fetchRepoFiles("https://github.com/acme/widgets", { fetchImpl: impl, maxBytesPerFile: 1024 });
70
105
  expect(files).toHaveLength(0);
71
106
  });
72
107
 
73
- test("refuses to follow a redirect", async () => {
74
- const { impl } = fakeFetch([{ match: "/contents/", make: () => new Response(null, { status: 302 }) }]);
75
- await expect(fetchCiFiles("https://github.com/acme/widgets", { fetchImpl: impl })).rejects.toThrow(/redirect/i);
108
+ test("throws when the total byte cap is exceeded", async () => {
109
+ const big = "x".repeat(2000);
110
+ const { impl } = gitTreeMock({ "a.yaml": big, "b.yaml": big });
111
+ await expect(fetchRepoFiles("https://github.com/acme/widgets", { fetchImpl: impl, maxTotalBytes: 3000 })).rejects.toThrow(/total size cap/i);
112
+ });
113
+
114
+ test("refuses to follow a redirect on the tree request", async () => {
115
+ const impl = (async (url: string | URL | Request) => {
116
+ const u = String(url);
117
+ if (u.includes("/git/trees/")) return new Response(null, { status: 302 });
118
+ return new Response(JSON.stringify({ default_branch: "main" }), { status: 200 });
119
+ }) as unknown as typeof fetch;
120
+ await expect(fetchRepoFiles("https://github.com/acme/widgets", { fetchImpl: impl })).rejects.toThrow(/redirect/i);
76
121
  });
77
122
 
78
123
  test("throws on a non-allowlisted host before any fetch", async () => {
79
- await expect(fetchCiFiles("https://evil.example.com/o/r")).rejects.toThrow(/Host not allowed/);
124
+ await expect(fetchRepoFiles("https://evil.example.com/o/r")).rejects.toThrow(/Host not allowed/);
80
125
  });
81
126
 
82
- test("returns the gitlab pipeline file", async () => {
83
- const { impl } = fakeFetch([
84
- { match: "/repository/files/", make: () => new Response("stages:\n - build\n", { status: 200 }) },
85
- ]);
86
- const files = await fetchCiFiles("https://gitlab.com/acme/widgets", { fetchImpl: impl });
87
- expect(files).toHaveLength(1);
88
- expect(files[0].path).toBe(".gitlab-ci.yml");
89
- expect(files[0].lexicon).toBe("gitlab");
90
- });
91
-
92
- test("forgejo (codeberg) reads .forgejo/workflows", async () => {
93
- const { impl } = fakeFetch([
94
- {
95
- match: "/contents/.forgejo/workflows/ci.yml",
96
- make: () => new Response(JSON.stringify({ name: "ci.yml", path: ".forgejo/workflows/ci.yml", type: "file", content: b64(CI_YAML), encoding: "base64" }), { status: 200 }),
97
- },
98
- {
99
- match: "/contents/.forgejo/workflows",
100
- make: () => new Response(JSON.stringify([{ name: "ci.yml", path: ".forgejo/workflows/ci.yml", type: "file", size: 100 }]), { status: 200 }),
101
- },
102
- ]);
103
- const files = await fetchCiFiles("https://codeberg.org/acme/widgets", { fetchImpl: impl });
104
- expect(files).toHaveLength(1);
105
- expect(files[0].lexicon).toBe("forgejo");
106
- expect(files[0].path).toBe(".forgejo/workflows/ci.yml");
127
+ test("gitlab: paginated tree + raw file content", async () => {
128
+ const { impl } = gitlabMock({ ".gitlab-ci.yml": "stages:\n - build\n" });
129
+ const files = await fetchRepoFiles("https://gitlab.com/acme/widgets", { fetchImpl: impl });
130
+ expect(files.map((f) => f.path)).toEqual([".gitlab-ci.yml"]);
131
+ expect(files[0].content).toContain("stages:");
132
+ });
133
+
134
+ test("forgejo (codeberg): gitea tree + contents", async () => {
135
+ const { impl } = gitTreeMock({ ".forgejo/workflows/ci.yml": CI_YAML });
136
+ const files = await fetchRepoFiles("https://codeberg.org/acme/widgets", { fetchImpl: impl });
137
+ expect(files.map((f) => f.path)).toEqual([".forgejo/workflows/ci.yml"]);
107
138
  });
108
139
 
109
- test("a repo with no CI files returns []", async () => {
110
- const { impl } = fakeFetch([]); // everything 404s
111
- const files = await fetchCiFiles("https://github.com/acme/empty", { fetchImpl: impl });
140
+ test("an empty repo returns []", async () => {
141
+ const { impl } = gitTreeMock({});
142
+ const files = await fetchRepoFiles("https://github.com/acme/empty", { fetchImpl: impl });
112
143
  expect(files).toEqual([]);
113
144
  });
114
145
  });