@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,669 @@
1
+ /**
2
+ * "Where does this platform keep its **admin** surfaces, and whose are they?" —
3
+ * the frontend twin of `lib/module-roots.ts` (feature 091, Phase 0).
4
+ *
5
+ * ## Why it exists
6
+ *
7
+ * `check:module-boundary` walks `layout.moduleWalkRoots`, and `admin/` is none
8
+ * of them. So the cross-module reaches inside `admin/src/modules` are outside
9
+ * every boundary instrument in the repository — and the failure mode is not
10
+ * that they go unjudged now, it is what happens when an admin directory moves
11
+ * into its module's package. `specs/084-small-f4-package-layout/contracts/module-package-layout.md`
12
+ * §0 records the backend precedent: rewriting a ledgered relative import as a
13
+ * package specifier **deleted** the reach from the walk, whereupon the two-way
14
+ * ledger reported the entry describing it as stale. Doing that after a move
15
+ * means doing it once per reach, silently, in the direction that looks like
16
+ * progress. Hence FR-017: the instrument learns admin code **before** the first
17
+ * directory moves.
18
+ *
19
+ * ## The derivation — three facts, each read from the artefact that owns it
20
+ *
21
+ * 1. **Which workspace member is the frontend.** The member whose
22
+ * `tsconfig.json` declares a `"@/*"` path. That alias is not decoration:
23
+ * it is what `tsc` and Vite both resolve the admin project's own imports
24
+ * through, so it is a live declaration rather than a convention, and this
25
+ * check has to resolve it anyway. Exactly one member declares it, and
26
+ * **zero or two is exit 2**, the same refusal {@link findManifestIndex}
27
+ * makes for an ambiguous manifest index: picking one narrows every scan to
28
+ * it without saying so.
29
+ * 2. **The source root** — the directory the alias points at (`admin/src`).
30
+ * Never a path spelled here. It is where the **generated** contribution
31
+ * registry lands, which is the one artefact that stays the admin
32
+ * project's whatever else moves (feature 110, R3.2).
33
+ * 2a. **The shell root** — the source root holding the route table and the
34
+ * nav, `App.tsx` and `components/AppShell.tsx`. Feature 110's T120 moved
35
+ * them out of the alias member into `@endora-commerce/admin-shell`, so the
36
+ * two roots are no longer one directory, and the derivation follows the
37
+ * **artefacts** rather than the directory: every workspace member's source
38
+ * root is a candidate, exactly one may hold the pair, and zero or two is
39
+ * the same refusal as above. On a tree where the alias member still holds
40
+ * them the answer is byte-identical to what it was, which is what makes
41
+ * that move measurable rather than a rewrite of the instrument. A checkout
42
+ * whose shell has gone is exit 2 rather than a scan that reports on the
43
+ * thin project that is left — issue #215's shape, one frontend over.
44
+ * 3. **The module root** — the one directory under a **host root** holding a
45
+ * child named after a registered module id. That is
46
+ * {@link applicationModuleRoots}' predicate minus its `manifest.ts` half,
47
+ * which an admin directory does not have. Exactly one directory may
48
+ * answer; two would be ambiguous and is refused.
49
+ *
50
+ * ## The directory name is not the module id
51
+ *
52
+ * 49 of the 55 directories under the module root are named after a registered
53
+ * module. Six are not, and they are **not** six host-owned directories:
54
+ * `admin/src/components/AppShell.tsx` attributes `/warehouses` to
55
+ * `module: 'inventory'`, so `warehouses` is `inventory`'s admin surface under
56
+ * another name. Keying on `basename` would leave that directory attributed to a
57
+ * module that does not exist — an orphan shard — and would make a reach into it
58
+ * from `inventory`'s own screens read as *cross-module* when it is a module
59
+ * reaching its own code.
60
+ *
61
+ * So attribution is read from the **route**, which is where the admin already
62
+ * writes it down: `App.tsx` maps a path to a component it imports from a
63
+ * module directory, and `AppShell.tsx`'s `NAV` carries `{ to, module }` for
64
+ * that path. {@link adminModuleDirectories} joins the two. A directory no NAV
65
+ * entry claims is the admin application's own (`_shared`, `home`, `platform`,
66
+ * `profile`) and is not a module's — which is the fail-closed direction: its
67
+ * files are not judged as a module's, and a reach into it is not a cross-module
68
+ * finding somebody would have to ledger under a module that does not own it.
69
+ *
70
+ * **One directory is attributed by neither rule and it is worth naming:**
71
+ * `cms_pages`. Nothing under `admin/` imports its one component, so no route
72
+ * renders it and no NAV entry claims it. `specs/091-module-owned-admin-surfaces/spec.md`
73
+ * §2 records it as `cms`'s, which is a judgement about the name rather than a
74
+ * fact the tree states; this derivation therefore leaves it host-owned. It
75
+ * costs nothing today — the file makes no cross-module reach and nothing
76
+ * reaches into it — and the alternative would be a mapping written down, which
77
+ * is D-100 exactly.
78
+ */
79
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
80
+ import { join, resolve } from 'node:path';
81
+ import ts from 'typescript';
82
+ /** The alias every admin file writes its own host imports through. */
83
+ export const ADMIN_SOURCE_ALIAS = '@/*';
84
+ /**
85
+ * The owner id of the admin application itself.
86
+ *
87
+ * Not a module, and it has to be spelled somewhere: since P1 of
88
+ * `specs/091-module-owned-admin-surfaces/` § *Phase 4* `check:module-boundary`
89
+ * attributes an admin **host** file to it, so a reach out of one has a consumer
90
+ * to be ledgered under.
91
+ *
92
+ * One spelling, **here**, and this is the moment that placement was for:
93
+ * `check:admin-registrations` — which attributed a route or a nav entry no
94
+ * module claimed to the same owner — and `ledgers/admin-registrations.ts`,
95
+ * where this constant used to live, were deleted by batch 16 when the drain
96
+ * emptied their population (SC-007, Phase 5 T5). The boundary ledger outlives
97
+ * them and goes on using this name unchanged.
98
+ */
99
+ export const ADMIN_HOST_OWNER = 'host';
100
+ /**
101
+ * The generated admin contribution registry, by name.
102
+ *
103
+ * `generate-composer.ts` renders it into {@link AdminSurfaceLayout.sourceRoot},
104
+ * and `check:module-boundary` exempts it: a generated registry naming every
105
+ * contributing module package by a bare specifier is a composition root doing
106
+ * its job, exactly as `composition.generated.ts` is in the backend. Both derive
107
+ * the path from this constant and from the alias member, so neither can end up
108
+ * looking at a file the other has stopped writing.
109
+ */
110
+ export const ADMIN_REGISTRY_ARTEFACT = 'modules.generated.ts';
111
+ /** Raised when the admin layout cannot be resolved; a caller turns it into exit 2. */
112
+ export class AdminLayoutUnresolvableError extends Error {
113
+ name = 'AdminLayoutUnresolvableError';
114
+ }
115
+ /** Directories that are not this repository's own source. */
116
+ const PRUNED = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.docusaurus']);
117
+ /**
118
+ * The one workspace member whose tsconfig declares the source alias.
119
+ *
120
+ * Read out of `tsconfig.json` rather than out of a Vite config, because the
121
+ * tsconfig is the one both `tsc` and the bundler agree on and the one this
122
+ * repository's own `paths` discipline is already written against.
123
+ */
124
+ export function findAliasMember(members, readText = defaultReadText) {
125
+ const found = [];
126
+ for (const member of members) {
127
+ const text = readText(join(member.dir, 'tsconfig.json'));
128
+ if (text === null)
129
+ continue;
130
+ const target = aliasTargetOf(text);
131
+ if (target !== null)
132
+ found.push({ member, target });
133
+ }
134
+ if (found.length === 0) {
135
+ throw new AdminLayoutUnresolvableError(`no workspace member declares a \`"${ADMIN_SOURCE_ALIAS}"\` path in its tsconfig.json — ` +
136
+ 'the admin source root is derived from that alias, and there is nothing to derive it from');
137
+ }
138
+ if (found.length > 1) {
139
+ throw new AdminLayoutUnresolvableError(`${found.length} workspace members declare a \`"${ADMIN_SOURCE_ALIAS}"\` path ` +
140
+ `(${found.map((entry) => entry.member.name).join(', ')}) — the admin source root is ` +
141
+ 'ambiguous, and picking one narrows the walk to it without saying so');
142
+ }
143
+ return found[0];
144
+ }
145
+ function defaultReadText(path) {
146
+ try {
147
+ return readFileSync(path, 'utf8');
148
+ }
149
+ catch {
150
+ return null;
151
+ }
152
+ }
153
+ /**
154
+ * The directory `"@/*"` maps to, as written in the tsconfig, or `null`.
155
+ *
156
+ * Parsed with the compiler's own JSONC reader, because a tsconfig in this
157
+ * repository carries comments and `JSON.parse` refuses one.
158
+ */
159
+ export function aliasTargetOf(tsconfigText) {
160
+ const parsed = ts.parseConfigFileTextToJson('tsconfig.json', tsconfigText);
161
+ const config = parsed.config;
162
+ const targets = config?.compilerOptions?.paths?.[ADMIN_SOURCE_ALIAS];
163
+ if (targets === undefined || targets.length === 0)
164
+ return null;
165
+ const first = targets[0];
166
+ // `./src/*` → `src`. The trailing wildcard is what makes it a prefix rule and
167
+ // is never part of the directory.
168
+ const trimmed = first.replace(/\/\*$/, '').replace(/^\.\//, '');
169
+ return trimmed === '' ? null : trimmed;
170
+ }
171
+ /**
172
+ * Does this workspace hold an admin application at all?
173
+ *
174
+ * `true` when a member declares the source alias, which is the one declaration
175
+ * that says *"this repository has a frontend"* and needs neither the shell nor
176
+ * a registered-id set to answer. It is the discriminator between the two states
177
+ * a `null` {@link AdminSurfaceLayout} has run together since feature 091: a
178
+ * workspace with no frontend — every fixture in the tree, and the behaviour
179
+ * that shipped — and a frontend whose route table the walk has stopped finding,
180
+ * which is exit 2 (feature 110, T122).
181
+ *
182
+ * The second state is not hypothetical and is what T120 makes reachable: with
183
+ * `App.tsx` and the nav inside `@endora-commerce/admin-shell`, a checkout whose
184
+ * shell is not installed, not built, or renamed has a perfectly good alias
185
+ * member holding four files — and every admin population would come back empty
186
+ * with `violations=0` beside it.
187
+ */
188
+ export function adminApplicationPresent(members, readText = defaultReadText) {
189
+ try {
190
+ findAliasMember(members, readText);
191
+ return true;
192
+ }
193
+ catch (error) {
194
+ if (error instanceof AdminLayoutUnresolvableError)
195
+ return false;
196
+ throw error;
197
+ }
198
+ }
199
+ /**
200
+ * The route table and the nav, relative to a source root — spelled **once**,
201
+ * here, because three things locate the pair: the shell-root derivation below,
202
+ * {@link AdminSurfaceLayout.registryFiles}, and the parse in
203
+ * {@link resolveAdminSurfaces}. A second spelling is a second answer to *"where
204
+ * does this application write its routes down"*.
205
+ */
206
+ const ROUTE_TABLE_FILE = 'App.tsx';
207
+ const NAV_FILE = join('components', 'AppShell.tsx');
208
+ /**
209
+ * Every source root a workspace member could keep admin sources in, in the
210
+ * order the search reads them.
211
+ *
212
+ * The alias member's target first — it is the one root a declaration names, so
213
+ * an unmoved tree resolves on the first candidate and answers exactly what it
214
+ * answered before feature 110 — then `<dir>/src` for every member, which is the
215
+ * convention every package in this workspace already builds from
216
+ * (`tsconfig.build.json`'s `rootDir`). Nothing here spells a package name or a
217
+ * directory under `packages/`.
218
+ */
219
+ function candidateSourceRoots(members, readText) {
220
+ const roots = [];
221
+ try {
222
+ const { member, target } = findAliasMember(members, readText);
223
+ roots.push(resolve(member.dir, target));
224
+ }
225
+ catch (error) {
226
+ if (!(error instanceof AdminLayoutUnresolvableError))
227
+ throw error;
228
+ }
229
+ for (const member of members) {
230
+ const candidate = resolve(member.dir, 'src');
231
+ if (!roots.includes(candidate))
232
+ roots.push(candidate);
233
+ }
234
+ return roots;
235
+ }
236
+ /**
237
+ * The source root holding **both** the route table and the nav.
238
+ *
239
+ * Feature 110's T120 moved them out of the admin project into
240
+ * `@endora-commerce/admin-shell`, and T122's requirement is that the two checks
241
+ * reading them report the same findings before and after. So the subject is the
242
+ * **pair of artefacts** and never the directory: whichever member holds them is
243
+ * the shell, and on a tree where that is still the alias member the answer does
244
+ * not move at all.
245
+ *
246
+ * Zero is a refusal and not a `null`, unlike {@link findAdminModuleRoot}'s
247
+ * empty answer: an admin application with no route table is not a drained
248
+ * population, it is a walk that has stopped finding the file every attribution
249
+ * in this module is read out of. Two is a refusal for {@link findAliasMember}'s
250
+ * reason — picking one narrows every scan to it without saying so.
251
+ */
252
+ export function findAdminShellRoot(members, readText = defaultReadText) {
253
+ const found = candidateSourceRoots(members, readText).filter((root) => readText(join(root, ROUTE_TABLE_FILE)) !== null && readText(join(root, NAV_FILE)) !== null);
254
+ if (found.length === 0) {
255
+ throw new AdminLayoutUnresolvableError(`no workspace member holds both ${ROUTE_TABLE_FILE} and ${NAV_FILE} under its source ` +
256
+ 'root — the module a surface belongs to is read from the route table and the nav, and ' +
257
+ 'a scan that carried on without them would report on whatever is left rather than ' +
258
+ 'saying it had stopped looking');
259
+ }
260
+ if (found.length > 1) {
261
+ throw new AdminLayoutUnresolvableError(`${found.length} source roots hold both ${ROUTE_TABLE_FILE} and ${NAV_FILE}:\n` +
262
+ `${found.map((path) => ` ${path}`).join('\n')}\n` +
263
+ 'the admin shell is ambiguous, and picking one narrows the walk to it without saying so');
264
+ }
265
+ return found[0];
266
+ }
267
+ /**
268
+ * Every source root the admin application owns — the alias member's and the
269
+ * shell's — sorted, deduplicated.
270
+ *
271
+ * **Narrower than the layout, deliberately**, on {@link adminRegistryPathOf}'s
272
+ * reasoning: `i18n:hardcoded` needs the walk and needs nothing about module
273
+ * attribution, so it must not be made to depend on a registered-id set it has
274
+ * no other use for. One derivation, two entry points —
275
+ * {@link resolveAdminSurfaces} builds its `hostRoots` from this function.
276
+ */
277
+ export function adminHostRootsOf(members, readText = defaultReadText) {
278
+ const roots = new Set([findAdminShellRoot(members, readText)]);
279
+ try {
280
+ const { member, target } = findAliasMember(members, readText);
281
+ roots.add(resolve(member.dir, target));
282
+ }
283
+ catch (error) {
284
+ if (!(error instanceof AdminLayoutUnresolvableError))
285
+ throw error;
286
+ }
287
+ return [...roots].sort();
288
+ }
289
+ /**
290
+ * The generated admin contribution registry's path, or `null` when this
291
+ * workspace has no admin application at all (feature 091, R13b).
292
+ *
293
+ * **Narrower than the layout, deliberately.** It needs the alias member and its
294
+ * target and nothing else — not `App.tsx`, not `components/AppShell.tsx`, not a
295
+ * module root — because two checks gate a population floor on this file's
296
+ * presence and *"the gate went true for a reason having nothing to do with the
297
+ * registry"* is the exact defect §7.5 measured. `resolveAdminSurfaces` builds
298
+ * {@link AdminSurfaceLayout.generatedRegistryFile} from this function, so there
299
+ * is one derivation with two entry points rather than two answers.
300
+ *
301
+ * `null` here is the honest absence: with no alias member there is no directory
302
+ * the artefact could be under, and a caller omits its token rather than
303
+ * printing `0/0`.
304
+ */
305
+ export function adminRegistryPathOf(members, readText = defaultReadText) {
306
+ let resolved;
307
+ try {
308
+ resolved = findAliasMember(members, readText);
309
+ }
310
+ catch (error) {
311
+ if (error instanceof AdminLayoutUnresolvableError)
312
+ return null;
313
+ throw error;
314
+ }
315
+ return join(resolve(resolved.member.dir, resolved.target), ADMIN_REGISTRY_ARTEFACT);
316
+ }
317
+ /**
318
+ * The one directory under `sourceRoot` whose children are named after registered
319
+ * modules, or `null` when none is.
320
+ *
321
+ * The same predicate {@link applicationModuleRoots} applies in `backend/src`,
322
+ * minus its `manifest.ts` half: an admin surface directory has no manifest, so
323
+ * requiring one would find nothing.
324
+ *
325
+ * **Absence is a measurement, ambiguity is blindness** (feature 091, R16). Zero
326
+ * is `null`, because this feature exists to empty that directory and a refusal
327
+ * would send a reader to repair a finished tree; two is still a throw, because
328
+ * picking one narrows every walk to it without saying so.
329
+ *
330
+ * It takes the admin's **host roots** since feature 110's T122 — the admin
331
+ * project's and the shell's — because a module surface directory left in the
332
+ * application would move with `App.tsx` rather than stay behind it. A single
333
+ * root is still accepted, and means the same thing it meant.
334
+ */
335
+ export function findAdminModuleRoot(sourceRoots, registered) {
336
+ const searched = typeof sourceRoots === 'string' ? [sourceRoots] : sourceRoots;
337
+ const roots = new Set();
338
+ const visit = (dir) => {
339
+ let entries;
340
+ try {
341
+ entries = readdirSync(dir);
342
+ }
343
+ catch {
344
+ return;
345
+ }
346
+ for (const entry of entries) {
347
+ if (PRUNED.has(entry))
348
+ continue;
349
+ const full = join(dir, entry);
350
+ let directory;
351
+ try {
352
+ directory = statSync(full).isDirectory();
353
+ }
354
+ catch {
355
+ continue;
356
+ }
357
+ if (!directory)
358
+ continue;
359
+ if (registered.has(entry)) {
360
+ roots.add(dir);
361
+ continue;
362
+ }
363
+ visit(full);
364
+ }
365
+ };
366
+ for (const root of searched)
367
+ visit(root);
368
+ const found = [...roots].sort();
369
+ // The terminal state SC-007 reaches: every module's admin surfaces have moved
370
+ // into that module's package, so `admin/src/modules/` holds only the admin
371
+ // application's own directories and none of them is a registered id.
372
+ if (found.length === 0)
373
+ return null;
374
+ if (found.length > 1) {
375
+ throw new AdminLayoutUnresolvableError(`more than one directory under ${searched.join(', ')} holds children named after ` +
376
+ `registered modules:\n${found.map((path) => ` ${path}`).join('\n')}\n` +
377
+ 'the admin module root is ambiguous, and picking one narrows the walk to it without ' +
378
+ 'saying so');
379
+ }
380
+ return found[0];
381
+ }
382
+ /** `<Route path="…" element={<X … />} />` — every one `source` declares. */
383
+ export function adminRoutes(source, file = 'App.tsx') {
384
+ const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
385
+ const found = [];
386
+ const visit = (node) => {
387
+ const opening = ts.isJsxSelfClosingElement(node)
388
+ ? node
389
+ : ts.isJsxElement(node)
390
+ ? node.openingElement
391
+ : null;
392
+ if (opening !== null && opening.tagName.getText(sf) === 'Route') {
393
+ const path = stringAttribute(opening, 'path', sf);
394
+ if (path !== null) {
395
+ found.push({
396
+ path,
397
+ component: elementComponent(opening, sf),
398
+ line: sf.getLineAndCharacterOfPosition(opening.getStart(sf)).line + 1,
399
+ });
400
+ }
401
+ }
402
+ ts.forEachChild(node, visit);
403
+ };
404
+ visit(sf);
405
+ return found;
406
+ }
407
+ function stringAttribute(opening, name, sf) {
408
+ for (const attribute of opening.attributes.properties) {
409
+ if (!ts.isJsxAttribute(attribute))
410
+ continue;
411
+ if (attribute.name.getText(sf) !== name)
412
+ continue;
413
+ const initializer = attribute.initializer;
414
+ if (initializer !== undefined && ts.isStringLiteral(initializer))
415
+ return initializer.text;
416
+ }
417
+ return null;
418
+ }
419
+ /** The component name inside `element={<X … />}`, or `null`. */
420
+ function elementComponent(opening, sf) {
421
+ for (const attribute of opening.attributes.properties) {
422
+ if (!ts.isJsxAttribute(attribute))
423
+ continue;
424
+ if (attribute.name.getText(sf) !== 'element')
425
+ continue;
426
+ const initializer = attribute.initializer;
427
+ if (initializer === undefined || !ts.isJsxExpression(initializer))
428
+ continue;
429
+ const expression = initializer.expression;
430
+ if (expression === undefined)
431
+ continue;
432
+ if (ts.isJsxSelfClosingElement(expression))
433
+ return expression.tagName.getText(sf);
434
+ if (ts.isJsxElement(expression))
435
+ return expression.openingElement.tagName.getText(sf);
436
+ }
437
+ return null;
438
+ }
439
+ /**
440
+ * `{ to: '…', …, module: '…' }` — every nav entry `source` declares.
441
+ *
442
+ * Read as object literals carrying both fields rather than as members of a
443
+ * constant named `NAV`, so a second nav array — a mobile strip, a settings
444
+ * sub-nav — is in the population by construction rather than by being added
445
+ * here.
446
+ */
447
+ export function adminNavEntries(source, file = 'AppShell.tsx') {
448
+ const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
449
+ const found = [];
450
+ const visit = (node) => {
451
+ if (ts.isObjectLiteralExpression(node)) {
452
+ const to = literalProperty(node, 'to');
453
+ const owner = literalProperty(node, 'module');
454
+ // `to: null` is not a nav entry — a destination is a string. `module`
455
+ // legitimately is `null`, which is how the admin's own entries declare
456
+ // themselves, so the two fields are narrowed differently on purpose.
457
+ if (typeof to === 'string' && owner !== undefined) {
458
+ found.push({
459
+ to,
460
+ module: owner,
461
+ line: sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1,
462
+ });
463
+ }
464
+ }
465
+ ts.forEachChild(node, visit);
466
+ };
467
+ visit(sf);
468
+ return found;
469
+ }
470
+ /**
471
+ * A property's string value, `null` for a literal `null`, `undefined` when the
472
+ * property is absent or is not one of the two.
473
+ *
474
+ * The three-way answer is what lets a nav entry declaring `module: null` — the
475
+ * admin's own destinations — be recognised as *declared and host-owned* rather
476
+ * than as *not a nav entry at all*.
477
+ */
478
+ function literalProperty(node, name) {
479
+ for (const property of node.properties) {
480
+ if (!ts.isPropertyAssignment(property))
481
+ continue;
482
+ const key = ts.isIdentifier(property.name)
483
+ ? property.name.text
484
+ : ts.isStringLiteral(property.name)
485
+ ? property.name.text
486
+ : null;
487
+ if (key !== name)
488
+ continue;
489
+ const value = property.initializer;
490
+ if (ts.isStringLiteral(value))
491
+ return value.text;
492
+ if (value.kind === ts.SyntaxKind.NullKeyword)
493
+ return null;
494
+ return undefined;
495
+ }
496
+ return undefined;
497
+ }
498
+ /**
499
+ * Component name → the module directory `App.tsx` imports it from.
500
+ *
501
+ * Only specifiers landing inside the module root are recorded, so a host
502
+ * component is absent rather than attributed to a directory that is not a
503
+ * module's.
504
+ */
505
+ export function routeComponentDirectories(source, moduleRootSegment, file = 'App.tsx') {
506
+ const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
507
+ const directories = new Map();
508
+ const prefixes = [`./${moduleRootSegment}/`, `@/${moduleRootSegment}/`];
509
+ for (const statement of sf.statements) {
510
+ if (!ts.isImportDeclaration(statement))
511
+ continue;
512
+ const specifier = statement.moduleSpecifier;
513
+ if (!ts.isStringLiteral(specifier))
514
+ continue;
515
+ const prefix = prefixes.find((candidate) => specifier.text.startsWith(candidate));
516
+ if (prefix === undefined)
517
+ continue;
518
+ const directory = specifier.text.slice(prefix.length).split('/')[0];
519
+ if (directory === undefined || directory === '')
520
+ continue;
521
+ const clause = statement.importClause;
522
+ if (clause === undefined)
523
+ continue;
524
+ if (clause.name !== undefined)
525
+ directories.set(clause.name.text, directory);
526
+ const bound = clause.namedBindings;
527
+ if (bound !== undefined && ts.isNamedImports(bound)) {
528
+ for (const element of bound.elements)
529
+ directories.set(element.name.text, directory);
530
+ }
531
+ }
532
+ return directories;
533
+ }
534
+ /**
535
+ * Directory name → the module that owns it, for every directory under the
536
+ * module root.
537
+ *
538
+ * Two rules, in order, and the second is the one this function exists for:
539
+ *
540
+ * 1. a directory named after a registered module is that module's;
541
+ * 2. a directory whose component renders a route a nav entry attributes to a
542
+ * registered module is that module's.
543
+ *
544
+ * A directory neither rule claims is the admin application's own and is absent
545
+ * from the map. Rule 2's join is by route path — exact first, then the longest
546
+ * nav `to` the route sits under, because `/warehouses/new` is declared as a
547
+ * route and never as its own sidebar row.
548
+ */
549
+ export function adminModuleDirectories(input) {
550
+ const owners = new Map();
551
+ const present = new Set(input.directories);
552
+ for (const directory of input.directories) {
553
+ if (input.registered.has(directory))
554
+ owners.set(directory, directory);
555
+ }
556
+ const navByPath = new Map();
557
+ for (const entry of input.nav)
558
+ navByPath.set(normalisePath(entry.to), entry.module);
559
+ for (const route of input.routes) {
560
+ if (route.component === null)
561
+ continue;
562
+ const directory = input.componentDirectories.get(route.component);
563
+ // A directory the module root does not hold is not a surface directory. The
564
+ // route table imports plenty of components from elsewhere, and attributing
565
+ // one of those would put an owner in the map that no walk can ever produce
566
+ // a file for — a coverage expectation nothing satisfies.
567
+ if (directory === undefined || !present.has(directory) || owners.has(directory))
568
+ continue;
569
+ const owner = navOwnerOf(normalisePath(route.path), navByPath);
570
+ if (owner !== null && input.registered.has(owner))
571
+ owners.set(directory, owner);
572
+ }
573
+ return owners;
574
+ }
575
+ function normalisePath(path) {
576
+ const trimmed = path.replace(/\/+$/, '');
577
+ return trimmed === '' ? '/' : trimmed;
578
+ }
579
+ /**
580
+ * The module a nav entry attributes this route to: the exact `to`, else the
581
+ * longest `to` the route sits under.
582
+ */
583
+ function navOwnerOf(path, navByPath) {
584
+ const exact = navByPath.get(path);
585
+ if (exact !== undefined)
586
+ return exact;
587
+ let best = null;
588
+ for (const [to, owner] of navByPath) {
589
+ if (to === '/' || !path.startsWith(`${to}/`))
590
+ continue;
591
+ if (best === null || to.length > best.to.length)
592
+ best = { to, module: owner };
593
+ }
594
+ return best?.module ?? null;
595
+ }
596
+ /**
597
+ * The whole admin layout, resolved once.
598
+ *
599
+ * `registered` is the manifest index's own id list, handed in rather than read
600
+ * here, so the two derivations of "which modules exist" cannot disagree.
601
+ */
602
+ export function resolveAdminSurfaces(members, registered, readText = defaultReadText) {
603
+ const { member, target } = findAliasMember(members, readText);
604
+ const sourceRoot = resolve(member.dir, target);
605
+ if (!existsSync(sourceRoot)) {
606
+ throw new AdminLayoutUnresolvableError(`${member.name} declares \`"${ADMIN_SOURCE_ALIAS}"\` pointing at ${sourceRoot}, which ` +
607
+ 'does not exist — every admin specifier is resolved through that alias');
608
+ }
609
+ // The admin's own sources, wherever they are: the project's and the shell's
610
+ // (feature 110, T122). Both refusals inside — no shell, and two — are the
611
+ // layout's, so every consumer inherits them and none re-derives the pair.
612
+ const hostRoots = adminHostRootsOf(members, readText);
613
+ const shellRoot = findAdminShellRoot(members, readText);
614
+ const moduleRoot = findAdminModuleRoot(hostRoots, registered);
615
+ const directories = moduleRoot === null
616
+ ? []
617
+ : readdirSync(moduleRoot)
618
+ .filter((entry) => !PRUNED.has(entry) && statSync(join(moduleRoot, entry)).isDirectory())
619
+ .sort();
620
+ // The two hand-written registries, spelled once. `registryFiles` below is
621
+ // these same two paths, so a consumer that has to exclude them — the boundary
622
+ // check's host population — never writes a second copy of the pair.
623
+ const routeTableFile = join(shellRoot, ROUTE_TABLE_FILE);
624
+ const navFile = join(shellRoot, NAV_FILE);
625
+ const appSource = readText(routeTableFile);
626
+ const shellSource = readText(navFile);
627
+ // Unreachable through `findAdminShellRoot`, which selected this root by
628
+ // reading both files; kept because a `null` here would otherwise be parsed as
629
+ // an empty route table, which attributes every surface directory to nobody.
630
+ if (appSource === null || shellSource === null) {
631
+ throw new AdminLayoutUnresolvableError(`${shellRoot} has no ${ROUTE_TABLE_FILE} or no ${NAV_FILE} — the module a surface ` +
632
+ 'directory belongs to is read from the route table and the nav, and attributing by ' +
633
+ 'directory name instead would file two modules\' reaches under names no module ' +
634
+ 'answers to');
635
+ }
636
+ const routes = adminRoutes(appSource);
637
+ const nav = adminNavEntries(shellSource);
638
+ // With no module root there is no directory a route component could be
639
+ // imported from, so the map is empty — a measurement, like `directories`.
640
+ const componentDirectories = moduleRoot === null
641
+ ? new Map()
642
+ : routeComponentDirectories(appSource,
643
+ // The module root's segment relative to whichever host root holds it.
644
+ moduleRoot.slice((hostRoots.find((root) => moduleRoot.startsWith(`${root}/`)) ?? sourceRoot).length + 1));
645
+ return {
646
+ memberDir: member.dir,
647
+ sourceRoot,
648
+ shellRoot,
649
+ hostRoots,
650
+ moduleRoot,
651
+ aliasPrefix: `${ADMIN_SOURCE_ALIAS.slice(0, -1)}`,
652
+ directories,
653
+ moduleOfDirectory: adminModuleDirectories({
654
+ directories,
655
+ registered,
656
+ routes,
657
+ nav,
658
+ componentDirectories,
659
+ }),
660
+ routes,
661
+ nav,
662
+ componentDirectories,
663
+ registryFiles: [routeTableFile, navFile],
664
+ // One derivation, two entry points — see `adminRegistryPathOf`. Non-null
665
+ // here by construction: the alias member has already resolved above.
666
+ generatedRegistryFile: adminRegistryPathOf(members, readText),
667
+ };
668
+ }
669
+ //# sourceMappingURL=admin-surfaces.js.map