@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,926 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `endora` — the argv layer, and nothing else.
4
+ *
5
+ * Three properties are contract (`contracts/cli-surface.md` §1–§2):
6
+ *
7
+ * * **`node:util`'s `parseArgs`**, so the program takes no dependency to read
8
+ * its own arguments, and an unrecognised flag is a refusal rather than a
9
+ * silent ignore.
10
+ * * **non-interactive by default, and guaranteed** (`cli-product.md` R2.5c,
11
+ * amending R2.5). A value the tool invented is still a value nobody
12
+ * reviewed, and nothing here invents one: a command that resolves inputs
13
+ * prints a provenance line whose `defaulted=` is `0`. What the amendment
14
+ * added is a *fallback* — on a terminal, a missing **required** input is
15
+ * asked for rather than refused. A prompt is issued only when stdin and
16
+ * stdout are both TTYs, `--non-interactive` and `--dry-run` are absent and
17
+ * no CI marker is set; failing any of those, a missing required input is
18
+ * exit `1` naming every one of them and the flag that supplies each, in one
19
+ * refusal. No command may block on a question nobody can answer.
20
+ * * **one meaning per exit code.** `0` did what it was asked and found nothing
21
+ * to report; `1` a finding or a refusal the author can act on; `2` an input
22
+ * it could not read. `2` is never reported as clean and never merged into
23
+ * `1`: a run that could not read its input has said nothing about the tree.
24
+ *
25
+ * It reads no configuration file. There is no `.endorarc` and no environment
26
+ * variable that changes a verdict.
27
+ */
28
+ import { realpathSync } from 'node:fs';
29
+ import { resolve } from 'node:path';
30
+ import { fileURLToPath } from 'node:url';
31
+ import { parseArgs } from 'node:util';
32
+ import { estateIds, runCheck } from '../check/index.js';
33
+ import { NotAModulePackageError } from '../check/layout.js';
34
+ import { runNewModule } from '../new-module/index.js';
35
+ import { ScaffoldHostError, ScaffoldInputError } from '../new-module/spec.js';
36
+ import { DeclarationLoadError } from '../inputs/declaration.js';
37
+ import { inputForFlag } from '../inputs/resolve.js';
38
+ import { MissingInputsError, runNewStorefront, storefrontInputFlags, } from '../new-storefront/index.js';
39
+ import { StorefrontHostError, StorefrontInputError } from '../new-storefront/reference.js';
40
+ import { InstanceHostError, InstanceInputError, runNewInstance, } from '../new-instance/index.js';
41
+ import { InstallHostError, InstallInputError, runInstall } from '../install/index.js';
42
+ import { DevHostError, DevInputError, runDev } from '../dev/index.js';
43
+ import { generateReport, GenerateHostError, GenerateInputError, runGenerate, } from '../generate/index.js';
44
+ const USAGE = `endora — scaffolding and conformance tooling for Endora Commerce modules.
45
+
46
+ Usage:
47
+ endora new module <id> --name <text> --description <text> [options]
48
+ endora new instance <dir> [--module <id>...] [--without <member>...] [--deployment <name>]
49
+ [--registry <url>] [--topology single-host|three-host] [--dry-run]
50
+ endora new storefront <dir> [--registry <url>] [--<input> <value>...] [--dry-run]
51
+ endora install [<dir>] [--admin-email <e>] [--admin-password <p>] [--admin-first-name <f>]
52
+ [--admin-last-name <l>] [--demo | --no-demo] [--without <member>...]
53
+ [--no-services] [--no-storefront] [--storefront-dir <path>]
54
+ [--module <id>...] [--deployment <name>] [--registry <url>]
55
+ [--topology single-host|three-host] [--non-interactive] [--dry-run]
56
+ endora generate [--dry-run]
57
+ endora dev [--storefront-dir <path>] [--no-storefront]
58
+ endora --help
59
+
60
+ \`endora new module\` writes a module package that is composed by the platform,
61
+ gated by the operator, discoverable in the command palette and correct on its
62
+ first commit. It does not author the package's package.json: that file is
63
+ rendered by the platform's own manifest generator, so the two cannot disagree.
64
+
65
+ Options for \`new module\`:
66
+ --name <text> the module's human-readable name (required)
67
+ --description <text> one sentence; seeds the package description (required)
68
+ --dir <path> where to write (default: the workspace's module tree)
69
+ --scope <@scope/> the npm scope; must be the workspace's own
70
+ --depends <id>[,<id>...] manifest dependencies (repeatable)
71
+ --permission <code>=<label> a permission code and its operator-facing label (repeatable)
72
+ --action <id>=<route> a command-palette action and the admin route it opens (repeatable)
73
+ --icon <KnownIconName> the icon for the emitted actions (default: Boxes)
74
+ --admin <navSection> emit the ./admin layer: a gated screen and the sidebar entry
75
+ that opens it, in the named section (main, sales, catalog,
76
+ inventory, pricing, customers, channels, content, messaging,
77
+ newsletter, analyticsAds, system). Needs a --permission
78
+ --entities own a table: an entity, a migration and the ./migrations subpath
79
+ --tenant-scope <scope> org-scoped | customer-scoped | global (default: org-scoped)
80
+ --ports publish a type-only ./ports subpath other modules resolve
81
+ --worker register a queue consumer through the module seam
82
+ --subscriber register an event subscription through the module seam
83
+ --activation-setting <code> the Setting holding the operator's on/off choice
84
+ (default: <id>.enabled)
85
+ --non-deactivatable <reason> declare that the platform cannot run without this module
86
+ --dry-run report what would be written; write nothing
87
+
88
+ \`endora new instance\` writes the repository that composes this platform for one
89
+ deployment: one workspace, one module list, and a copy of nothing. It is not a
90
+ fork of the platform and holds no file of it — the platform, the admin shell and
91
+ every module arrive as dependencies, so a fix in any of them reaches you through
92
+ \`pnpm update\` with no file in your tree edited.
93
+
94
+ Options for \`new instance\`:
95
+ <dir> where to write. Required; it must be empty, or
96
+ hold nothing but a \`.env\` you placed there. Its
97
+ basename becomes the workspace name
98
+ --module <id>[,<id>...] a module to install (repeatable). The set is
99
+ closed over the manifests' own dependencies.
100
+ Given none, it writes the smallest set that
101
+ composes — the modules the platform cannot run
102
+ without, closed the same way
103
+ --without <member>[,<member>...]
104
+ a member not to write (repeatable): \`admin\` or
105
+ \`docs\`. The default is every member; \`backend\`
106
+ cannot be declined, because it is what composes the
107
+ platform. The module list is the same either way
108
+ --deployment <name> the directory under \`apps/\` holding your overlay
109
+ modules and your divergence declaration, and the
110
+ value of \`DEPLOYMENT\` (default: the workspace name)
111
+ --registry <url> the endpoint the instance installs
112
+ \`@endora-commerce/*\` from. It writes an \`.npmrc\`
113
+ naming that endpoint, with the token as an
114
+ environment reference and never as a value
115
+ --topology <name> which machine layout the EXAMPLE deployment files
116
+ in \`deploy/\` describe: \`single-host\` (default) puts
117
+ every layer on one machine, \`three-host\` writes one
118
+ compose example and one \`.env.example\` per machine
119
+ for a backend, a storefront and an admin that scale
120
+ apart. It selects; it records nothing — no file in
121
+ the tree carries the value and nothing reads it back
122
+ --dry-run report every file it would write, the resolved
123
+ module set with its closure, and every omission;
124
+ write nothing
125
+
126
+ \`endora install\` is the one command: it writes the instance (and, from inside a
127
+ checkout of this repository, the storefront beside it), starts the development
128
+ services, installs, generates, builds, migrates, installs every module and creates
129
+ the administrator — the sequence \`endora new instance\` prints and nothing else. It
130
+ composes the two \`new\` commands and reimplements neither, and every step is echoed
131
+ before it runs so an operator can reproduce any one of them by hand. A failing step
132
+ exits with that step's own code and prints what is left.
133
+
134
+ With no \`--module\` it installs every module of the open-source set, each switched
135
+ on; switch any of them off later under Modules in the admin, without losing its
136
+ data. Run where nothing of ours is installed beside the target — from an empty
137
+ directory, or through \`npx\` — it first installs this release's packages into a
138
+ temporary directory to read the module set from, and removes it when done.
139
+
140
+ At a terminal it asks what the flags below did not answer — at most seven questions,
141
+ and Enter takes the recommendation wherever there is one: every part, the services
142
+ started. The demo question has no recommendation and the administrator has no
143
+ default. Every question has a flag; with \`--non-interactive\`, \`--dry-run\`, a CI
144
+ marker or no terminal it asks nothing, and a missing answer is one refusal naming
145
+ every flag still owed.
146
+
147
+ Options for \`install\`:
148
+ <dir> where the instance goes; it must be empty, or hold
149
+ nothing but a \`.env\` you placed there. Asked at a
150
+ terminal (recommending \`./endora-commerce\`),
151
+ required everywhere else
152
+ --admin-email <address> the administrator you sign in as. All four are
153
+ --admin-password <secret> required: nothing else creates an account, and the
154
+ --admin-first-name <text> password is never generated — it is the one value
155
+ --admin-last-name <text> you have to remember
156
+ --demo | --no-demo whether to seed every installed module's example
157
+ data. Required — asked at a terminal — and
158
+ deliberately with no default:
159
+ an instance you will sell from wants none of it and
160
+ one you are evaluating wants it before the first
161
+ screen. Seeding runs last and a failure in it does
162
+ not fail the install
163
+ --no-services do not start PostgreSQL, Redis, Meilisearch and the
164
+ mail catcher, and do not write their addresses into
165
+ the instance's \`.env\`. Use it when you run those
166
+ services yourself
167
+ --without <member>[,<member>...]
168
+ a member of the instance not to write (\`admin\`,
169
+ \`docs\`), passed to \`new instance\`; the default is
170
+ every member
171
+ --no-storefront write the instance alone. The storefront is copied
172
+ out of a checkout of this repository, so a run from
173
+ anywhere else needs this flag
174
+ --storefront-dir <path> where the storefront goes (default: \`<dir>-storefront\`,
175
+ a SIBLING — inside the instance it would be swept into
176
+ that workspace and become a member of it)
177
+ --module, --deployment, --registry, --topology
178
+ passed through to \`new instance\` unread
179
+ --non-interactive ask nothing, even at a terminal
180
+ --dry-run report every file and every step; write nothing,
181
+ start nothing, run nothing and ask nothing
182
+
183
+ \`endora dev\` is what an instance's \`pnpm run dev:all\` runs: the API (\`pnpm run
184
+ start\`), the admin preview (\`pnpm run preview:admin\`) and, when there is one, the
185
+ storefront beside the instance (its \`pnpm run dev\`), in one terminal with each
186
+ line prefixed by its layer. Ctrl-C stops all of them; any one ending stops the
187
+ rest and names which. It changes no layer's own build or start command — each is
188
+ still deployed on its own. Run it from the instance's root.
189
+
190
+ Options for \`dev\`:
191
+ --storefront-dir <path> where the storefront is (default: \`<dir>-storefront\`
192
+ beside the instance, when it exists)
193
+ --no-storefront start the API and the admin preview only
194
+
195
+ \`endora generate\` renders the two files an instance's admin project is built
196
+ from and commits neither: the contribution registry of the module packages this
197
+ instance installed, and the stylesheet enumeration that makes their utility
198
+ classes survive Tailwind's scan. Both are facts about the install rather than
199
+ about the tree, so they are regenerated after every \`pnpm install\` and are
200
+ git-ignored. Run it anywhere inside the instance; it finds the workspace root
201
+ upwards. It reports every package the discovery excluded, because a linked
202
+ package is invisible to this instance's runtime discovery too.
203
+
204
+ \`endora new storefront\` copies the reference storefront out of this repository
205
+ into a directory you then own outright, and rewrites every declaration in it that
206
+ names something above the storefront's own directory: each \`workspace:\` range
207
+ into published semver, each configuration file the storefront extends into a
208
+ vendored standalone copy, and each glob naming the workspace's package tree into
209
+ the place a standalone application finds those packages. It keeps no channel back
210
+ to what it wrote — a scaffold that did would be a kit wearing a different name
211
+ (D-195). An outward declaration it cannot make standalone is a refusal, never a
212
+ file copied out unchanged.
213
+
214
+ Options for \`new storefront\`:
215
+ <dir> where to write. Required; it must be empty, or
216
+ hold nothing but a \`.env\` you placed there
217
+ --<input> <value> a value for one of the inputs the reference
218
+ storefront declares, named after the variable in
219
+ lower case with underscores as dashes —
220
+ \`--next-public-api-base-url https://api.example.com\`.
221
+ Inputs resolve in one fixed order: this flag,
222
+ then a \`.env\` already in the target directory,
223
+ then a prompt if you are on a terminal, then a
224
+ refusal naming every one that is still missing.
225
+ Run with \`--dry-run\` to see the list first
226
+ --non-interactive refuse rather than ask, even on a terminal
227
+ --registry <url> the endpoint the scaffold installs \`@endora-commerce/*\`
228
+ from. It writes an \`.npmrc\` naming that endpoint for the
229
+ scopes the storefront declares, with the token as
230
+ \`\${ENDORA_NPM_TOKEN}\` — an environment reference pnpm
231
+ expands at install time, so the file holds no secret and
232
+ is committable. Omitted, it writes no \`.npmrc\` at all,
233
+ which is what a consumer of the public registry holds
234
+ --dry-run report the copy, every rewrite and every omission;
235
+ write nothing
236
+
237
+ \`endora check\` evaluates the platform's whole static-check estate against one
238
+ module package. Every rule in that estate gets exactly one verdict on every run —
239
+ it ran (clean, or with findings), it is \`not-applicable\` because the package
240
+ declares no subject for it or because its subject is the platform repository, it
241
+ is \`unreadable\` because an input the author can supply is absent, or it is
242
+ \`pending\` because this build has no package-scope host for it yet. A rule that
243
+ is neither run nor explained is the silent skip the whole design refuses, which
244
+ is why the incompleteness is **printed** rather than waived: while any rule is
245
+ \`pending\` the run exits 2 and names the phase that lands it.
246
+
247
+ Options for \`check\`:
248
+ [path] the module package to check (default: the working
249
+ directory, or the nearest ancestor declaring
250
+ \`endora: { "type": "module", "id": … }\`)
251
+ --rule <id> evaluate only this rule (repeatable). It can only
252
+ remove; there is no flag that adds a rule, changes
253
+ a verdict or relaxes a refusal
254
+ --as-platform read every acknowledged finding as a finding. The
255
+ package's own ledger answers the author's question
256
+ (is my module in the state I decided it should be
257
+ in?) and never the platform's (does this module
258
+ satisfy the rules we admit modules on?)
259
+ --list-rules print the estate and each rule's classification
260
+
261
+ Exit codes: 0 the estate was completely evaluated and found nothing; 1 it was
262
+ completely evaluated and there are findings; 2 the picture is incomplete — some
263
+ rule could not be read, or has no host in this build. \`findings=<n>\` is on the
264
+ arithmetic line whatever the code is.
265
+ `;
266
+ /**
267
+ * `strict: true` stays, and the option table grows instead.
268
+ *
269
+ * `endora new storefront` accepts one flag per input the reference storefront
270
+ * **declares** (feature 117, FR-010), and those names are the storefront's
271
+ * rather than this program's. Parsing that subcommand leniently was the obvious
272
+ * alternative and is the wrong one: it would turn a typo'd
273
+ * `--next-public-api-base-ur` from a loud refusal into a silently unset required
274
+ * input, which is the class of failure this whole feature exists to remove. So
275
+ * the names are resolved from the declaration first and handed in here.
276
+ */
277
+ function parse(argv, declaredInputFlags = []) {
278
+ const { values, positionals } = parseArgs({
279
+ args: [...argv],
280
+ allowPositionals: true,
281
+ strict: true,
282
+ options: {
283
+ ...Object.fromEntries(declaredInputFlags.map((flag) => [flag, { type: 'string' }])),
284
+ help: { type: 'boolean', short: 'h' },
285
+ rule: { type: 'string', multiple: true },
286
+ 'as-platform': { type: 'boolean' },
287
+ 'list-rules': { type: 'boolean' },
288
+ name: { type: 'string' },
289
+ description: { type: 'string' },
290
+ dir: { type: 'string' },
291
+ scope: { type: 'string' },
292
+ depends: { type: 'string', multiple: true },
293
+ permission: { type: 'string', multiple: true },
294
+ action: { type: 'string', multiple: true },
295
+ icon: { type: 'string' },
296
+ admin: { type: 'string' },
297
+ entities: { type: 'boolean' },
298
+ ports: { type: 'boolean' },
299
+ worker: { type: 'boolean' },
300
+ subscriber: { type: 'boolean' },
301
+ 'tenant-scope': { type: 'string' },
302
+ 'activation-setting': { type: 'string' },
303
+ 'non-deactivatable': { type: 'string' },
304
+ module: { type: 'string', multiple: true },
305
+ without: { type: 'string', multiple: true },
306
+ deployment: { type: 'string' },
307
+ registry: { type: 'string' },
308
+ topology: { type: 'string' },
309
+ 'non-interactive': { type: 'boolean' },
310
+ 'dry-run': { type: 'boolean' },
311
+ // `endora install` (feature 125). `--no-*` is a flag of its own rather
312
+ // than a negation `parseArgs` understands: the library has no negation,
313
+ // and a default that is `true` needs a name to be turned off by.
314
+ 'no-services': { type: 'boolean' },
315
+ 'no-storefront': { type: 'boolean' },
316
+ 'storefront-dir': { type: 'string' },
317
+ 'admin-email': { type: 'string' },
318
+ 'admin-password': { type: 'string' },
319
+ 'admin-first-name': { type: 'string' },
320
+ 'admin-last-name': { type: 'string' },
321
+ 'revalidate-secret': { type: 'string' },
322
+ demo: { type: 'boolean' },
323
+ 'no-demo': { type: 'boolean' },
324
+ },
325
+ });
326
+ return { values, positionals };
327
+ }
328
+ function asString(value) {
329
+ return typeof value === 'string' ? value : undefined;
330
+ }
331
+ function asList(value) {
332
+ if (Array.isArray(value))
333
+ return value.flatMap((entry) => entry.split(','));
334
+ return typeof value === 'string' ? value.split(',') : [];
335
+ }
336
+ function asFlag(value) {
337
+ return value === true;
338
+ }
339
+ /**
340
+ * `endora check` — the argv half.
341
+ *
342
+ * It resolves nothing and decides nothing: the package comes off the working
343
+ * directory (or one positional), the verdicts come from the estate, and the exit
344
+ * code is `runCheck`'s. A flag that could change a verdict is the configuration
345
+ * this program does not have.
346
+ */
347
+ function runCheckCommand(parsed, positionals, cwd) {
348
+ if (asFlag(parsed.values['list-rules'])) {
349
+ for (const id of estateIds())
350
+ process.stdout.write(`${id}\n`);
351
+ return 0;
352
+ }
353
+ if (positionals.length > 1) {
354
+ process.stderr.write(`endora: \`check\` takes one package path; got ${positionals.length} ` +
355
+ `(${positionals.join(', ')}).\n`);
356
+ return 1;
357
+ }
358
+ const rules = asList(parsed.values['rule']);
359
+ const known = new Set(estateIds());
360
+ const unknown = rules.filter((rule) => !known.has(rule));
361
+ if (unknown.length > 0) {
362
+ process.stderr.write(`endora: \`--rule\` names ${unknown.join(', ')}, which the estate does not hold. ` +
363
+ `Run \`endora check --list-rules\` for the ids.\n`);
364
+ return 1;
365
+ }
366
+ try {
367
+ const run = runCheck({
368
+ cwd: positionals[0] === undefined ? cwd : resolve(cwd, positionals[0]),
369
+ rules,
370
+ asPlatform: asFlag(parsed.values['as-platform']),
371
+ });
372
+ for (const line of run.lines)
373
+ process.stdout.write(`${line}\n`);
374
+ return run.report.exitCode;
375
+ }
376
+ catch (error) {
377
+ if (error instanceof NotAModulePackageError) {
378
+ process.stderr.write(`endora: ${error.message}\n`);
379
+ return 1;
380
+ }
381
+ // Anything else is an input this run could not read, and a run that could
382
+ // not read its input has said nothing about the tree.
383
+ process.stderr.write(`endora: check could not read its input: ` +
384
+ `${error instanceof Error ? error.message : String(error)}\n`);
385
+ return 2;
386
+ }
387
+ }
388
+ /**
389
+ * `endora new storefront` — the argv half.
390
+ *
391
+ * It decides nothing. The target comes off one positional, the population and
392
+ * every rewrite come from the reference storefront in this checkout, and the
393
+ * exit code is the refusal's class: an author-fixable refusal is 1, a checkout
394
+ * this command cannot read a reference storefront out of is 2.
395
+ */
396
+ async function runNewStorefrontCommand(parsed, rest, cwd, declaredInputFlags) {
397
+ if (rest.length > 1) {
398
+ process.stderr.write(`endora: \`new storefront\` takes one directory; got ${String(rest.length)} ` +
399
+ `(${rest.join(', ')}).\n`);
400
+ return 1;
401
+ }
402
+ // The inputs, keyed by **variable name**: the flag is derived from the name
403
+ // (`inputForFlag`), so a storefront declaring a variable this build has never
404
+ // heard of is supplied by its own flag with nothing here to update.
405
+ const inputs = {};
406
+ for (const flag of declaredInputFlags) {
407
+ const value = asString(parsed.values[flag]);
408
+ if (value !== undefined)
409
+ inputs[inputForFlag(flag)] = value;
410
+ }
411
+ try {
412
+ const result = await runNewStorefront({
413
+ ...(rest[0] === undefined ? {} : { dir: rest[0] }),
414
+ ...(asString(parsed.values['registry']) === undefined
415
+ ? {}
416
+ : { registry: asString(parsed.values['registry']) }),
417
+ dryRun: asFlag(parsed.values['dry-run']),
418
+ nonInteractive: asFlag(parsed.values['non-interactive']),
419
+ inputs,
420
+ cwd,
421
+ });
422
+ const { plan } = result;
423
+ process.stdout.write(`endora new storefront ${result.targetDir}${result.dryRun ? ' — dry run, nothing written' : ''}\n`);
424
+ process.stdout.write(` ${result.dryRun ? 'would write' : 'wrote'} ${String(plan.files.length)} files, from ` +
425
+ `${result.reference.dir}\n`);
426
+ for (const range of plan.ranges) {
427
+ process.stdout.write(` ${range.field}.${range.name}: ${range.from} -> ${range.to}\n`);
428
+ }
429
+ for (const rewrite of plan.rewrites) {
430
+ process.stdout.write(` ${rewrite.file}: ${rewrite.specifier} -> ${rewrite.to}\n`);
431
+ }
432
+ for (const file of plan.files) {
433
+ if (file.source === null && file.path !== 'package.json') {
434
+ process.stdout.write(` ${file.path} — ${file.note ?? ''}\n`);
435
+ }
436
+ }
437
+ for (const omission of plan.omitted) {
438
+ process.stdout.write(` omitted ${omission.path} — ${omission.reason}\n`);
439
+ }
440
+ // R2.1 — one provenance line per run, accounting for every input, with a
441
+ // `defaulted=` count that is contract-bound to `0`. It is printed on every
442
+ // run including a dry one, because the claim it makes is about the run.
443
+ process.stdout.write(`\n${result.provenance}\n`);
444
+ for (const name of result.wouldPrompt) {
445
+ process.stdout.write(` would ask for ${name}\n`);
446
+ }
447
+ for (const name of result.wouldGenerate) {
448
+ process.stdout.write(` would generate ${name}\n`);
449
+ }
450
+ for (const name of result.unset) {
451
+ process.stdout.write(` left unset ${name} — optional\n`);
452
+ }
453
+ process.stdout.write(`\nNext steps:\n`);
454
+ result.nextSteps.forEach((step, index) => {
455
+ process.stdout.write(` ${String(index + 1)}. ${step}\n`);
456
+ });
457
+ return 0;
458
+ }
459
+ catch (error) {
460
+ // A missing required input is a ninth refusal class and it is exit `1`
461
+ // (R7.2): the operator has something to supply, and the message names every
462
+ // one at once rather than the first.
463
+ if (error instanceof MissingInputsError) {
464
+ process.stderr.write(`${error.message}\n`);
465
+ return 1;
466
+ }
467
+ if (error instanceof StorefrontInputError) {
468
+ process.stderr.write(`endora: ${error.message}\n`);
469
+ return 1;
470
+ }
471
+ // A declaration this run could not read is a derivation failure, not an
472
+ // operator input: R7.1 — asking a human to type a value the tool failed to
473
+ // *read* is how a wrong value enters wearing the operator's authority.
474
+ if (error instanceof DeclarationLoadError) {
475
+ process.stderr.write(`endora: ${error.message}\n`);
476
+ return 2;
477
+ }
478
+ if (error instanceof StorefrontHostError) {
479
+ process.stderr.write(`endora: ${error.message}\n`);
480
+ return 2;
481
+ }
482
+ throw error;
483
+ }
484
+ }
485
+ /**
486
+ * `endora new instance` — the argv half.
487
+ *
488
+ * It decides nothing. The target comes off one positional, the module set and
489
+ * every range come from the packages the command resolved, and the exit code is
490
+ * the refusal's class: an operator-fixable refusal is 1, an input the run could
491
+ * not read is 2 (`instance-tree.md` §4).
492
+ */
493
+ /**
494
+ * `endora generate` — the artefacts an instance is built from and commits none
495
+ * of (`contracts/instance-tree.md` §2.6): its admin project's two, and its
496
+ * documentation site's navigation, module map and per-module reference pages.
497
+ *
498
+ * It takes no positional: the instance is the workspace above the working
499
+ * directory, so `pnpm -C admin run generate` and a client standing in the root
500
+ * both answer the same. A positional would be a second way to name a tree the
501
+ * run is already standing in.
502
+ */
503
+ async function runGenerateCommand(parsed, rest, cwd) {
504
+ if (rest.length > 0) {
505
+ process.stderr.write(`endora: \`generate\` takes no argument; got ${rest.join(', ')}. It renders the ` +
506
+ `artefacts of the instance whose workspace root is above the current directory.\n`);
507
+ return 1;
508
+ }
509
+ try {
510
+ const result = await runGenerate({ cwd, dryRun: asFlag(parsed.values['dry-run']) });
511
+ process.stdout.write(`endora generate ${result.root}${result.dryRun ? ' — dry run, nothing written' : ''}\n`);
512
+ for (const line of generateReport(result))
513
+ process.stdout.write(` ${line}\n`);
514
+ process.stdout.write(` ${String(result.modules)} installed module package(s) in the population\n`);
515
+ return 0;
516
+ }
517
+ catch (error) {
518
+ if (error instanceof GenerateInputError) {
519
+ process.stderr.write(`endora: ${error.message}\n`);
520
+ return 1;
521
+ }
522
+ if (error instanceof GenerateHostError) {
523
+ process.stderr.write(`endora: ${error.message}\n`);
524
+ return 2;
525
+ }
526
+ throw error;
527
+ }
528
+ }
529
+ async function runNewInstanceCommand(parsed, rest, cwd) {
530
+ if (rest.length > 1) {
531
+ process.stderr.write(`endora: \`new instance\` takes one directory; got ${String(rest.length)} ` +
532
+ `(${rest.join(', ')}).\n`);
533
+ return 1;
534
+ }
535
+ try {
536
+ const result = await runNewInstance({
537
+ ...(rest[0] === undefined ? {} : { dir: rest[0] }),
538
+ modules: asList(parsed.values['module']),
539
+ without: asList(parsed.values['without']),
540
+ ...(asString(parsed.values['deployment']) === undefined
541
+ ? {}
542
+ : { deployment: asString(parsed.values['deployment']) }),
543
+ ...(asString(parsed.values['registry']) === undefined
544
+ ? {}
545
+ : { registry: asString(parsed.values['registry']) }),
546
+ ...(asString(parsed.values['topology']) === undefined
547
+ ? {}
548
+ : { topology: asString(parsed.values['topology']) }),
549
+ dryRun: asFlag(parsed.values['dry-run']),
550
+ cwd,
551
+ });
552
+ const { plan, modules } = result;
553
+ process.stdout.write(`endora new instance ${result.targetDir}` +
554
+ `${result.dryRun ? ' — dry run, nothing written' : ''}\n`);
555
+ process.stdout.write(` ${result.dryRun ? 'would write' : 'wrote'} ${String(plan.files.length)} files ` +
556
+ `across ${plan.members.join(', ')}\n`);
557
+ for (const file of plan.files) {
558
+ process.stdout.write(` ${result.dryRun ? 'would write' : 'wrote'} ${file.path} — ${file.kind}\n`);
559
+ }
560
+ // §3 — the set, its closure and where the default came from, because a
561
+ // module list nobody can see the derivation of is a list.
562
+ process.stdout.write(`\n[modules] ${String(modules.ids.length)} in the set` +
563
+ `${modules.defaulted ? ' (no --module given: the modules the platform cannot run without)' : ''}` +
564
+ `: ${modules.ids.join(', ')}\n`);
565
+ if (modules.closure.length > 0) {
566
+ process.stdout.write(` added by closure: ${modules.closure.join(', ')}\n`);
567
+ }
568
+ if (plan.registry !== null) {
569
+ process.stdout.write(` installs from ${plan.registry}\n`);
570
+ }
571
+ for (const omission of plan.omitted) {
572
+ process.stdout.write(` omitted ${omission.path} — ${omission.reason}\n`);
573
+ }
574
+ process.stdout.write(` wiring: ${String(result.wiringLines)} lines\n`);
575
+ process.stdout.write(`\n${result.provenance}\n`);
576
+ process.stdout.write(`\nNext steps:\n`);
577
+ result.nextSteps.forEach((step, index) => {
578
+ process.stdout.write(` ${String(index + 1)}. ${step}\n`);
579
+ });
580
+ return 0;
581
+ }
582
+ catch (error) {
583
+ if (error instanceof InstanceInputError) {
584
+ process.stderr.write(`endora: [${error.refusal}] ${error.message}\n`);
585
+ return 1;
586
+ }
587
+ if (error instanceof InstanceHostError) {
588
+ process.stderr.write(`endora: [${error.refusal}] ${error.message}\n`);
589
+ return 2;
590
+ }
591
+ throw error;
592
+ }
593
+ }
594
+ /**
595
+ * `endora install` — the argv half (feature 125, FR-140…FR-161).
596
+ *
597
+ * It decides nothing of its own. Every value is a flag, the two trees are
598
+ * written by the two `new` commands, and the exit code is either the refusal's
599
+ * class — 1 for an operator-fixable refusal, 2 for an input it could not read —
600
+ * or, when the pipeline ran and a step failed, **that step's own code** (FR-156).
601
+ */
602
+ async function runInstallCommand(parsed, rest, cwd) {
603
+ if (rest.length > 1) {
604
+ process.stderr.write(`endora: \`install\` takes one directory; got ${String(rest.length)} ` +
605
+ `(${rest.join(', ')}).\n`);
606
+ return 1;
607
+ }
608
+ // `--demo` and `--no-demo` are two flags and one answer, so a run that gives
609
+ // both is a run whose author believes two different things. Refused rather
610
+ // than resolved by precedence: a precedence rule here would silently discard
611
+ // half of what they typed.
612
+ if (asFlag(parsed.values['demo']) && asFlag(parsed.values['no-demo'])) {
613
+ process.stderr.write('endora: `--demo` and `--no-demo` were both given, and they are the two answers to one ' +
614
+ 'question. Pass one.\n');
615
+ return 1;
616
+ }
617
+ const demo = asFlag(parsed.values['demo'])
618
+ ? true
619
+ : asFlag(parsed.values['no-demo'])
620
+ ? false
621
+ : undefined;
622
+ try {
623
+ const result = await runInstall({
624
+ ...(rest[0] === undefined ? {} : { dir: rest[0] }),
625
+ cwd,
626
+ modules: asList(parsed.values['module']),
627
+ ...(asString(parsed.values['deployment']) === undefined
628
+ ? {}
629
+ : { deployment: asString(parsed.values['deployment']) }),
630
+ ...(asString(parsed.values['registry']) === undefined
631
+ ? {}
632
+ : { registry: asString(parsed.values['registry']) }),
633
+ ...(asString(parsed.values['topology']) === undefined
634
+ ? {}
635
+ : { topology: asString(parsed.values['topology']) }),
636
+ // A key only for a flag that was typed: the wizard reads *absent* as a
637
+ // question still to ask, and a `true` here would be an answer nobody gave.
638
+ ...(asFlag(parsed.values['no-storefront']) ? { storefront: false } : {}),
639
+ ...(asString(parsed.values['storefront-dir']) === undefined
640
+ ? {}
641
+ : { storefrontDir: asString(parsed.values['storefront-dir']) }),
642
+ ...(asFlag(parsed.values['no-services']) ? { services: false } : {}),
643
+ without: asList(parsed.values['without']),
644
+ ...(demo === undefined ? {} : { demo }),
645
+ ...(asString(parsed.values['admin-email']) === undefined
646
+ ? {}
647
+ : { adminEmail: asString(parsed.values['admin-email']) }),
648
+ ...(asString(parsed.values['admin-password']) === undefined
649
+ ? {}
650
+ : { adminPassword: asString(parsed.values['admin-password']) }),
651
+ ...(asString(parsed.values['admin-first-name']) === undefined
652
+ ? {}
653
+ : { adminFirstName: asString(parsed.values['admin-first-name']) }),
654
+ ...(asString(parsed.values['admin-last-name']) === undefined
655
+ ? {}
656
+ : { adminLastName: asString(parsed.values['admin-last-name']) }),
657
+ ...(asString(parsed.values['revalidate-secret']) === undefined
658
+ ? {}
659
+ : { revalidateSecret: asString(parsed.values['revalidate-secret']) }),
660
+ dryRun: asFlag(parsed.values['dry-run']),
661
+ nonInteractive: asFlag(parsed.values['non-interactive']),
662
+ // The pipeline's own output is the operator's: every step inherits the
663
+ // descriptors, so what pnpm says is what they see, live.
664
+ echo: (line) => void process.stdout.write(`${line}\n`),
665
+ });
666
+ return result.exitCode;
667
+ }
668
+ catch (error) {
669
+ if (error instanceof InstallInputError) {
670
+ process.stderr.write(`endora: ${error.message}\n`);
671
+ return 1;
672
+ }
673
+ if (error instanceof InstallHostError) {
674
+ process.stderr.write(`endora: ${error.message}\n`);
675
+ // 2 for an input it could not read; a failed host install exits with
676
+ // that step's own code, like every other step (FR-156, D-271).
677
+ return error.exitCode;
678
+ }
679
+ // The two commands this one composes keep their own classes and their own
680
+ // codes: a refusal they make is theirs, and reporting it as this command's
681
+ // would lose the class `instance-tree.md` §4 assigns it.
682
+ if (error instanceof InstanceInputError || error instanceof StorefrontInputError) {
683
+ process.stderr.write(`endora: ${error.message}\n`);
684
+ return 1;
685
+ }
686
+ if (error instanceof InstanceHostError || error instanceof StorefrontHostError) {
687
+ process.stderr.write(`endora: ${error.message}\n`);
688
+ return 2;
689
+ }
690
+ if (error instanceof MissingInputsError) {
691
+ process.stderr.write(`${error.message}\n`);
692
+ return 1;
693
+ }
694
+ throw error;
695
+ }
696
+ }
697
+ /**
698
+ * `endora dev` — the argv half of an instance's `pnpm run dev:all`
699
+ * (`specs/136-open-source-publication/` GAP-7, FR-060).
700
+ *
701
+ * The terminal's interrupt reaches this process and is forwarded: each layer
702
+ * runs in a process group of its own, so the supervisor — not the terminal —
703
+ * decides the order things stop in, and a backgrounded watcher inside a layer
704
+ * is stopped with it. An interrupt is exit 0: the operator asked for the stop.
705
+ */
706
+ async function runDevCommand(parsed, rest, cwd) {
707
+ if (rest.length > 0) {
708
+ process.stderr.write(`endora: \`dev\` takes no argument; got ${rest.join(', ')}. Run it from the root of ` +
709
+ `the instance, as \`pnpm run dev:all\`.\n`);
710
+ return 1;
711
+ }
712
+ const storefrontDir = asString(parsed.values['storefront-dir']);
713
+ if (asFlag(parsed.values['no-storefront']) && storefrontDir !== undefined) {
714
+ process.stderr.write('endora: `--no-storefront` and `--storefront-dir` were both given, and they are two ' +
715
+ 'answers to one question. Pass one.\n');
716
+ return 1;
717
+ }
718
+ const controller = new AbortController();
719
+ const interrupt = () => controller.abort();
720
+ process.on('SIGINT', interrupt);
721
+ process.on('SIGTERM', interrupt);
722
+ try {
723
+ return await runDev({
724
+ cwd,
725
+ storefront: asFlag(parsed.values['no-storefront']) ? false : storefrontDir,
726
+ write: (line) => void process.stdout.write(`${line}\n`),
727
+ signal: controller.signal,
728
+ });
729
+ }
730
+ catch (error) {
731
+ if (error instanceof DevInputError) {
732
+ process.stderr.write(`endora: ${error.message}\n`);
733
+ return 1;
734
+ }
735
+ if (error instanceof DevHostError) {
736
+ process.stderr.write(`endora: ${error.message}\n`);
737
+ return 2;
738
+ }
739
+ throw error;
740
+ }
741
+ finally {
742
+ process.off('SIGINT', interrupt);
743
+ process.off('SIGTERM', interrupt);
744
+ }
745
+ }
746
+ export async function main(argv, cwd) {
747
+ // The one thing that has to happen **before** the parse: `new storefront`
748
+ // accepts a flag per input the reference storefront declares, and those names
749
+ // are the storefront's. Keyed on the two leading tokens rather than on a
750
+ // heuristic over the whole of argv, so the answer never depends on whether a
751
+ // flag's *value* happens to read like a subcommand.
752
+ let declaredInputFlags = [];
753
+ if (argv[0] === 'new' && argv[1] === 'storefront') {
754
+ try {
755
+ declaredInputFlags = await storefrontInputFlags(cwd);
756
+ }
757
+ catch (error) {
758
+ if (error instanceof StorefrontHostError || error instanceof DeclarationLoadError) {
759
+ process.stderr.write(`endora: ${error.message}\n`);
760
+ return 2;
761
+ }
762
+ throw error;
763
+ }
764
+ }
765
+ let parsed;
766
+ try {
767
+ parsed = parse(argv, declaredInputFlags);
768
+ }
769
+ catch (error) {
770
+ process.stderr.write(`endora: ${error instanceof Error ? error.message : String(error)}\n`);
771
+ process.stderr.write(`Run \`endora --help\` for the flags this build accepts.\n`);
772
+ return 1;
773
+ }
774
+ const [command, subject, ...rest] = parsed.positionals;
775
+ if (asFlag(parsed.values['help']) || command === undefined) {
776
+ process.stdout.write(USAGE);
777
+ return command === undefined && !asFlag(parsed.values['help']) ? 1 : 0;
778
+ }
779
+ if (command === 'check') {
780
+ return runCheckCommand(parsed, [subject, ...rest].filter((v) => v !== undefined), cwd);
781
+ }
782
+ if (command === 'install') {
783
+ return runInstallCommand(parsed, [subject, ...rest].filter((v) => v !== undefined), cwd);
784
+ }
785
+ if (command === 'generate') {
786
+ return runGenerateCommand(parsed, [subject, ...rest].filter((v) => v !== undefined), cwd);
787
+ }
788
+ if (command === 'dev') {
789
+ return runDevCommand(parsed, [subject, ...rest].filter((v) => v !== undefined), cwd);
790
+ }
791
+ if (command !== 'new') {
792
+ process.stderr.write(`endora: unknown command "${command}". This build provides \`new module\`, ` +
793
+ `\`new instance\`, \`new storefront\`, \`install\`, \`generate\`, \`dev\` and \`check\`.\n`);
794
+ return 1;
795
+ }
796
+ if (subject === 'storefront') {
797
+ return runNewStorefrontCommand(parsed, rest, cwd, declaredInputFlags);
798
+ }
799
+ if (subject === 'instance') {
800
+ return runNewInstanceCommand(parsed, rest, cwd);
801
+ }
802
+ if (subject !== 'module') {
803
+ process.stderr.write(`endora: unknown subject "${subject ?? '<none>'}" for \`new\`. This build provides ` +
804
+ `\`new module\`, \`new instance\` and \`new storefront\`; the other generators named ` +
805
+ `in the ` +
806
+ `scaffolding contract are not delivered.\n`);
807
+ return 1;
808
+ }
809
+ const id = rest[0];
810
+ if (rest.length > 1) {
811
+ process.stderr.write(`endora: \`new module\` takes one id; got ${rest.length} (${rest.join(', ')}).\n`);
812
+ return 1;
813
+ }
814
+ try {
815
+ const result = await runNewModule({
816
+ ...(id === undefined ? {} : { id }),
817
+ ...(asString(parsed.values['name']) === undefined
818
+ ? {}
819
+ : { name: asString(parsed.values['name']) }),
820
+ ...(asString(parsed.values['description']) === undefined
821
+ ? {}
822
+ : { description: asString(parsed.values['description']) }),
823
+ ...(asString(parsed.values['dir']) === undefined
824
+ ? {}
825
+ : { dir: asString(parsed.values['dir']) }),
826
+ ...(asString(parsed.values['scope']) === undefined
827
+ ? {}
828
+ : { scope: asString(parsed.values['scope']) }),
829
+ dependencies: asList(parsed.values['depends']),
830
+ permissions: asList(parsed.values['permission']),
831
+ actions: asList(parsed.values['action']),
832
+ ...(asString(parsed.values['icon']) === undefined
833
+ ? {}
834
+ : { icon: asString(parsed.values['icon']) }),
835
+ ...(asString(parsed.values['admin']) === undefined
836
+ ? {}
837
+ : { admin: asString(parsed.values['admin']) }),
838
+ entities: asFlag(parsed.values['entities']),
839
+ ports: asFlag(parsed.values['ports']),
840
+ worker: asFlag(parsed.values['worker']),
841
+ subscriber: asFlag(parsed.values['subscriber']),
842
+ ...(asString(parsed.values['tenant-scope']) === undefined
843
+ ? {}
844
+ : { tenantScope: asString(parsed.values['tenant-scope']) }),
845
+ ...(asString(parsed.values['activation-setting']) === undefined
846
+ ? {}
847
+ : { activationSetting: asString(parsed.values['activation-setting']) }),
848
+ ...(asString(parsed.values['non-deactivatable']) === undefined
849
+ ? {}
850
+ : { nonDeactivatable: asString(parsed.values['non-deactivatable']) }),
851
+ dryRun: asFlag(parsed.values['dry-run']),
852
+ cwd,
853
+ });
854
+ if (result.dryRun) {
855
+ process.stdout.write(`endora new module ${result.spec.id} — dry run, nothing written.\n\n`);
856
+ for (const file of result.files) {
857
+ process.stdout.write(`--- ${file.path}\n${file.content}\n`);
858
+ }
859
+ process.stdout.write(`--- package.json\n(rendered by the platform's manifest generator once the sources ` +
860
+ `above are on disk; this command composes no field of it)\n\n`);
861
+ }
862
+ else {
863
+ process.stdout.write(`endora new module ${result.spec.id} — ${result.packageName}\n`);
864
+ for (const file of result.files) {
865
+ process.stdout.write(` wrote ${file.path}\n`);
866
+ }
867
+ for (const written of result.renderedManifests) {
868
+ process.stdout.write(` rendered ${written}\n`);
869
+ }
870
+ }
871
+ process.stdout.write(`\nNext steps, inside this repository:\n`);
872
+ result.nextSteps.forEach((step, index) => {
873
+ process.stdout.write(` ${String(index + 1)}. ${step}\n`);
874
+ });
875
+ return 0;
876
+ }
877
+ catch (error) {
878
+ if (error instanceof ScaffoldInputError) {
879
+ process.stderr.write(`endora: ${error.message}\n`);
880
+ return 1;
881
+ }
882
+ if (error instanceof ScaffoldHostError) {
883
+ process.stderr.write(`endora: ${error.message}\n`);
884
+ return 2;
885
+ }
886
+ throw error;
887
+ }
888
+ }
889
+ /**
890
+ * Is this module the program the process was started to run?
891
+ *
892
+ * The comparison is between **realpaths**, and that is the whole content of the
893
+ * function. A package manager does not invoke `dist/bin/endora.js` directly: it
894
+ * links the `bin`, so `process.argv[1]` names the link — pnpm's shim execs
895
+ * `node "$basedir/../@endora-commerce/cli/dist/bin/endora.js"`, a path running
896
+ * through the `node_modules/@endora-commerce/cli` symlink into the
897
+ * content-addressed store — while Node's ESM loader resolves a module URL to
898
+ * its real location before evaluating it, so `import.meta.url` is the store
899
+ * path. Comparing the two as written is therefore false for **every** consumer
900
+ * who installed this package and true only in the checkout that developed it,
901
+ * where nothing is linked. Measured on a `pnpm pack`ed tarball installed into a
902
+ * scratch directory: `endora --help` printed nothing at all and exited 0.
903
+ *
904
+ * Both failure directions are worth naming. Answering *no* wrongly is the
905
+ * silence above. Answering *yes* wrongly would run the program on `import
906
+ * { main }`, which is what the `./` export exists for, so the negative case is
907
+ * asserted as well.
908
+ *
909
+ * An `argv[1]` naming nothing on disk answers `false` rather than throwing:
910
+ * `realpathSync` raises `ENOENT`, and an uncaught one here would turn a wrong
911
+ * guess about the invocation into a crash before any command is dispatched.
912
+ */
913
+ export function isDirectEntry(entry, moduleUrl) {
914
+ if (entry === undefined)
915
+ return false;
916
+ try {
917
+ return realpathSync(entry) === realpathSync(fileURLToPath(moduleUrl));
918
+ }
919
+ catch {
920
+ return false;
921
+ }
922
+ }
923
+ if (isDirectEntry(process.argv[1], import.meta.url)) {
924
+ process.exitCode = await main(process.argv.slice(2), process.cwd());
925
+ }
926
+ //# sourceMappingURL=endora.js.map