@endora-commerce/cli 0.100.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 (356) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -0
  3. package/dist/bin/endora.d.ts +28 -0
  4. package/dist/bin/endora.d.ts.map +1 -0
  5. package/dist/bin/endora.js +926 -0
  6. package/dist/bin/endora.js.map +1 -0
  7. package/dist/check/estate.d.ts +189 -0
  8. package/dist/check/estate.d.ts.map +1 -0
  9. package/dist/check/estate.js +1037 -0
  10. package/dist/check/estate.js.map +1 -0
  11. package/dist/check/hosts.d.ts +25 -0
  12. package/dist/check/hosts.d.ts.map +1 -0
  13. package/dist/check/hosts.js +1347 -0
  14. package/dist/check/hosts.js.map +1 -0
  15. package/dist/check/index.d.ts +60 -0
  16. package/dist/check/index.d.ts.map +1 -0
  17. package/dist/check/index.js +111 -0
  18. package/dist/check/index.js.map +1 -0
  19. package/dist/check/layout.d.ts +136 -0
  20. package/dist/check/layout.d.ts.map +1 -0
  21. package/dist/check/layout.js +262 -0
  22. package/dist/check/layout.js.map +1 -0
  23. package/dist/check/ledger.d.ts +98 -0
  24. package/dist/check/ledger.d.ts.map +1 -0
  25. package/dist/check/ledger.js +173 -0
  26. package/dist/check/ledger.js.map +1 -0
  27. package/dist/check/peer-owners.d.ts +116 -0
  28. package/dist/check/peer-owners.d.ts.map +1 -0
  29. package/dist/check/peer-owners.js +225 -0
  30. package/dist/check/peer-owners.js.map +1 -0
  31. package/dist/check/report.d.ts +33 -0
  32. package/dist/check/report.d.ts.map +1 -0
  33. package/dist/check/report.js +107 -0
  34. package/dist/check/report.js.map +1 -0
  35. package/dist/check/run.d.ts +147 -0
  36. package/dist/check/run.d.ts.map +1 -0
  37. package/dist/check/run.js +111 -0
  38. package/dist/check/run.js.map +1 -0
  39. package/dist/checks.d.ts +17 -0
  40. package/dist/checks.d.ts.map +1 -0
  41. package/dist/checks.js +17 -0
  42. package/dist/checks.js.map +1 -0
  43. package/dist/dev/index.d.ts +83 -0
  44. package/dist/dev/index.d.ts.map +1 -0
  45. package/dist/dev/index.js +298 -0
  46. package/dist/dev/index.js.map +1 -0
  47. package/dist/generate/divergence.d.ts +38 -0
  48. package/dist/generate/divergence.d.ts.map +1 -0
  49. package/dist/generate/divergence.js +237 -0
  50. package/dist/generate/divergence.js.map +1 -0
  51. package/dist/generate/index.d.ts +90 -0
  52. package/dist/generate/index.d.ts.map +1 -0
  53. package/dist/generate/index.js +369 -0
  54. package/dist/generate/index.js.map +1 -0
  55. package/dist/index.d.ts +23 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +35 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/inputs/declaration.d.ts +18 -0
  60. package/dist/inputs/declaration.d.ts.map +1 -0
  61. package/dist/inputs/declaration.js +64 -0
  62. package/dist/inputs/declaration.js.map +1 -0
  63. package/dist/inputs/env-file.d.ts +73 -0
  64. package/dist/inputs/env-file.d.ts.map +1 -0
  65. package/dist/inputs/env-file.js +134 -0
  66. package/dist/inputs/env-file.js.map +1 -0
  67. package/dist/inputs/prompt.d.ts +21 -0
  68. package/dist/inputs/prompt.d.ts.map +1 -0
  69. package/dist/inputs/prompt.js +59 -0
  70. package/dist/inputs/prompt.js.map +1 -0
  71. package/dist/inputs/resolve.d.ts +163 -0
  72. package/dist/inputs/resolve.d.ts.map +1 -0
  73. package/dist/inputs/resolve.js +290 -0
  74. package/dist/inputs/resolve.js.map +1 -0
  75. package/dist/install/host.d.ts +27 -0
  76. package/dist/install/host.d.ts.map +1 -0
  77. package/dist/install/host.js +90 -0
  78. package/dist/install/host.js.map +1 -0
  79. package/dist/install/index.d.ts +173 -0
  80. package/dist/install/index.d.ts.map +1 -0
  81. package/dist/install/index.js +793 -0
  82. package/dist/install/index.js.map +1 -0
  83. package/dist/install/wizard.d.ts +144 -0
  84. package/dist/install/wizard.d.ts.map +1 -0
  85. package/dist/install/wizard.js +362 -0
  86. package/dist/install/wizard.js.map +1 -0
  87. package/dist/lib/admin-artefacts.d.ts +70 -0
  88. package/dist/lib/admin-artefacts.d.ts.map +1 -0
  89. package/dist/lib/admin-artefacts.js +354 -0
  90. package/dist/lib/admin-artefacts.js.map +1 -0
  91. package/dist/lib/admin-surfaces.d.ts +298 -0
  92. package/dist/lib/admin-surfaces.d.ts.map +1 -0
  93. package/dist/lib/admin-surfaces.js +669 -0
  94. package/dist/lib/admin-surfaces.js.map +1 -0
  95. package/dist/lib/delegated-composer.d.ts +85 -0
  96. package/dist/lib/delegated-composer.d.ts.map +1 -0
  97. package/dist/lib/delegated-composer.js +241 -0
  98. package/dist/lib/delegated-composer.js.map +1 -0
  99. package/dist/lib/divergence-artefacts.d.ts +305 -0
  100. package/dist/lib/divergence-artefacts.d.ts.map +1 -0
  101. package/dist/lib/divergence-artefacts.js +828 -0
  102. package/dist/lib/divergence-artefacts.js.map +1 -0
  103. package/dist/lib/divergence.d.ts +337 -0
  104. package/dist/lib/divergence.d.ts.map +1 -0
  105. package/dist/lib/divergence.js +1005 -0
  106. package/dist/lib/divergence.js.map +1 -0
  107. package/dist/lib/docs-artefacts.d.ts +395 -0
  108. package/dist/lib/docs-artefacts.d.ts.map +1 -0
  109. package/dist/lib/docs-artefacts.js +781 -0
  110. package/dist/lib/docs-artefacts.js.map +1 -0
  111. package/dist/lib/emitted-exports.d.ts +21 -0
  112. package/dist/lib/emitted-exports.d.ts.map +1 -0
  113. package/dist/lib/emitted-exports.js +96 -0
  114. package/dist/lib/emitted-exports.js.map +1 -0
  115. package/dist/lib/emitted-freshness.d.ts +112 -0
  116. package/dist/lib/emitted-freshness.d.ts.map +1 -0
  117. package/dist/lib/emitted-freshness.js +288 -0
  118. package/dist/lib/emitted-freshness.js.map +1 -0
  119. package/dist/lib/entity-index-artefact.d.ts +95 -0
  120. package/dist/lib/entity-index-artefact.d.ts.map +1 -0
  121. package/dist/lib/entity-index-artefact.js +210 -0
  122. package/dist/lib/entity-index-artefact.js.map +1 -0
  123. package/dist/lib/instance-build-inputs.d.ts +108 -0
  124. package/dist/lib/instance-build-inputs.d.ts.map +1 -0
  125. package/dist/lib/instance-build-inputs.js +165 -0
  126. package/dist/lib/instance-build-inputs.js.map +1 -0
  127. package/dist/lib/module-docs.d.ts +473 -0
  128. package/dist/lib/module-docs.d.ts.map +1 -0
  129. package/dist/lib/module-docs.js +711 -0
  130. package/dist/lib/module-docs.js.map +1 -0
  131. package/dist/lib/module-package-subpaths.d.ts +56 -0
  132. package/dist/lib/module-package-subpaths.d.ts.map +1 -0
  133. package/dist/lib/module-package-subpaths.js +223 -0
  134. package/dist/lib/module-package-subpaths.js.map +1 -0
  135. package/dist/lib/module-packages.d.ts +200 -0
  136. package/dist/lib/module-packages.d.ts.map +1 -0
  137. package/dist/lib/module-packages.js +580 -0
  138. package/dist/lib/module-packages.js.map +1 -0
  139. package/dist/lib/module-population.d.ts +129 -0
  140. package/dist/lib/module-population.d.ts.map +1 -0
  141. package/dist/lib/module-population.js +172 -0
  142. package/dist/lib/module-population.js.map +1 -0
  143. package/dist/lib/module-roots.d.ts +337 -0
  144. package/dist/lib/module-roots.d.ts.map +1 -0
  145. package/dist/lib/module-roots.js +586 -0
  146. package/dist/lib/module-roots.js.map +1 -0
  147. package/dist/lib/nested-checkouts.d.ts +33 -0
  148. package/dist/lib/nested-checkouts.d.ts.map +1 -0
  149. package/dist/lib/nested-checkouts.js +160 -0
  150. package/dist/lib/nested-checkouts.js.map +1 -0
  151. package/dist/lib/platform-root.d.ts +43 -0
  152. package/dist/lib/platform-root.d.ts.map +1 -0
  153. package/dist/lib/platform-root.js +134 -0
  154. package/dist/lib/platform-root.js.map +1 -0
  155. package/dist/lib/platform-surface.d.ts +235 -0
  156. package/dist/lib/platform-surface.d.ts.map +1 -0
  157. package/dist/lib/platform-surface.js +393 -0
  158. package/dist/lib/platform-surface.js.map +1 -0
  159. package/dist/lib/port-registrations.d.ts +223 -0
  160. package/dist/lib/port-registrations.d.ts.map +1 -0
  161. package/dist/lib/port-registrations.js +532 -0
  162. package/dist/lib/port-registrations.js.map +1 -0
  163. package/dist/lib/read-size.d.ts +154 -0
  164. package/dist/lib/read-size.d.ts.map +1 -0
  165. package/dist/lib/read-size.js +182 -0
  166. package/dist/lib/read-size.js.map +1 -0
  167. package/dist/lib/registration-owners.d.ts +79 -0
  168. package/dist/lib/registration-owners.d.ts.map +1 -0
  169. package/dist/lib/registration-owners.js +77 -0
  170. package/dist/lib/registration-owners.js.map +1 -0
  171. package/dist/lib/release-index.d.ts +53 -0
  172. package/dist/lib/release-index.d.ts.map +1 -0
  173. package/dist/lib/release-index.js +162 -0
  174. package/dist/lib/release-index.js.map +1 -0
  175. package/dist/lib/repeating-timers.d.ts +79 -0
  176. package/dist/lib/repeating-timers.d.ts.map +1 -0
  177. package/dist/lib/repeating-timers.js +189 -0
  178. package/dist/lib/repeating-timers.js.map +1 -0
  179. package/dist/lib/source-text.d.ts +34 -0
  180. package/dist/lib/source-text.d.ts.map +1 -0
  181. package/dist/lib/source-text.js +80 -0
  182. package/dist/lib/source-text.js.map +1 -0
  183. package/dist/lib/specifiers.d.ts +20 -0
  184. package/dist/lib/specifiers.d.ts.map +1 -0
  185. package/dist/lib/specifiers.js +130 -0
  186. package/dist/lib/specifiers.js.map +1 -0
  187. package/dist/lib/sql-tables.d.ts +166 -0
  188. package/dist/lib/sql-tables.d.ts.map +1 -0
  189. package/dist/lib/sql-tables.js +464 -0
  190. package/dist/lib/sql-tables.js.map +1 -0
  191. package/dist/lib/switchable-modules.d.ts +54 -0
  192. package/dist/lib/switchable-modules.d.ts.map +1 -0
  193. package/dist/lib/switchable-modules.js +104 -0
  194. package/dist/lib/switchable-modules.js.map +1 -0
  195. package/dist/lib/tailwind-sources.d.ts +136 -0
  196. package/dist/lib/tailwind-sources.d.ts.map +1 -0
  197. package/dist/lib/tailwind-sources.js +307 -0
  198. package/dist/lib/tailwind-sources.js.map +1 -0
  199. package/dist/lib/ui-layer.d.ts +54 -0
  200. package/dist/lib/ui-layer.d.ts.map +1 -0
  201. package/dist/lib/ui-layer.js +57 -0
  202. package/dist/lib/ui-layer.js.map +1 -0
  203. package/dist/lib/workspace-packages.d.ts +186 -0
  204. package/dist/lib/workspace-packages.d.ts.map +1 -0
  205. package/dist/lib/workspace-packages.js +351 -0
  206. package/dist/lib/workspace-packages.js.map +1 -0
  207. package/dist/new-instance/deploy.d.ts +211 -0
  208. package/dist/new-instance/deploy.d.ts.map +1 -0
  209. package/dist/new-instance/deploy.js +1381 -0
  210. package/dist/new-instance/deploy.js.map +1 -0
  211. package/dist/new-instance/docs-toolchain.d.ts +66 -0
  212. package/dist/new-instance/docs-toolchain.d.ts.map +1 -0
  213. package/dist/new-instance/docs-toolchain.js +69 -0
  214. package/dist/new-instance/docs-toolchain.js.map +1 -0
  215. package/dist/new-instance/host.d.ts +124 -0
  216. package/dist/new-instance/host.d.ts.map +1 -0
  217. package/dist/new-instance/host.js +276 -0
  218. package/dist/new-instance/host.js.map +1 -0
  219. package/dist/new-instance/index.d.ts +118 -0
  220. package/dist/new-instance/index.d.ts.map +1 -0
  221. package/dist/new-instance/index.js +567 -0
  222. package/dist/new-instance/index.js.map +1 -0
  223. package/dist/new-instance/modules.d.ts +188 -0
  224. package/dist/new-instance/modules.d.ts.map +1 -0
  225. package/dist/new-instance/modules.js +392 -0
  226. package/dist/new-instance/modules.js.map +1 -0
  227. package/dist/new-instance/template.d.ts +505 -0
  228. package/dist/new-instance/template.d.ts.map +1 -0
  229. package/dist/new-instance/template.js +1886 -0
  230. package/dist/new-instance/template.js.map +1 -0
  231. package/dist/new-module/emit.d.ts +67 -0
  232. package/dist/new-module/emit.d.ts.map +1 -0
  233. package/dist/new-module/emit.js +1393 -0
  234. package/dist/new-module/emit.js.map +1 -0
  235. package/dist/new-module/host.d.ts +67 -0
  236. package/dist/new-module/host.d.ts.map +1 -0
  237. package/dist/new-module/host.js +224 -0
  238. package/dist/new-module/host.js.map +1 -0
  239. package/dist/new-module/index.d.ts +32 -0
  240. package/dist/new-module/index.d.ts.map +1 -0
  241. package/dist/new-module/index.js +193 -0
  242. package/dist/new-module/index.js.map +1 -0
  243. package/dist/new-module/spec.d.ts +188 -0
  244. package/dist/new-module/spec.d.ts.map +1 -0
  245. package/dist/new-module/spec.js +404 -0
  246. package/dist/new-module/spec.js.map +1 -0
  247. package/dist/new-module/text.d.ts +15 -0
  248. package/dist/new-module/text.d.ts.map +1 -0
  249. package/dist/new-module/text.js +22 -0
  250. package/dist/new-module/text.js.map +1 -0
  251. package/dist/new-storefront/dockerfile.d.ts +20 -0
  252. package/dist/new-storefront/dockerfile.d.ts.map +1 -0
  253. package/dist/new-storefront/dockerfile.js +131 -0
  254. package/dist/new-storefront/dockerfile.js.map +1 -0
  255. package/dist/new-storefront/gitignore.d.ts +23 -0
  256. package/dist/new-storefront/gitignore.d.ts.map +1 -0
  257. package/dist/new-storefront/gitignore.js +39 -0
  258. package/dist/new-storefront/gitignore.js.map +1 -0
  259. package/dist/new-storefront/index.d.ts +93 -0
  260. package/dist/new-storefront/index.d.ts.map +1 -0
  261. package/dist/new-storefront/index.js +329 -0
  262. package/dist/new-storefront/index.js.map +1 -0
  263. package/dist/new-storefront/npmrc.d.ts +98 -0
  264. package/dist/new-storefront/npmrc.d.ts.map +1 -0
  265. package/dist/new-storefront/npmrc.js +217 -0
  266. package/dist/new-storefront/npmrc.js.map +1 -0
  267. package/dist/new-storefront/reference.d.ts +189 -0
  268. package/dist/new-storefront/reference.d.ts.map +1 -0
  269. package/dist/new-storefront/reference.js +430 -0
  270. package/dist/new-storefront/reference.js.map +1 -0
  271. package/dist/new-storefront/rewrite.d.ts +171 -0
  272. package/dist/new-storefront/rewrite.d.ts.map +1 -0
  273. package/dist/new-storefront/rewrite.js +701 -0
  274. package/dist/new-storefront/rewrite.js.map +1 -0
  275. package/dist/release-index.json +284 -0
  276. package/dist/rules/action-route-permissions.d.ts +141 -0
  277. package/dist/rules/action-route-permissions.d.ts.map +1 -0
  278. package/dist/rules/action-route-permissions.js +556 -0
  279. package/dist/rules/action-route-permissions.js.map +1 -0
  280. package/dist/rules/bundle-pairing.d.ts +74 -0
  281. package/dist/rules/bundle-pairing.d.ts.map +1 -0
  282. package/dist/rules/bundle-pairing.js +281 -0
  283. package/dist/rules/bundle-pairing.js.map +1 -0
  284. package/dist/rules/channel-resolution.d.ts +17 -0
  285. package/dist/rules/channel-resolution.d.ts.map +1 -0
  286. package/dist/rules/channel-resolution.js +382 -0
  287. package/dist/rules/channel-resolution.js.map +1 -0
  288. package/dist/rules/command-coverage.d.ts +210 -0
  289. package/dist/rules/command-coverage.d.ts.map +1 -0
  290. package/dist/rules/command-coverage.js +714 -0
  291. package/dist/rules/command-coverage.js.map +1 -0
  292. package/dist/rules/container-imports.d.ts +60 -0
  293. package/dist/rules/container-imports.d.ts.map +1 -0
  294. package/dist/rules/container-imports.js +158 -0
  295. package/dist/rules/container-imports.js.map +1 -0
  296. package/dist/rules/default-language-prose.d.ts +212 -0
  297. package/dist/rules/default-language-prose.d.ts.map +1 -0
  298. package/dist/rules/default-language-prose.js +710 -0
  299. package/dist/rules/default-language-prose.js.map +1 -0
  300. package/dist/rules/diacritic-folds.d.ts +238 -0
  301. package/dist/rules/diacritic-folds.d.ts.map +1 -0
  302. package/dist/rules/diacritic-folds.js +681 -0
  303. package/dist/rules/diacritic-folds.js.map +1 -0
  304. package/dist/rules/entity-tenant-classification.d.ts +171 -0
  305. package/dist/rules/entity-tenant-classification.d.ts.map +1 -0
  306. package/dist/rules/entity-tenant-classification.js +323 -0
  307. package/dist/rules/entity-tenant-classification.js.map +1 -0
  308. package/dist/rules/entry-presence.d.ts +142 -0
  309. package/dist/rules/entry-presence.d.ts.map +1 -0
  310. package/dist/rules/entry-presence.js +339 -0
  311. package/dist/rules/entry-presence.js.map +1 -0
  312. package/dist/rules/entry-scope.d.ts +91 -0
  313. package/dist/rules/entry-scope.d.ts.map +1 -0
  314. package/dist/rules/entry-scope.js +404 -0
  315. package/dist/rules/entry-scope.js.map +1 -0
  316. package/dist/rules/env-inputs.d.ts +222 -0
  317. package/dist/rules/env-inputs.d.ts.map +1 -0
  318. package/dist/rules/env-inputs.js +951 -0
  319. package/dist/rules/env-inputs.js.map +1 -0
  320. package/dist/rules/kernel-boundary.d.ts +37 -0
  321. package/dist/rules/kernel-boundary.d.ts.map +1 -0
  322. package/dist/rules/kernel-boundary.js +195 -0
  323. package/dist/rules/kernel-boundary.js.map +1 -0
  324. package/dist/rules/nul-bytes.d.ts +233 -0
  325. package/dist/rules/nul-bytes.d.ts.map +1 -0
  326. package/dist/rules/nul-bytes.js +332 -0
  327. package/dist/rules/nul-bytes.js.map +1 -0
  328. package/dist/rules/platform-surface.d.ts +479 -0
  329. package/dist/rules/platform-surface.d.ts.map +1 -0
  330. package/dist/rules/platform-surface.js +749 -0
  331. package/dist/rules/platform-surface.js.map +1 -0
  332. package/dist/rules/port-catches.d.ts +225 -0
  333. package/dist/rules/port-catches.d.ts.map +1 -0
  334. package/dist/rules/port-catches.js +1374 -0
  335. package/dist/rules/port-catches.js.map +1 -0
  336. package/dist/rules/port-shape.d.ts +213 -0
  337. package/dist/rules/port-shape.d.ts.map +1 -0
  338. package/dist/rules/port-shape.js +670 -0
  339. package/dist/rules/port-shape.js.map +1 -0
  340. package/dist/rules/queue-names.d.ts +108 -0
  341. package/dist/rules/queue-names.d.ts.map +1 -0
  342. package/dist/rules/queue-names.js +395 -0
  343. package/dist/rules/queue-names.js.map +1 -0
  344. package/dist/rules/singleton-identity.d.ts +205 -0
  345. package/dist/rules/singleton-identity.d.ts.map +1 -0
  346. package/dist/rules/singleton-identity.js +830 -0
  347. package/dist/rules/singleton-identity.js.map +1 -0
  348. package/dist/rules/subscribe-seam.d.ts +121 -0
  349. package/dist/rules/subscribe-seam.d.ts.map +1 -0
  350. package/dist/rules/subscribe-seam.js +594 -0
  351. package/dist/rules/subscribe-seam.js.map +1 -0
  352. package/dist/rules/transaction-context.d.ts +40 -0
  353. package/dist/rules/transaction-context.d.ts.map +1 -0
  354. package/dist/rules/transaction-context.js +294 -0
  355. package/dist/rules/transaction-context.js.map +1 -0
  356. package/package.json +59 -0
@@ -0,0 +1,711 @@
1
+ /**
2
+ * "Which module is this documentation page about, and where does the site keep
3
+ * it?" — one derivation, two readers (feature 100 / roadmap F12, Phase 1).
4
+ *
5
+ * `backend/scripts/generate-composer.ts` emits the navigation from it and
6
+ * `backend/scripts/check-module-docs.ts` refuses the population defects it
7
+ * cannot see. They are deliberately not two walks: three hand-maintained lists
8
+ * already described one population — the docs sidebar, the module map and the
9
+ * pages on disk — and all three disagreed with the generated manifest index and
10
+ * with each other, which is the whole of `spec.md` § 0.2. A second derivation is
11
+ * two answers waiting to disagree, which is the state this feature ends.
12
+ *
13
+ * ## What is derived, and from whose declaration
14
+ *
15
+ * Nothing here is a repository path written down (D-100):
16
+ *
17
+ * * **the site** is the workspace member holding a `docusaurus.config.*`.
18
+ * Zero is a refusal and two is a refusal — an ambiguous site silently
19
+ * narrows every walk to whichever sorted first, which is the failure
20
+ * `lib/module-roots.ts` records for the manifest index;
21
+ * * **the content root** is that config's own `path` for the docs preset,
22
+ * defaulting to Docusaurus's own `docs` when the config declares none, as
23
+ * this site's does;
24
+ * * **the module ids** are the generated manifest index's, through
25
+ * `lib/module-population.ts`, so a module that became a package is followed
26
+ * rather than dropped (issue #215);
27
+ * * **the front matter** is Docusaurus's own — `title`, `sidebar_label`,
28
+ * `sidebar_position`, `description`. No field this repository invented, so a
29
+ * third-party module author writes ordinary Docusaurus markdown and learns
30
+ * nothing from us (`contracts/module-documentation-layer.md` R3.4).
31
+ *
32
+ * The one name this feature does own is {@link MODULES_CATEGORY}, the directory
33
+ * under the content root that holds a module's pages. It is a decision rather
34
+ * than a derived fact — a category has to be called something — so it is
35
+ * declared once, here, and read by both consumers.
36
+ *
37
+ * ## Attribution — the shipper, then the slug
38
+ *
39
+ * A page a **module ships** is that module's: the walk knows which `docs/`
40
+ * layer it came out of, and no derivation from the file name can be more
41
+ * authoritative than the module's own declaration. A page in the **site's own
42
+ * tree** has no shipper, so its slug answers — `google-analytics` is
43
+ * `google_analytics`, and `lifecycle` is `_lifecycle`, by
44
+ * {@link slugNamesModule}'s two derivations (D-200).
45
+ *
46
+ * The two can disagree, and the disagreement is a **finding** rather than a
47
+ * silent re-attribution: a module shipping a page under a *sibling's* slug
48
+ * takes an address the sibling owns, which is Constitution I applied to prose
49
+ * and the same shape `check:admin-zones` refuses as `foreign-module-id`.
50
+ *
51
+ * A module may own **more than one slug** — `organizations` ships both
52
+ * `organizations.md` and `organization-hierarchy.md` — and that costs nothing,
53
+ * because the slug is how a reader finds a page and the shipper is who owns it.
54
+ * A four-entry table of hand-declared attributions used to stand here for
55
+ * exactly the cases the two rules above now cover; D-200 answered the question
56
+ * it was waiting on and it retired with the answer.
57
+ *
58
+ * ## What this file cannot see, stated here rather than discovered later
59
+ *
60
+ * * **Front matter is a leading `---`-delimited block** and is read as
61
+ * `key: value` lines. A page whose front matter is produced at build time by
62
+ * a remark plugin has none as far as this is concerned, and a value spanning
63
+ * lines is read as its first line.
64
+ * * **A doc id is a file path**, so a page that overrides its own `id` or
65
+ * `slug` in front matter is followed for neither. Nothing in the tree does;
66
+ * if something starts to, this file is where the reader goes.
67
+ * * **It says nothing about whether a page is good, current or complete.** It
68
+ * answers reachability and attribution.
69
+ */
70
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
71
+ import { basename, join } from 'node:path';
72
+ import { pathToFileURL } from 'node:url';
73
+ import { workspaceMembers, nodeWorkspaceFs } from './workspace-packages.js';
74
+ /**
75
+ * The directory under the site's content root that holds a module's pages.
76
+ *
77
+ * The one name this feature owns rather than derives. It is declared here so
78
+ * that the generator and the check cannot come to disagree about it, and so a
79
+ * reader looking for "where does `modules/` come from" finds one answer.
80
+ */
81
+ export const MODULES_CATEGORY = 'modules';
82
+ /** The page every reader of a module's category lands on. */
83
+ export const CATEGORY_INDEX = 'index';
84
+ /**
85
+ * The generated sidebar fragment, at the site's root beside `sidebars.js`.
86
+ *
87
+ * Named here rather than in the generator so that the generator, `overlay:check`
88
+ * and `check:module-docs` cannot come to disagree about which file they mean.
89
+ */
90
+ export const DOCS_SIDEBAR_ARTEFACT = 'sidebars.modules.generated.js';
91
+ /** The generated module map, inside the modules category it indexes. */
92
+ export const MODULE_MAP_ARTEFACT = 'module-map.generated.md';
93
+ /**
94
+ * The category holding one generated reference page per module (Phase 3,
95
+ * FR-022/FR-024).
96
+ *
97
+ * A category of its own, **outside** {@link MODULES_CATEGORY}, and the
98
+ * separation is the design rather than a filing preference. Three things follow
99
+ * from it, none of which would if the pages sat beside the prose:
100
+ *
101
+ * * **a generated page can never collide with a hand-written one.** A module's
102
+ * prose page is `modules/<slug>`, its reference page `module-reference/<slug>`.
103
+ * There is no name a page author can choose that takes an address the
104
+ * generator writes, and no rule anybody has to remember;
105
+ * * **the attribution walk's population does not move.** `undocumented-module`
106
+ * asks whether anybody *wrote* about a module, and a generated table is not
107
+ * an answer to it — a reference page inside the modules category would have
108
+ * made every registered module documented and retired that whole ledger in
109
+ * the merge request that added the generator;
110
+ * * **it is committed on ordinary terms.** `docs/docs/modules/**` is
111
+ * git-ignored, because the module-owned pages are copied there at build
112
+ * time, so a committed artefact under that tree would need `git add -f` for
113
+ * ever after.
114
+ *
115
+ * The pages are still *reached* from the Modules category: the generated
116
+ * sidebar fragment names each module's reference page beside its prose, so a
117
+ * reader never has to know that the two live in different directories.
118
+ */
119
+ export const MODULE_REFERENCE_CATEGORY = 'module-reference';
120
+ /**
121
+ * The documentation slug that names a module — {@link slugNamesModule}'s
122
+ * inverse, and the one place that choice is made.
123
+ *
124
+ * A slug is hyphenated where an id is snake_case, and a **leading underscore is
125
+ * dropped**: Docusaurus excludes an underscore-prefixed file from routing by
126
+ * design, so `_i18n` is documented at `i18n` and `_lifecycle` at `lifecycle`
127
+ * (D-200). Both transformations are {@link slugNamesModule}'s applied the other
128
+ * way round, so a page this names is a page that derivation attributes back —
129
+ * asserted as a round trip over every registered id rather than left to the two
130
+ * staying in step by inspection.
131
+ */
132
+ export function slugForModule(moduleId) {
133
+ return moduleId.replace(/^_/, '').split('_').join('-');
134
+ }
135
+ /** Extensions Docusaurus reads as a documentation page. */
136
+ export const PAGE_EXTENSIONS = ['.md', '.mdx'];
137
+ /** Raised when the documentation layout cannot be resolved; a caller exits 2. */
138
+ export class DocsLayoutUnresolvableError extends Error {
139
+ name = 'DocsLayoutUnresolvableError';
140
+ }
141
+ /** Docusaurus config file names, in the order Docusaurus itself accepts them. */
142
+ const CONFIG_FILENAMES = [
143
+ 'docusaurus.config.js',
144
+ 'docusaurus.config.mjs',
145
+ 'docusaurus.config.cjs',
146
+ 'docusaurus.config.ts',
147
+ ];
148
+ /**
149
+ * The docs content root the config declares, or Docusaurus's own default.
150
+ *
151
+ * Read as a literal `path:` inside the docs preset options. A computed value is
152
+ * not followed — it is reported as absent, which lands on the default, and the
153
+ * bound is stated here rather than discovered later.
154
+ */
155
+ export function contentPathOf(configText) {
156
+ const match = /\bdocs\s*:\s*\{[^}]*?\bpath\s*:\s*['"]([^'"]+)['"]/s.exec(configText);
157
+ return match?.[1] ?? 'docs';
158
+ }
159
+ /**
160
+ * Where the site is, from this repository's own workspace declaration.
161
+ *
162
+ * Zero members holding a Docusaurus config and more than one are both
163
+ * refusals, for `lib/module-roots.ts`' reason: an ambiguous root narrows the
164
+ * walk to whichever sorted first and says nothing about having done so.
165
+ */
166
+ export function resolveDocsLayout(repoRoot) {
167
+ const found = [];
168
+ for (const member of workspaceMembers(repoRoot, nodeWorkspaceFs())) {
169
+ for (const name of CONFIG_FILENAMES) {
170
+ const configPath = join(member.dir, name);
171
+ if (existsSync(configPath)) {
172
+ found.push({ member, configPath });
173
+ break;
174
+ }
175
+ }
176
+ }
177
+ if (found.length === 0) {
178
+ throw new DocsLayoutUnresolvableError('no workspace member holds a Docusaurus configuration — the documentation site is ' +
179
+ 'derived from that file, and there is nothing to derive it from');
180
+ }
181
+ if (found.length > 1) {
182
+ throw new DocsLayoutUnresolvableError(`${found.length} workspace members hold a Docusaurus configuration ` +
183
+ `(${found.map((entry) => entry.member.name).join(', ')}) — the documentation site is ` +
184
+ 'ambiguous, and picking one narrows every walk to it without saying so');
185
+ }
186
+ const { member, configPath } = found[0];
187
+ const contentRoot = join(member.dir, contentPathOf(readFileSync(configPath, 'utf8')));
188
+ return {
189
+ member,
190
+ contentRoot,
191
+ modulesRoot: join(contentRoot, MODULES_CATEGORY),
192
+ sidebarPath: join(member.dir, 'sidebars.js'),
193
+ };
194
+ }
195
+ const EMPTY_FRONT_MATTER = {
196
+ title: null,
197
+ sidebarLabel: null,
198
+ sidebarPosition: null,
199
+ description: null,
200
+ };
201
+ /** A `key: value` line's value, unquoted. */
202
+ function scalarOf(raw) {
203
+ const trimmed = raw.trim();
204
+ if ((trimmed.startsWith("'") && trimmed.endsWith("'") && trimmed.length > 1) ||
205
+ (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length > 1)) {
206
+ return trimmed.slice(1, -1).split("\\'").join("'").split('\\"').join('"');
207
+ }
208
+ return trimmed;
209
+ }
210
+ /**
211
+ * The leading `---`-delimited block, read as top-level `key: value` lines.
212
+ *
213
+ * Deliberately not a YAML parser: a direct dependency on one would be a new
214
+ * runtime edge in the package that runs the check estate (`plan.md` §
215
+ * Complexity Tracking), and the four fields this feature reads are scalars. The
216
+ * bound is that a nested or multi-line value is read as its first line, which is
217
+ * stated rather than discovered later.
218
+ */
219
+ export function parseFrontMatter(source) {
220
+ const text = source.startsWith('') ? source.slice(1) : source;
221
+ if (!text.startsWith('---'))
222
+ return EMPTY_FRONT_MATTER;
223
+ const end = text.indexOf('\n---', 3);
224
+ if (end === -1)
225
+ return EMPTY_FRONT_MATTER;
226
+ const block = text.slice(text.indexOf('\n') + 1, end);
227
+ const fields = new Map();
228
+ for (const line of block.split('\n')) {
229
+ if (/^\s/.test(line))
230
+ continue;
231
+ const match = /^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.*)$/.exec(line);
232
+ if (match === null)
233
+ continue;
234
+ fields.set(match[1], scalarOf(match[2]));
235
+ }
236
+ const position = fields.get('sidebar_position');
237
+ const parsedPosition = position === undefined ? Number.NaN : Number(position);
238
+ return {
239
+ title: fields.get('title') ?? null,
240
+ sidebarLabel: fields.get('sidebar_label') ?? null,
241
+ sidebarPosition: Number.isFinite(parsedPosition) ? parsedPosition : null,
242
+ description: fields.get('description') ?? null,
243
+ };
244
+ }
245
+ /**
246
+ * A module's own position in the Modules category, or `null` for alphabetical.
247
+ *
248
+ * Read from the entry page's `sidebar_position` **only when that page is not a
249
+ * directory index**, and the distinction is Docusaurus's own rather than this
250
+ * feature's: `sidebar_position` orders a page among its *siblings*, so an
251
+ * `index.md`'s is its place inside its own directory and says nothing about
252
+ * where its module belongs among 65 others. Every such value in the tree today
253
+ * is `1` — an intra-directory ordering that has been inert under a manual
254
+ * sidebar — and reading it would have hoisted five modules to the top of the
255
+ * category for a reason nobody wrote.
256
+ */
257
+ export function categoryPositionOf(entry) {
258
+ return entry.inDirectory ? null : entry.frontMatter.sidebarPosition;
259
+ }
260
+ /**
261
+ * Every page one fragment of the modules category holds, sorted by doc id.
262
+ *
263
+ * The **fragment** is the unit, and that is the mechanism rather than an
264
+ * implementation detail: a module's `docs/` directory is its own fragment of
265
+ * this category, laid out exactly as the category lays it out, so one walk
266
+ * reads the site's tree and a module's alike and the two produce identical doc
267
+ * ids for the same page. It is what makes the Phase 2 move invisible — no URL
268
+ * changes, no doc id changes, and no ledger key keyed on `(module id, page
269
+ * slug)` goes stale (`contracts/docs-registry.md` R4.1).
270
+ *
271
+ * It also lets a module own **more than one slug** (`organizations` ships both
272
+ * `organizations.md` and `organization-hierarchy.md`) and publish under a slug
273
+ * that is not its id (`_i18n` ships `admin-i18n.md`), neither of which a
274
+ * one-directory-per-module rule can express without renaming a public URL —
275
+ * which is `spec.md` Q2 and nobody's to decide here.
276
+ */
277
+ export function collectPagesUnder(root, origin) {
278
+ if (!existsSync(root))
279
+ return [];
280
+ const pages = [];
281
+ const isPage = (name) => PAGE_EXTENSIONS.some((ext) => name.endsWith(ext));
282
+ const stem = (name) => name.replace(/\.mdx?$/, '');
283
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
284
+ const full = join(root, entry.name);
285
+ if (entry.isDirectory()) {
286
+ for (const child of readdirSync(full, { withFileTypes: true })) {
287
+ if (!child.isFile() || !isPage(child.name))
288
+ continue;
289
+ const file = join(full, child.name);
290
+ pages.push({
291
+ docId: `${MODULES_CATEGORY}/${entry.name}/${stem(child.name)}`,
292
+ path: file,
293
+ relativePath: `${entry.name}/${child.name}`,
294
+ slug: entry.name,
295
+ isEntry: stem(child.name) === CATEGORY_INDEX,
296
+ inDirectory: true,
297
+ frontMatter: parseFrontMatter(readFileSync(file, 'utf8')),
298
+ origin,
299
+ });
300
+ }
301
+ continue;
302
+ }
303
+ if (!entry.isFile() || !isPage(entry.name))
304
+ continue;
305
+ // Two files under the **site's** root are the category's, not a module's,
306
+ // and both are skipped rather than judged: the landing page a reader
307
+ // arrives at, and the generated map itself. The map is an artefact —
308
+ // `overlay:check` owns whether it is current, and reading it back as a page
309
+ // would make it `unlocated-page` for naming no module, which is a finding
310
+ // about this walk's population dressed as one about the tree.
311
+ //
312
+ // A module's own fragment is not exempted from either name: a module that
313
+ // ships `docs/README.md` is shipping a page for the category's landing
314
+ // slug, which is a finding and not a file to skip.
315
+ if (origin.kind === 'site' && stem(entry.name) === 'README')
316
+ continue;
317
+ if (origin.kind === 'site' && entry.name === MODULE_MAP_ARTEFACT)
318
+ continue;
319
+ pages.push({
320
+ docId: `${MODULES_CATEGORY}/${stem(entry.name)}`,
321
+ path: full,
322
+ relativePath: entry.name,
323
+ slug: stem(entry.name),
324
+ isEntry: true,
325
+ inDirectory: false,
326
+ frontMatter: parseFrontMatter(readFileSync(full, 'utf8')),
327
+ origin,
328
+ });
329
+ }
330
+ return pages.sort((a, b) => a.docId.localeCompare(b.docId));
331
+ }
332
+ /**
333
+ * Every page the site's own tree holds, sorted by doc id.
334
+ *
335
+ * `copies` is the set of absolute paths the collection step writes into this
336
+ * tree, and it is excluded rather than judged. The copies are **not committed**
337
+ * — one editable page and one that looks editable and is not is the state this
338
+ * feature exists to end — but a developer who has run the docs build has them
339
+ * on disk, and a walk that counted them would report every module's page as
340
+ * claimed by two sources: a finding about the copy step dressed as one about
341
+ * the tree.
342
+ *
343
+ * Passing an empty set is the fresh-checkout state and reads the tree as it is.
344
+ */
345
+ export function collectDocPages(modulesRoot, copies = new Set()) {
346
+ return collectPagesUnder(modulesRoot, {
347
+ kind: 'site',
348
+ moduleId: null,
349
+ root: modulesRoot,
350
+ }).filter((page) => !copies.has(page.path));
351
+ }
352
+ /** Every page the modules ship, sorted by doc id. */
353
+ export function collectModuleDocPages(sources) {
354
+ return sources
355
+ .flatMap((source) => collectPagesUnder(source.root, {
356
+ kind: 'module',
357
+ moduleId: source.moduleId,
358
+ root: source.root,
359
+ }))
360
+ .sort((a, b) => a.docId.localeCompare(b.docId));
361
+ }
362
+ /**
363
+ * Two pages claiming one doc id — the site's tree and a module's, or two
364
+ * modules'.
365
+ *
366
+ * A **mixed** tree is the supported state (`plan.md` § Phasing: the check
367
+ * accepts a page in the site tree and a page in a package on the same terms),
368
+ * so a batch that has moved half the pages is not a defect. Two sources for one
369
+ * id is, and it is not a silent one: the copy would write one over the other
370
+ * and whichever ran second would win, which is a build whose output depends on
371
+ * a directory read order.
372
+ */
373
+ export function duplicateDocIds(pages) {
374
+ const byId = new Map();
375
+ for (const page of pages) {
376
+ const group = byId.get(page.docId);
377
+ if (group === undefined)
378
+ byId.set(page.docId, [page.path]);
379
+ else
380
+ group.push(page.path);
381
+ }
382
+ return [...byId]
383
+ .filter(([, paths]) => paths.length > 1)
384
+ .map(([docId, paths]) => ({ docId, paths: [...paths].sort() }))
385
+ .sort((a, b) => a.docId.localeCompare(b.docId));
386
+ }
387
+ /**
388
+ * Does this slug name this module? — **D-200**, and the one place the rule
389
+ * lives.
390
+ *
391
+ * Two steps, and both are derivations rather than a table:
392
+ *
393
+ * * a URL segment is conventionally hyphenated and a module id is
394
+ * snake_case, so one is folded into the other;
395
+ * * a **leading underscore is stripped**, because an infrastructure module's
396
+ * id carries one and a documentation page's file name may not:
397
+ * **Docusaurus excludes an underscore-prefixed file from routing by
398
+ * design** — it becomes a *partial* for import into another page, generates
399
+ * no route, and cannot be found from `sidebars.js` at all. So `_i18n` is
400
+ * documented at `i18n` and `_lifecycle` at `lifecycle`.
401
+ *
402
+ * The underscore stays where it means something — in the module id, where
403
+ * `check:naming` enforces the convention — instead of leaking into a public URL.
404
+ * The rule covers every future infrastructure module with nothing to add, which
405
+ * is what retired the alias table Phase 1 shipped: a four-entry table of
406
+ * hand-declared attributions, in the feature whose whole subject is that three
407
+ * hand-maintained lists disagreed with one population and with each other.
408
+ *
409
+ * It is not a heuristic that guesses. Only these two transformations are
410
+ * applied, and the result must equal a registered id exactly.
411
+ */
412
+ export function slugNamesModule(slug, moduleId) {
413
+ const folded = slug.split('-').join('_');
414
+ return folded === moduleId || `_${folded}` === moduleId;
415
+ }
416
+ /**
417
+ * The module a slug names, or `null`, over the registered set.
418
+ *
419
+ * {@link slugNamesModule} in the direction a walk needs it: the candidates are
420
+ * derived from the slug rather than by scanning every registered id, so the
421
+ * cost does not grow with the platform.
422
+ */
423
+ export function moduleOfSlug(slug, registered) {
424
+ const folded = slug.split('-').join('_');
425
+ if (registered.has(folded))
426
+ return folded;
427
+ if (registered.has(`_${folded}`))
428
+ return `_${folded}`;
429
+ return null;
430
+ }
431
+ /** The label a navigation entry carries, from the page's own front matter. */
432
+ export function labelOf(page, fallback) {
433
+ return page.frontMatter.sidebarLabel ?? page.frontMatter.title ?? fallback;
434
+ }
435
+ /** Ordering: a declared `sidebar_position` first, then alphabetical by label. */
436
+ export function comparePages(a, b) {
437
+ if (a.position !== null || b.position !== null) {
438
+ const left = a.position ?? Number.POSITIVE_INFINITY;
439
+ const right = b.position ?? Number.POSITIVE_INFINITY;
440
+ if (left !== right)
441
+ return left - right;
442
+ }
443
+ return a.label.localeCompare(b.label, 'en');
444
+ }
445
+ /**
446
+ * Place every page against the registered module set.
447
+ *
448
+ * Pure over the two inputs, so a red proof enters where a real run enters
449
+ * (issue #130): the pages a walk produced and the ids the index registers.
450
+ */
451
+ export function attributeDocs(pages, registered) {
452
+ const registeredSet = new Set(registered);
453
+ // Grouped by **module**, not by slug. One module can own two slugs —
454
+ // `organizations` ships `organizations.md` and `organization-hierarchy.md` —
455
+ // and grouping by slug would emit that module twice, which the module map's
456
+ // "one row per registered module" cannot represent.
457
+ const byModule = new Map();
458
+ const unlocated = [];
459
+ const misowned = [];
460
+ for (const page of pages) {
461
+ const named = moduleOfSlug(page.slug, registeredSet);
462
+ // The **shipper** first. A module's own declaration of what it ships
463
+ // outranks any derivation from a file name, and it is what lets a module
464
+ // own a second slug without a hand-declared attribution anywhere.
465
+ const shipper = page.origin.kind === 'module' && page.origin.moduleId !== null
466
+ ? page.origin.moduleId
467
+ : null;
468
+ const moduleId = shipper ?? named;
469
+ if (moduleId === null) {
470
+ unlocated.push(page);
471
+ continue;
472
+ }
473
+ if (shipper !== null && named !== null && named !== shipper) {
474
+ misowned.push({ page, namesModule: named });
475
+ }
476
+ const group = byModule.get(moduleId);
477
+ if (group === undefined)
478
+ byModule.set(moduleId, [page]);
479
+ else
480
+ group.push(page);
481
+ }
482
+ const documented = [];
483
+ for (const [moduleId, group] of byModule) {
484
+ // The page a reader lands on, in three falling steps and never a guess: the
485
+ // module's *own* entry page (a slug that names the id by D-200's rule,
486
+ // marked entry by the walk), then any entry page — which is what answers
487
+ // for a module whose only page sits at a slug of its own — then whatever
488
+ // there is.
489
+ const entry = group.find((page) => page.isEntry && slugNamesModule(page.slug, moduleId)) ??
490
+ group.find((page) => page.isEntry) ??
491
+ group[0];
492
+ const slug = entry.slug;
493
+ const children = group
494
+ .filter((page) => page !== entry)
495
+ .sort((a, b) => comparePages({ position: a.frontMatter.sidebarPosition, label: labelOf(a, basename(a.path)) }, { position: b.frontMatter.sidebarPosition, label: labelOf(b, basename(b.path)) }));
496
+ documented.push({ moduleId, slug, entry, children });
497
+ }
498
+ documented.sort((a, b) => a.moduleId.localeCompare(b.moduleId));
499
+ const covered = new Set(documented.map((entry) => entry.moduleId));
500
+ return {
501
+ documented,
502
+ undocumented: registered.filter((id) => !covered.has(id)).sort(),
503
+ unlocated,
504
+ misowned: misowned.sort((a, b) => a.page.docId.localeCompare(b.page.docId)),
505
+ // Every segment of the page's own path inside the category, because
506
+ // Docusaurus excludes an underscore-prefixed **directory** from routing on
507
+ // the same terms as a file.
508
+ unroutable: pages.filter((page) => page.relativePath.split('/').some((segment) => segment.startsWith('_'))),
509
+ pages,
510
+ };
511
+ }
512
+ /**
513
+ * Where a module ships its own documentation, or `null` when it declares none.
514
+ *
515
+ * The anchor is `dirname(manifestPath)` — the same one the `_i18n` boot
516
+ * reconciler joins `bundlesDir` to, which `resolveManifestPath` already makes
517
+ * correct for a core-relative module, a workspace package and an installed
518
+ * package alike (`contracts/module-documentation-layer.md` R2.2). Nothing in a
519
+ * module names a package, a repository root or a build directory to find its own
520
+ * pages; the platform supplies the anchor.
521
+ *
522
+ * `false` and absent are **not** the same state and this returns the same
523
+ * `null` for both deliberately: the distinction is the *manifest's*, and the
524
+ * consumer that needs it (`undocumented-module`) reads the declaration itself.
525
+ */
526
+ export function declaredDocsDirectory(manifestPath, declaration) {
527
+ if (declaration === undefined || declaration === false)
528
+ return null;
529
+ return join(manifestPath.replace(/[/\\][^/\\]*$/, ''), declaration.dir);
530
+ }
531
+ /** True when `path` is a directory that exists. */
532
+ export function isDirectory(path) {
533
+ try {
534
+ return statSync(path).isDirectory();
535
+ }
536
+ catch {
537
+ return false;
538
+ }
539
+ }
540
+ /** Raised when a module declares a documentation directory that is not there. */
541
+ export class DeclaredDocsDirectoryMissingError extends Error {
542
+ name = 'DeclaredDocsDirectoryMissingError';
543
+ }
544
+ /**
545
+ * Where every module that declares documentation keeps it.
546
+ *
547
+ * **A declared directory that is not on disk is a refusal, naming the module**
548
+ * (FR-017, `module-documentation-layer.md` R2.4). It must not read as "this
549
+ * module ships no documentation", and the reason is a measured one rather than
550
+ * a stylistic preference: `backend/src/manifest-locations.ts`' header records
551
+ * that the `_i18n` boot reconciler *logs and skips* an absent bundles
552
+ * directory, so a packaged module would have rendered every command-palette
553
+ * entry as its raw i18n key with no error anywhere. The distinction between
554
+ * *declared and absent* and *not declared* is the whole of that repair.
555
+ */
556
+ export function moduleDocsSources(modules) {
557
+ const sources = [];
558
+ const missing = [];
559
+ for (const module of modules) {
560
+ const root = declaredDocsDirectory(module.manifestPath, module.declaration);
561
+ if (root === null)
562
+ continue;
563
+ if (!isDirectory(root)) {
564
+ missing.push(`${module.moduleId} declares ${JSON.stringify(module.declaration)} at ${root}`);
565
+ continue;
566
+ }
567
+ sources.push({ moduleId: module.moduleId, root });
568
+ }
569
+ if (missing.length > 0) {
570
+ throw new DeclaredDocsDirectoryMissingError(`${missing.length} module(s) declare a documentation directory that is not on disk:\n` +
571
+ missing.map((entry) => ` - ${entry}`).join('\n') +
572
+ '\nA declared directory that is absent is a refusal and never "this module ships no ' +
573
+ 'documentation" — declare `docs: false` if that is the decision, or ship the directory.');
574
+ }
575
+ return sources.sort((a, b) => a.moduleId.localeCompare(b.moduleId));
576
+ }
577
+ /**
578
+ * Where the site keeps the copy of a page a module ships.
579
+ *
580
+ * The module's fragment lands at the same relative path inside the category, so
581
+ * the copy preserves the page's doc id, its permalink and every relative link
582
+ * written against it (`module-documentation-layer.md` R5.2).
583
+ */
584
+ export function copyTargetOf(page, modulesRoot) {
585
+ return join(modulesRoot, page.relativePath);
586
+ }
587
+ /**
588
+ * Every relative markdown link a page writes, resolved to a doc id.
589
+ *
590
+ * Read as `](…)` with a `./` or `../` prefix, in the literal-text discipline
591
+ * the rest of the estate uses. The bounds are stated here rather than
592
+ * discovered later (`docs-registry.md` R3.10): a link built by an MDX
593
+ * expression or a component is invisible, a raw `<a href>` is invisible, and a
594
+ * `@site/`-prefixed absolute link is not a relative sibling link and is outside
595
+ * the population by construction.
596
+ *
597
+ * An anchor or a query is stripped, and a target that resolves outside the
598
+ * modules category resolves to `null` — a link into `../operations/runbooks/…`
599
+ * is a link to the site, not to a sibling module. Such a link carries
600
+ * `leavesCategory: true`, which is what lets a consumer tell it apart from a
601
+ * link to the category root; see {@link RelativeLink}.
602
+ */
603
+ export function relativeLinksIn(page, source) {
604
+ const fromDir = page.relativePath.includes('/')
605
+ ? page.relativePath.slice(0, page.relativePath.lastIndexOf('/'))
606
+ : '';
607
+ const links = [];
608
+ for (const match of source.matchAll(/\]\((\.{1,2}\/[^)\s]+)\)/g)) {
609
+ const raw = match[1];
610
+ const target = raw.split('#')[0].split('?')[0];
611
+ if (target === '')
612
+ continue;
613
+ const segments = [...fromDir.split('/').filter((part) => part !== ''), ...target.split('/')];
614
+ const resolved = [];
615
+ let escaped = false;
616
+ for (const segment of segments) {
617
+ if (segment === '.' || segment === '')
618
+ continue;
619
+ if (segment === '..') {
620
+ if (resolved.length === 0) {
621
+ escaped = true;
622
+ break;
623
+ }
624
+ resolved.pop();
625
+ continue;
626
+ }
627
+ resolved.push(segment);
628
+ }
629
+ if (escaped || resolved.length === 0) {
630
+ links.push({ target: raw, docId: null, leavesCategory: escaped });
631
+ continue;
632
+ }
633
+ const last = resolved[resolved.length - 1].replace(/\.mdx?$/, '');
634
+ resolved[resolved.length - 1] = last;
635
+ links.push({
636
+ target: raw,
637
+ docId: `${MODULES_CATEGORY}/${resolved.join('/')}`,
638
+ leavesCategory: false,
639
+ });
640
+ }
641
+ return links;
642
+ }
643
+ /**
644
+ * What a manifest's **source text** declares for `docs`.
645
+ *
646
+ * The generator reads the tree and the check reads the emitted manifest, so the
647
+ * two answer this question from two different artefacts — which is the estate's
648
+ * independent-author pattern rather than a duplication: a generator that
649
+ * imported the index it is about to render would be reading its own previous
650
+ * answer.
651
+ *
652
+ * `'unreadable'` is a **finding and never a skip** (issue #113). A computed
653
+ * `dir` is a directory this walk cannot place, and treating it as "declares
654
+ * nothing" is the direction that agrees with the defect — the module's pages
655
+ * would simply not be collected, with no error anywhere, which is the failure
656
+ * `manifest-locations.ts` was written to end.
657
+ */
658
+ export function docsDeclarationIn(source) {
659
+ const match = /^\s{0,4}docs\s*:\s*(false|\{[^}]*\})\s*,/m.exec(source);
660
+ if (match === null)
661
+ return undefined;
662
+ const value = match[1];
663
+ if (value === 'false')
664
+ return false;
665
+ const dir = /\bdir\s*:\s*['"]([^'"]+)['"]/.exec(value);
666
+ return dir === null ? 'unreadable' : { dir: dir[1] };
667
+ }
668
+ /**
669
+ * The `docs` declaration of every module the generated index registers.
670
+ *
671
+ * The index is **imported**, because a module package resolves through its own
672
+ * `exports` map at its build output and the declaration a running platform sees
673
+ * is the emitted one. Every reader that needs "where does each module keep its
674
+ * pages" starts here, so the population is derived once: two walks over one
675
+ * population are two answers waiting to disagree, which is the state this whole
676
+ * feature ends.
677
+ */
678
+ export async function moduleDocsDeclarantsFrom(manifestIndexPath) {
679
+ const loaded = (await import(pathToFileURL(manifestIndexPath).href));
680
+ const declarants = [];
681
+ for (const entry of loaded.DISCOVERED_MANIFESTS ?? []) {
682
+ if (entry.manifestPath === undefined)
683
+ continue;
684
+ declarants.push({
685
+ moduleId: entry.id,
686
+ manifestPath: entry.manifestPath,
687
+ declaration: entry.manifest?.docs,
688
+ });
689
+ }
690
+ return declarants;
691
+ }
692
+ /**
693
+ * Every page a module owns, and where the site keeps its copy.
694
+ *
695
+ * `copies` is the set every walk over the site's tree has to leave alone: the
696
+ * copies are not committed, so a fresh checkout has none and a developer who
697
+ * has run the site build has all of them — and a population that counted them
698
+ * would be a different size on the two machines.
699
+ */
700
+ export async function resolveModuleDocs(repoRoot, manifestIndexPath) {
701
+ const docs = resolveDocsLayout(repoRoot);
702
+ const sources = moduleDocsSources(await moduleDocsDeclarantsFrom(manifestIndexPath));
703
+ const modulePages = collectModuleDocPages(sources);
704
+ return {
705
+ docs,
706
+ sources,
707
+ modulePages,
708
+ copies: new Set(modulePages.map((page) => copyTargetOf(page, docs.modulesRoot))),
709
+ };
710
+ }
711
+ //# sourceMappingURL=module-docs.js.map