@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.
- package/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/bin/endora.d.ts +28 -0
- package/dist/bin/endora.d.ts.map +1 -0
- package/dist/bin/endora.js +926 -0
- package/dist/bin/endora.js.map +1 -0
- package/dist/check/estate.d.ts +189 -0
- package/dist/check/estate.d.ts.map +1 -0
- package/dist/check/estate.js +1037 -0
- package/dist/check/estate.js.map +1 -0
- package/dist/check/hosts.d.ts +25 -0
- package/dist/check/hosts.d.ts.map +1 -0
- package/dist/check/hosts.js +1347 -0
- package/dist/check/hosts.js.map +1 -0
- package/dist/check/index.d.ts +60 -0
- package/dist/check/index.d.ts.map +1 -0
- package/dist/check/index.js +111 -0
- package/dist/check/index.js.map +1 -0
- package/dist/check/layout.d.ts +136 -0
- package/dist/check/layout.d.ts.map +1 -0
- package/dist/check/layout.js +262 -0
- package/dist/check/layout.js.map +1 -0
- package/dist/check/ledger.d.ts +98 -0
- package/dist/check/ledger.d.ts.map +1 -0
- package/dist/check/ledger.js +173 -0
- package/dist/check/ledger.js.map +1 -0
- package/dist/check/peer-owners.d.ts +116 -0
- package/dist/check/peer-owners.d.ts.map +1 -0
- package/dist/check/peer-owners.js +225 -0
- package/dist/check/peer-owners.js.map +1 -0
- package/dist/check/report.d.ts +33 -0
- package/dist/check/report.d.ts.map +1 -0
- package/dist/check/report.js +107 -0
- package/dist/check/report.js.map +1 -0
- package/dist/check/run.d.ts +147 -0
- package/dist/check/run.d.ts.map +1 -0
- package/dist/check/run.js +111 -0
- package/dist/check/run.js.map +1 -0
- package/dist/checks.d.ts +17 -0
- package/dist/checks.d.ts.map +1 -0
- package/dist/checks.js +17 -0
- package/dist/checks.js.map +1 -0
- package/dist/dev/index.d.ts +83 -0
- package/dist/dev/index.d.ts.map +1 -0
- package/dist/dev/index.js +298 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/generate/divergence.d.ts +38 -0
- package/dist/generate/divergence.d.ts.map +1 -0
- package/dist/generate/divergence.js +237 -0
- package/dist/generate/divergence.js.map +1 -0
- package/dist/generate/index.d.ts +90 -0
- package/dist/generate/index.d.ts.map +1 -0
- package/dist/generate/index.js +369 -0
- package/dist/generate/index.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/inputs/declaration.d.ts +18 -0
- package/dist/inputs/declaration.d.ts.map +1 -0
- package/dist/inputs/declaration.js +64 -0
- package/dist/inputs/declaration.js.map +1 -0
- package/dist/inputs/env-file.d.ts +73 -0
- package/dist/inputs/env-file.d.ts.map +1 -0
- package/dist/inputs/env-file.js +134 -0
- package/dist/inputs/env-file.js.map +1 -0
- package/dist/inputs/prompt.d.ts +21 -0
- package/dist/inputs/prompt.d.ts.map +1 -0
- package/dist/inputs/prompt.js +59 -0
- package/dist/inputs/prompt.js.map +1 -0
- package/dist/inputs/resolve.d.ts +163 -0
- package/dist/inputs/resolve.d.ts.map +1 -0
- package/dist/inputs/resolve.js +290 -0
- package/dist/inputs/resolve.js.map +1 -0
- package/dist/install/host.d.ts +27 -0
- package/dist/install/host.d.ts.map +1 -0
- package/dist/install/host.js +90 -0
- package/dist/install/host.js.map +1 -0
- package/dist/install/index.d.ts +173 -0
- package/dist/install/index.d.ts.map +1 -0
- package/dist/install/index.js +793 -0
- package/dist/install/index.js.map +1 -0
- package/dist/install/wizard.d.ts +144 -0
- package/dist/install/wizard.d.ts.map +1 -0
- package/dist/install/wizard.js +362 -0
- package/dist/install/wizard.js.map +1 -0
- package/dist/lib/admin-artefacts.d.ts +70 -0
- package/dist/lib/admin-artefacts.d.ts.map +1 -0
- package/dist/lib/admin-artefacts.js +354 -0
- package/dist/lib/admin-artefacts.js.map +1 -0
- package/dist/lib/admin-surfaces.d.ts +298 -0
- package/dist/lib/admin-surfaces.d.ts.map +1 -0
- package/dist/lib/admin-surfaces.js +669 -0
- package/dist/lib/admin-surfaces.js.map +1 -0
- package/dist/lib/delegated-composer.d.ts +85 -0
- package/dist/lib/delegated-composer.d.ts.map +1 -0
- package/dist/lib/delegated-composer.js +241 -0
- package/dist/lib/delegated-composer.js.map +1 -0
- package/dist/lib/divergence-artefacts.d.ts +305 -0
- package/dist/lib/divergence-artefacts.d.ts.map +1 -0
- package/dist/lib/divergence-artefacts.js +828 -0
- package/dist/lib/divergence-artefacts.js.map +1 -0
- package/dist/lib/divergence.d.ts +337 -0
- package/dist/lib/divergence.d.ts.map +1 -0
- package/dist/lib/divergence.js +1005 -0
- package/dist/lib/divergence.js.map +1 -0
- package/dist/lib/docs-artefacts.d.ts +395 -0
- package/dist/lib/docs-artefacts.d.ts.map +1 -0
- package/dist/lib/docs-artefacts.js +781 -0
- package/dist/lib/docs-artefacts.js.map +1 -0
- package/dist/lib/emitted-exports.d.ts +21 -0
- package/dist/lib/emitted-exports.d.ts.map +1 -0
- package/dist/lib/emitted-exports.js +96 -0
- package/dist/lib/emitted-exports.js.map +1 -0
- package/dist/lib/emitted-freshness.d.ts +112 -0
- package/dist/lib/emitted-freshness.d.ts.map +1 -0
- package/dist/lib/emitted-freshness.js +288 -0
- package/dist/lib/emitted-freshness.js.map +1 -0
- package/dist/lib/entity-index-artefact.d.ts +95 -0
- package/dist/lib/entity-index-artefact.d.ts.map +1 -0
- package/dist/lib/entity-index-artefact.js +210 -0
- package/dist/lib/entity-index-artefact.js.map +1 -0
- package/dist/lib/instance-build-inputs.d.ts +108 -0
- package/dist/lib/instance-build-inputs.d.ts.map +1 -0
- package/dist/lib/instance-build-inputs.js +165 -0
- package/dist/lib/instance-build-inputs.js.map +1 -0
- package/dist/lib/module-docs.d.ts +473 -0
- package/dist/lib/module-docs.d.ts.map +1 -0
- package/dist/lib/module-docs.js +711 -0
- package/dist/lib/module-docs.js.map +1 -0
- package/dist/lib/module-package-subpaths.d.ts +56 -0
- package/dist/lib/module-package-subpaths.d.ts.map +1 -0
- package/dist/lib/module-package-subpaths.js +223 -0
- package/dist/lib/module-package-subpaths.js.map +1 -0
- package/dist/lib/module-packages.d.ts +200 -0
- package/dist/lib/module-packages.d.ts.map +1 -0
- package/dist/lib/module-packages.js +580 -0
- package/dist/lib/module-packages.js.map +1 -0
- package/dist/lib/module-population.d.ts +129 -0
- package/dist/lib/module-population.d.ts.map +1 -0
- package/dist/lib/module-population.js +172 -0
- package/dist/lib/module-population.js.map +1 -0
- package/dist/lib/module-roots.d.ts +337 -0
- package/dist/lib/module-roots.d.ts.map +1 -0
- package/dist/lib/module-roots.js +586 -0
- package/dist/lib/module-roots.js.map +1 -0
- package/dist/lib/nested-checkouts.d.ts +33 -0
- package/dist/lib/nested-checkouts.d.ts.map +1 -0
- package/dist/lib/nested-checkouts.js +160 -0
- package/dist/lib/nested-checkouts.js.map +1 -0
- package/dist/lib/platform-root.d.ts +43 -0
- package/dist/lib/platform-root.d.ts.map +1 -0
- package/dist/lib/platform-root.js +134 -0
- package/dist/lib/platform-root.js.map +1 -0
- package/dist/lib/platform-surface.d.ts +235 -0
- package/dist/lib/platform-surface.d.ts.map +1 -0
- package/dist/lib/platform-surface.js +393 -0
- package/dist/lib/platform-surface.js.map +1 -0
- package/dist/lib/port-registrations.d.ts +223 -0
- package/dist/lib/port-registrations.d.ts.map +1 -0
- package/dist/lib/port-registrations.js +532 -0
- package/dist/lib/port-registrations.js.map +1 -0
- package/dist/lib/read-size.d.ts +154 -0
- package/dist/lib/read-size.d.ts.map +1 -0
- package/dist/lib/read-size.js +182 -0
- package/dist/lib/read-size.js.map +1 -0
- package/dist/lib/registration-owners.d.ts +79 -0
- package/dist/lib/registration-owners.d.ts.map +1 -0
- package/dist/lib/registration-owners.js +77 -0
- package/dist/lib/registration-owners.js.map +1 -0
- package/dist/lib/release-index.d.ts +53 -0
- package/dist/lib/release-index.d.ts.map +1 -0
- package/dist/lib/release-index.js +162 -0
- package/dist/lib/release-index.js.map +1 -0
- package/dist/lib/repeating-timers.d.ts +79 -0
- package/dist/lib/repeating-timers.d.ts.map +1 -0
- package/dist/lib/repeating-timers.js +189 -0
- package/dist/lib/repeating-timers.js.map +1 -0
- package/dist/lib/source-text.d.ts +34 -0
- package/dist/lib/source-text.d.ts.map +1 -0
- package/dist/lib/source-text.js +80 -0
- package/dist/lib/source-text.js.map +1 -0
- package/dist/lib/specifiers.d.ts +20 -0
- package/dist/lib/specifiers.d.ts.map +1 -0
- package/dist/lib/specifiers.js +130 -0
- package/dist/lib/specifiers.js.map +1 -0
- package/dist/lib/sql-tables.d.ts +166 -0
- package/dist/lib/sql-tables.d.ts.map +1 -0
- package/dist/lib/sql-tables.js +464 -0
- package/dist/lib/sql-tables.js.map +1 -0
- package/dist/lib/switchable-modules.d.ts +54 -0
- package/dist/lib/switchable-modules.d.ts.map +1 -0
- package/dist/lib/switchable-modules.js +104 -0
- package/dist/lib/switchable-modules.js.map +1 -0
- package/dist/lib/tailwind-sources.d.ts +136 -0
- package/dist/lib/tailwind-sources.d.ts.map +1 -0
- package/dist/lib/tailwind-sources.js +307 -0
- package/dist/lib/tailwind-sources.js.map +1 -0
- package/dist/lib/ui-layer.d.ts +54 -0
- package/dist/lib/ui-layer.d.ts.map +1 -0
- package/dist/lib/ui-layer.js +57 -0
- package/dist/lib/ui-layer.js.map +1 -0
- package/dist/lib/workspace-packages.d.ts +186 -0
- package/dist/lib/workspace-packages.d.ts.map +1 -0
- package/dist/lib/workspace-packages.js +351 -0
- package/dist/lib/workspace-packages.js.map +1 -0
- package/dist/new-instance/deploy.d.ts +211 -0
- package/dist/new-instance/deploy.d.ts.map +1 -0
- package/dist/new-instance/deploy.js +1381 -0
- package/dist/new-instance/deploy.js.map +1 -0
- package/dist/new-instance/docs-toolchain.d.ts +66 -0
- package/dist/new-instance/docs-toolchain.d.ts.map +1 -0
- package/dist/new-instance/docs-toolchain.js +69 -0
- package/dist/new-instance/docs-toolchain.js.map +1 -0
- package/dist/new-instance/host.d.ts +124 -0
- package/dist/new-instance/host.d.ts.map +1 -0
- package/dist/new-instance/host.js +276 -0
- package/dist/new-instance/host.js.map +1 -0
- package/dist/new-instance/index.d.ts +118 -0
- package/dist/new-instance/index.d.ts.map +1 -0
- package/dist/new-instance/index.js +567 -0
- package/dist/new-instance/index.js.map +1 -0
- package/dist/new-instance/modules.d.ts +188 -0
- package/dist/new-instance/modules.d.ts.map +1 -0
- package/dist/new-instance/modules.js +392 -0
- package/dist/new-instance/modules.js.map +1 -0
- package/dist/new-instance/template.d.ts +505 -0
- package/dist/new-instance/template.d.ts.map +1 -0
- package/dist/new-instance/template.js +1886 -0
- package/dist/new-instance/template.js.map +1 -0
- package/dist/new-module/emit.d.ts +67 -0
- package/dist/new-module/emit.d.ts.map +1 -0
- package/dist/new-module/emit.js +1393 -0
- package/dist/new-module/emit.js.map +1 -0
- package/dist/new-module/host.d.ts +67 -0
- package/dist/new-module/host.d.ts.map +1 -0
- package/dist/new-module/host.js +224 -0
- package/dist/new-module/host.js.map +1 -0
- package/dist/new-module/index.d.ts +32 -0
- package/dist/new-module/index.d.ts.map +1 -0
- package/dist/new-module/index.js +193 -0
- package/dist/new-module/index.js.map +1 -0
- package/dist/new-module/spec.d.ts +188 -0
- package/dist/new-module/spec.d.ts.map +1 -0
- package/dist/new-module/spec.js +404 -0
- package/dist/new-module/spec.js.map +1 -0
- package/dist/new-module/text.d.ts +15 -0
- package/dist/new-module/text.d.ts.map +1 -0
- package/dist/new-module/text.js +22 -0
- package/dist/new-module/text.js.map +1 -0
- package/dist/new-storefront/dockerfile.d.ts +20 -0
- package/dist/new-storefront/dockerfile.d.ts.map +1 -0
- package/dist/new-storefront/dockerfile.js +131 -0
- package/dist/new-storefront/dockerfile.js.map +1 -0
- package/dist/new-storefront/gitignore.d.ts +23 -0
- package/dist/new-storefront/gitignore.d.ts.map +1 -0
- package/dist/new-storefront/gitignore.js +39 -0
- package/dist/new-storefront/gitignore.js.map +1 -0
- package/dist/new-storefront/index.d.ts +93 -0
- package/dist/new-storefront/index.d.ts.map +1 -0
- package/dist/new-storefront/index.js +329 -0
- package/dist/new-storefront/index.js.map +1 -0
- package/dist/new-storefront/npmrc.d.ts +98 -0
- package/dist/new-storefront/npmrc.d.ts.map +1 -0
- package/dist/new-storefront/npmrc.js +217 -0
- package/dist/new-storefront/npmrc.js.map +1 -0
- package/dist/new-storefront/reference.d.ts +189 -0
- package/dist/new-storefront/reference.d.ts.map +1 -0
- package/dist/new-storefront/reference.js +430 -0
- package/dist/new-storefront/reference.js.map +1 -0
- package/dist/new-storefront/rewrite.d.ts +171 -0
- package/dist/new-storefront/rewrite.d.ts.map +1 -0
- package/dist/new-storefront/rewrite.js +701 -0
- package/dist/new-storefront/rewrite.js.map +1 -0
- package/dist/release-index.json +284 -0
- package/dist/rules/action-route-permissions.d.ts +141 -0
- package/dist/rules/action-route-permissions.d.ts.map +1 -0
- package/dist/rules/action-route-permissions.js +556 -0
- package/dist/rules/action-route-permissions.js.map +1 -0
- package/dist/rules/bundle-pairing.d.ts +74 -0
- package/dist/rules/bundle-pairing.d.ts.map +1 -0
- package/dist/rules/bundle-pairing.js +281 -0
- package/dist/rules/bundle-pairing.js.map +1 -0
- package/dist/rules/channel-resolution.d.ts +17 -0
- package/dist/rules/channel-resolution.d.ts.map +1 -0
- package/dist/rules/channel-resolution.js +382 -0
- package/dist/rules/channel-resolution.js.map +1 -0
- package/dist/rules/command-coverage.d.ts +210 -0
- package/dist/rules/command-coverage.d.ts.map +1 -0
- package/dist/rules/command-coverage.js +714 -0
- package/dist/rules/command-coverage.js.map +1 -0
- package/dist/rules/container-imports.d.ts +60 -0
- package/dist/rules/container-imports.d.ts.map +1 -0
- package/dist/rules/container-imports.js +158 -0
- package/dist/rules/container-imports.js.map +1 -0
- package/dist/rules/default-language-prose.d.ts +212 -0
- package/dist/rules/default-language-prose.d.ts.map +1 -0
- package/dist/rules/default-language-prose.js +710 -0
- package/dist/rules/default-language-prose.js.map +1 -0
- package/dist/rules/diacritic-folds.d.ts +238 -0
- package/dist/rules/diacritic-folds.d.ts.map +1 -0
- package/dist/rules/diacritic-folds.js +681 -0
- package/dist/rules/diacritic-folds.js.map +1 -0
- package/dist/rules/entity-tenant-classification.d.ts +171 -0
- package/dist/rules/entity-tenant-classification.d.ts.map +1 -0
- package/dist/rules/entity-tenant-classification.js +323 -0
- package/dist/rules/entity-tenant-classification.js.map +1 -0
- package/dist/rules/entry-presence.d.ts +142 -0
- package/dist/rules/entry-presence.d.ts.map +1 -0
- package/dist/rules/entry-presence.js +339 -0
- package/dist/rules/entry-presence.js.map +1 -0
- package/dist/rules/entry-scope.d.ts +91 -0
- package/dist/rules/entry-scope.d.ts.map +1 -0
- package/dist/rules/entry-scope.js +404 -0
- package/dist/rules/entry-scope.js.map +1 -0
- package/dist/rules/env-inputs.d.ts +222 -0
- package/dist/rules/env-inputs.d.ts.map +1 -0
- package/dist/rules/env-inputs.js +951 -0
- package/dist/rules/env-inputs.js.map +1 -0
- package/dist/rules/kernel-boundary.d.ts +37 -0
- package/dist/rules/kernel-boundary.d.ts.map +1 -0
- package/dist/rules/kernel-boundary.js +195 -0
- package/dist/rules/kernel-boundary.js.map +1 -0
- package/dist/rules/nul-bytes.d.ts +233 -0
- package/dist/rules/nul-bytes.d.ts.map +1 -0
- package/dist/rules/nul-bytes.js +332 -0
- package/dist/rules/nul-bytes.js.map +1 -0
- package/dist/rules/platform-surface.d.ts +479 -0
- package/dist/rules/platform-surface.d.ts.map +1 -0
- package/dist/rules/platform-surface.js +749 -0
- package/dist/rules/platform-surface.js.map +1 -0
- package/dist/rules/port-catches.d.ts +225 -0
- package/dist/rules/port-catches.d.ts.map +1 -0
- package/dist/rules/port-catches.js +1374 -0
- package/dist/rules/port-catches.js.map +1 -0
- package/dist/rules/port-shape.d.ts +213 -0
- package/dist/rules/port-shape.d.ts.map +1 -0
- package/dist/rules/port-shape.js +670 -0
- package/dist/rules/port-shape.js.map +1 -0
- package/dist/rules/queue-names.d.ts +108 -0
- package/dist/rules/queue-names.d.ts.map +1 -0
- package/dist/rules/queue-names.js +395 -0
- package/dist/rules/queue-names.js.map +1 -0
- package/dist/rules/singleton-identity.d.ts +205 -0
- package/dist/rules/singleton-identity.d.ts.map +1 -0
- package/dist/rules/singleton-identity.js +830 -0
- package/dist/rules/singleton-identity.js.map +1 -0
- package/dist/rules/subscribe-seam.d.ts +121 -0
- package/dist/rules/subscribe-seam.d.ts.map +1 -0
- package/dist/rules/subscribe-seam.js +594 -0
- package/dist/rules/subscribe-seam.js.map +1 -0
- package/dist/rules/transaction-context.d.ts +40 -0
- package/dist/rules/transaction-context.d.ts.map +1 -0
- package/dist/rules/transaction-context.js +294 -0
- package/dist/rules/transaction-context.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The documentation artefacts a tree is built from — this repository's site and
|
|
3
|
+
* a client instance's alike (`contracts/instance-repository.md` R3.2, R3.5;
|
|
4
|
+
* `contracts/instance-tree.md` §2.6; feature 100 / roadmap F12).
|
|
5
|
+
*
|
|
6
|
+
* ## Why these renderers live in the package
|
|
7
|
+
*
|
|
8
|
+
* They were `backend/scripts/generate-composer.ts`', which no client can reach.
|
|
9
|
+
* `instance-tree.md` §2.6 lists the documentation registry among the three
|
|
10
|
+
* artefacts an instance generates, and until this move there was no
|
|
11
|
+
* implementation on the other side of that sentence: a scaffolded instance could
|
|
12
|
+
* install thirty module packages, each shipping its own `docs/` layer, and had
|
|
13
|
+
* no way to render a navigation over them. That is `admin-artefacts.ts`' story
|
|
14
|
+
* one artefact family over, and R3.5's answer is the same one — *"one generator,
|
|
15
|
+
* one derivation … never a second implementation"*, with the **population** as
|
|
16
|
+
* the parameter.
|
|
17
|
+
*
|
|
18
|
+
* ## The population enters as {@link DocsModule}, never as a walk
|
|
19
|
+
*
|
|
20
|
+
* `composer:generate` builds it from the manifest index walk (workspace members
|
|
21
|
+
* plus the modules the host itself owns); `endora generate` builds it from the
|
|
22
|
+
* packages an instance installed. Neither walk is in here, because the two
|
|
23
|
+
* populations are genuinely different questions and only the *rendering* is one
|
|
24
|
+
* program. What a module owes this file is four facts — its id, where its pages
|
|
25
|
+
* are, whether it has decided it has none, and which file its manifest is — and
|
|
26
|
+
* `DiscoveredManifest` is structurally one of these, so the workspace host hands
|
|
27
|
+
* its own record over unchanged.
|
|
28
|
+
*
|
|
29
|
+
* ## What it cannot see, stated here rather than discovered later
|
|
30
|
+
*
|
|
31
|
+
* Everything `lib/module-docs.ts`' header already records — front matter is a
|
|
32
|
+
* leading `---` block, a doc id is a file path, and nothing here says whether a
|
|
33
|
+
* page is good, current or complete. This file adds one of its own: the
|
|
34
|
+
* reference page is rendered from the manifest it is handed and from nothing
|
|
35
|
+
* else, so a module whose manifest is a compiled artefact is described by that
|
|
36
|
+
* artefact, which is the previous build if nobody rebuilt it (D-164).
|
|
37
|
+
*/
|
|
38
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
39
|
+
import { dirname, join, relative } from 'node:path';
|
|
40
|
+
import { pathToFileURL } from 'node:url';
|
|
41
|
+
import { attributeDocs, collectDocPages, collectModuleDocPages, comparePages, copyTargetOf, categoryPositionOf, docsDeclarationIn, duplicateDocIds, isDirectory, labelOf, slugForModule, DOCS_SIDEBAR_ARTEFACT, MODULE_MAP_ARTEFACT, MODULE_REFERENCE_CATEGORY, MODULES_CATEGORY, PAGE_EXTENSIONS, } from './module-docs.js';
|
|
42
|
+
import { ModulePackageError, publishedManifestEntryOf, } from './module-packages.js';
|
|
43
|
+
/** The label the module map's first column uses when a page carries none. */
|
|
44
|
+
/**
|
|
45
|
+
* The "do not edit" headers this repository's own generator writes.
|
|
46
|
+
*
|
|
47
|
+
* Parameters rather than literals, on `admin-artefacts.ts`' precedent: the two
|
|
48
|
+
* hosts tell a reader different things to run — `composer:generate` here,
|
|
49
|
+
* `endora generate` in an instance — and an artefact that named the wrong one
|
|
50
|
+
* would send a client to a script their tree does not have. The default is this
|
|
51
|
+
* repository's, so the committed artefacts are byte-identical across the move
|
|
52
|
+
* and a host that means the other one says so.
|
|
53
|
+
*/
|
|
54
|
+
export const COMPOSER_DOCS_HEADER = `// AUTO-GENERATED by scripts/generate-composer.ts — DO NOT EDIT.\n` +
|
|
55
|
+
`// Run \`pnpm --filter backend run composer:generate\` (or rebuild the backend)\n` +
|
|
56
|
+
`// to refresh. Editing this file by hand is undone by the next build, and\n` +
|
|
57
|
+
`// \`pnpm --filter backend run overlay:check\` fails on the drift.\n`;
|
|
58
|
+
/**
|
|
59
|
+
* The same, for a markdown page, whose comment syntax is HTML's.
|
|
60
|
+
*
|
|
61
|
+
* **It is emitted *below* the front matter, and that placement is load-bearing
|
|
62
|
+
* rather than aesthetic** (feature 133, FR-012). Front matter is front matter
|
|
63
|
+
* only at byte 0: a `---` fence that opens under a four-line banner is never
|
|
64
|
+
* parsed, so `title`, `sidebar_label` and `description` are inert — and the
|
|
65
|
+
* banner is then also the page's first content node, which is not a `# `
|
|
66
|
+
* heading, so the `contentTitle` route to a title is closed too. Every
|
|
67
|
+
* generated page shipped titled with its own doc id (`catalog | B2B Platform`)
|
|
68
|
+
* for as long as this was emitted first. Below the fence the comment is still a
|
|
69
|
+
* plain "do not edit" banner to a reader of the source and is invisible in the
|
|
70
|
+
* rendered page, which is all it was ever for.
|
|
71
|
+
*/
|
|
72
|
+
export const COMPOSER_DOCS_PAGE_HEADER = `<!-- AUTO-GENERATED by scripts/generate-composer.ts — DO NOT EDIT.\n` +
|
|
73
|
+
` Run \`pnpm --filter backend run composer:generate\` to refresh. Editing this\n` +
|
|
74
|
+
` file by hand is undone by the next run, and\n` +
|
|
75
|
+
` \`pnpm --filter backend run overlay:check\` fails on the drift. -->`;
|
|
76
|
+
const MAP_FALLBACK_LABEL = (moduleId) => moduleId;
|
|
77
|
+
/**
|
|
78
|
+
* Every registered module, with the documentation the site holds for it.
|
|
79
|
+
*
|
|
80
|
+
* The population is the **index's**, so a module with no page is an entry with
|
|
81
|
+
* no documentation rather than an absence — the module map is a census of the
|
|
82
|
+
* platform, not a census of what somebody happened to write.
|
|
83
|
+
*/
|
|
84
|
+
export function collectDocsRegistry(registered, attribution, packages,
|
|
85
|
+
/** Modules that declare `docs: false` — they owe no page, generated or written. */
|
|
86
|
+
declinedDocs = new Set()) {
|
|
87
|
+
const byModule = new Map(attribution.documented.map((entry) => [entry.moduleId, entry]));
|
|
88
|
+
const packageName = new Map(packages.map((pkg) => [pkg.moduleId, pkg.name]));
|
|
89
|
+
return [...registered]
|
|
90
|
+
.sort((a, b) => a.localeCompare(b))
|
|
91
|
+
.map((moduleId) => ({
|
|
92
|
+
moduleId,
|
|
93
|
+
docs: byModule.get(moduleId) ?? null,
|
|
94
|
+
shipsFrom: packageName.get(moduleId) ?? 'core',
|
|
95
|
+
referenceDocId: declinedDocs.has(moduleId)
|
|
96
|
+
? null
|
|
97
|
+
: `${MODULE_REFERENCE_CATEGORY}/${slugForModule(moduleId)}`,
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
/** A JS string literal for the emitted CommonJS fragment. */
|
|
101
|
+
function jsString(value) {
|
|
102
|
+
return `'${value.split('\\').join('\\\\').split("'").join("\\'")}'`;
|
|
103
|
+
}
|
|
104
|
+
/** Emit `label` + `key` so Docusaurus derives stable `sidebar.main.*` translation ids. */
|
|
105
|
+
function jsSidebarItemLabel(key, message) {
|
|
106
|
+
return `label: ${jsString(message)}, key: ${jsString(key)}`;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The sidebar's Modules category, as the array Docusaurus already accepts.
|
|
110
|
+
*
|
|
111
|
+
* `.js` rather than `.ts` because `sidebars.js` is `.js`, `sidebarPath` is
|
|
112
|
+
* `require`d by Docusaurus, and `docs/tsconfig.json` extends
|
|
113
|
+
* `@docusaurus/tsconfig`, which sets no `allowJs` — so a `.ts` fragment would be
|
|
114
|
+
* read by the site and by no type-checker, which is worse than either.
|
|
115
|
+
*
|
|
116
|
+
* Ordering is **flat and alphabetical by label**, with a page's own
|
|
117
|
+
* `sidebar_position` overriding (research D-5). Today's hand-written list
|
|
118
|
+
* clusters the payment and delivery vendors out of alphabetical order, and that
|
|
119
|
+
* clustering is not derivable — `tpay` does not declare `payments` in its
|
|
120
|
+
* manifest dependencies at all — so the flat list is taken and the loss is
|
|
121
|
+
* stated rather than papered over with a front-matter field this repository
|
|
122
|
+
* would have invented. `spec.md` Q1 is the owner's question about it.
|
|
123
|
+
*/
|
|
124
|
+
export function emitDocsSidebar(entries, header = COMPOSER_DOCS_HEADER) {
|
|
125
|
+
const items = entries
|
|
126
|
+
// A module with neither prose nor a reference page contributes nothing: it
|
|
127
|
+
// declared `docs: false`, and an entry for it would be a navigation line
|
|
128
|
+
// naming a page that is deliberately not there.
|
|
129
|
+
.filter((entry) => entry.docs !== null || entry.referenceDocId !== null)
|
|
130
|
+
.map((entry) => ({
|
|
131
|
+
label: entry.docs === null
|
|
132
|
+
? MAP_FALLBACK_LABEL(entry.moduleId)
|
|
133
|
+
: labelOf(entry.docs.entry, MAP_FALLBACK_LABEL(entry.moduleId)),
|
|
134
|
+
position: entry.docs === null ? null : categoryPositionOf(entry.docs.entry),
|
|
135
|
+
entry,
|
|
136
|
+
}))
|
|
137
|
+
.sort(comparePages)
|
|
138
|
+
.map(({ label, entry }) => {
|
|
139
|
+
const { docs, referenceDocId } = entry;
|
|
140
|
+
// A module nobody has written about yet still has a reference page, and
|
|
141
|
+
// this is where a reader reaches it. Without the entry the page would be
|
|
142
|
+
// findable only by guessing a URL — `spec.md` § 0.2's defect, arriving
|
|
143
|
+
// through the artefact meant to answer it.
|
|
144
|
+
if (docs === null) {
|
|
145
|
+
return ` { type: 'doc', id: ${jsString(referenceDocId ?? '')}, ${jsSidebarItemLabel(entry.moduleId, label)} },`;
|
|
146
|
+
}
|
|
147
|
+
const items = [
|
|
148
|
+
...docs.children.map((child) => child.docId),
|
|
149
|
+
...(referenceDocId === null ? [] : [referenceDocId]),
|
|
150
|
+
];
|
|
151
|
+
if (items.length === 0) {
|
|
152
|
+
return ` { type: 'doc', id: ${jsString(docs.entry.docId)}, ${jsSidebarItemLabel(entry.moduleId, label)} },`;
|
|
153
|
+
}
|
|
154
|
+
// The reference page goes **last**, after whatever sub-pages a module
|
|
155
|
+
// wrote: a generated table is what a reader falls back to, not what they
|
|
156
|
+
// are shown first.
|
|
157
|
+
const children = items.map((docId) => ` ${jsString(docId)},`).join('\n');
|
|
158
|
+
return (` {\n` +
|
|
159
|
+
` type: 'category',\n` +
|
|
160
|
+
` ${jsSidebarItemLabel(entry.moduleId, label)},\n` +
|
|
161
|
+
` link: { type: 'doc', id: ${jsString(docs.entry.docId)} },\n` +
|
|
162
|
+
` items: [\n${children}\n ],\n` +
|
|
163
|
+
` },`);
|
|
164
|
+
})
|
|
165
|
+
.join('\n');
|
|
166
|
+
// The generated map is navigation for a generated page, so it belongs in the
|
|
167
|
+
// generated fragment: putting it in `sidebars.js` would make the category's
|
|
168
|
+
// item list a hand-edited file again, one entry short of the thing this
|
|
169
|
+
// artefact exists to remove.
|
|
170
|
+
const map = ` { type: 'doc', id: ${jsString(`${MODULES_CATEGORY}/${MODULE_MAP_ARTEFACT.replace(/\.mdx?$/, '')}`)}, ${jsSidebarItemLabel('module-map', 'Module map')} },`;
|
|
171
|
+
return `${header}//
|
|
172
|
+
// The Modules category of the documentation sidebar (feature 100 / roadmap F12,
|
|
173
|
+
// \`contracts/docs-registry.md\` §1). \`sidebars.js\` requires it:
|
|
174
|
+
//
|
|
175
|
+
// items: require('./sidebars.modules.generated.js'),
|
|
176
|
+
//
|
|
177
|
+
// Every entry is derived — the module set from the generated manifest index, the
|
|
178
|
+
// page from the site's own tree, the label from the page's own Docusaurus front
|
|
179
|
+
// matter (\`sidebar_label\`, else \`title\`), the order from \`sidebar_position\` and
|
|
180
|
+
// then alphabetically by label. No field this repository invented appears here or
|
|
181
|
+
// in any page, so a third-party module author writes ordinary Docusaurus
|
|
182
|
+
// markdown and learns nothing from us.
|
|
183
|
+
//
|
|
184
|
+
// It exists because the hand-written list this replaces was edited by 12 of the
|
|
185
|
+
// 12 most recently added modules and forgotten by seven of them: \`ksef\`,
|
|
186
|
+
// \`newsletter\`, \`pwa\`, \`returns\`, \`shipments\`, \`transactional_emails\` and
|
|
187
|
+
// \`google_analytics\` each had a written page a reader could only reach by
|
|
188
|
+
// guessing a URL, and nothing in the repository could see it.
|
|
189
|
+
|
|
190
|
+
/** @type {import('@docusaurus/plugin-content-docs').SidebarItemConfig[]} */
|
|
191
|
+
const modules = [
|
|
192
|
+
${map}
|
|
193
|
+
${items}
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
module.exports = modules;
|
|
197
|
+
`;
|
|
198
|
+
}
|
|
199
|
+
/** Escape a cell so a capability sentence carrying a pipe cannot break the table. */
|
|
200
|
+
function markdownCell(value) {
|
|
201
|
+
return value.split('|').join('\\|').split('\n').join(' ').trim();
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* A front-matter value YAML will read back as the string it was given.
|
|
205
|
+
*
|
|
206
|
+
* Plain scalars are left plain — a quoted `title` would rewrite 152 tracked
|
|
207
|
+
* pages for nothing — and anything YAML would choke on or reinterpret is
|
|
208
|
+
* double-quoted. The case that shipped: `description` is a sentence reading
|
|
209
|
+
* *"…manifest declares: permissions, …"*, and `: ` inside a plain scalar is an
|
|
210
|
+
* incomplete mapping pair, so `docusaurus build` died in `gray-matter` on the
|
|
211
|
+
* first generated page it parsed. It parsed none of them before feature 133
|
|
212
|
+
* moved the front matter to byte 0, which is why nothing caught it earlier.
|
|
213
|
+
*
|
|
214
|
+
* The `#` rule is the mirror image of the colon's and was written backwards
|
|
215
|
+
* once: a comment starts at a `#` **preceded** by whitespace or at the start of
|
|
216
|
+
* the value, whatever follows it, so *"Sync catalog #1 with PIM"* truncates
|
|
217
|
+
* silently while `C#` and `a#b` are perfectly good plain scalars. Testing for a
|
|
218
|
+
* `#` followed by whitespace detected neither case. Exported for
|
|
219
|
+
* `test/docs-front-matter-scalar.test.ts`, which is the only reason this helper
|
|
220
|
+
* is not file-local.
|
|
221
|
+
*/
|
|
222
|
+
export function yamlScalar(value) {
|
|
223
|
+
const plain = !/(^|\s)#|:(\s|$)|^[\s>|&*!%@`'"[{-]|[:\s]$/.test(value);
|
|
224
|
+
return plain ? value : `"${value.split('\\').join('\\\\').split('"').join('\\"')}"`;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* The module map — one row per **registered** module, never per page.
|
|
228
|
+
*
|
|
229
|
+
* A module the index registers and no page documents gets a row saying so,
|
|
230
|
+
* rather than being silently absent: the map is a census of the platform, and an
|
|
231
|
+
* absence is the defect this feature exists to end. 23 registered modules had no
|
|
232
|
+
* row when this landed.
|
|
233
|
+
*
|
|
234
|
+
* The old table's third column, `Owns HTTP surface?`, is **dropped**. It was
|
|
235
|
+
* hand-written, wrong in several rows, and is not cheaply derivable — a module's
|
|
236
|
+
* routes are registered through `ctx.routes` at composition and declared in no
|
|
237
|
+
* manifest. A column that cannot be derived is a column that goes stale, which
|
|
238
|
+
* is the defect this artefact replaces.
|
|
239
|
+
*/
|
|
240
|
+
export function emitModuleMap(entries, header = COMPOSER_DOCS_PAGE_HEADER) {
|
|
241
|
+
const rows = entries
|
|
242
|
+
.map((entry) => {
|
|
243
|
+
if (entry.docs === null) {
|
|
244
|
+
return (`| \`${entry.moduleId}\` | _no page yet_ | ${markdownCell(entry.shipsFrom)} |`);
|
|
245
|
+
}
|
|
246
|
+
const label = labelOf(entry.docs.entry, MAP_FALLBACK_LABEL(entry.moduleId));
|
|
247
|
+
const href = `./${entry.docs.entry.relativePath}`;
|
|
248
|
+
const capability = entry.docs.entry.frontMatter.description ?? '_no description yet_';
|
|
249
|
+
return `| [${markdownCell(label)}](${href}) | ${markdownCell(capability)} | ${markdownCell(entry.shipsFrom)} |`;
|
|
250
|
+
})
|
|
251
|
+
.join('\n');
|
|
252
|
+
return `---
|
|
253
|
+
title: Module map
|
|
254
|
+
sidebar_label: Module map
|
|
255
|
+
description: Every module this platform composes, with the capability it owns and the package that ships it.
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
${header}
|
|
259
|
+
|
|
260
|
+
# Module map
|
|
261
|
+
|
|
262
|
+
One row per module the platform registers — derived from the generated manifest
|
|
263
|
+
index, the pages on disk and each page's own \`description\` front matter. A
|
|
264
|
+
module with no page is listed with none rather than left out: this is a census
|
|
265
|
+
of the platform, not of what happens to be written.
|
|
266
|
+
|
|
267
|
+
| Module | Capability | Ships from |
|
|
268
|
+
| --- | --- | --- |
|
|
269
|
+
${rows}
|
|
270
|
+
`;
|
|
271
|
+
}
|
|
272
|
+
/** Raised when a page in the reference category belongs to no module. */
|
|
273
|
+
export class StrayReferencePageError extends ModulePackageError {
|
|
274
|
+
}
|
|
275
|
+
/** A manifest field, read defensively — the generator must not trust a shape. */
|
|
276
|
+
function arrayOf(value) {
|
|
277
|
+
return Array.isArray(value) ? value : [];
|
|
278
|
+
}
|
|
279
|
+
function stringOr(value, fallback) {
|
|
280
|
+
return typeof value === 'string' && value.length > 0 ? value : fallback;
|
|
281
|
+
}
|
|
282
|
+
function stringOrNull(value) {
|
|
283
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* One module's reference record, from its manifest and the package that ships it.
|
|
287
|
+
*
|
|
288
|
+
* The manifest is **imported**, not parsed out of its source text, for the
|
|
289
|
+
* reason `renderComposer` already imports one: a manifest is TypeScript with
|
|
290
|
+
* arrays, spreads and helper calls in it, and a text scan of that is a second
|
|
291
|
+
* reader of a declaration whose first reader is the platform. What is imported
|
|
292
|
+
* is the module's own `manifest.ts` — never the generated index, which is this
|
|
293
|
+
* generator's own previous answer.
|
|
294
|
+
*/
|
|
295
|
+
export function referenceOf(moduleId, loaded, shipsFrom, prosePage, publishedLicense = null) {
|
|
296
|
+
const manifest = loaded.manifest ?? {};
|
|
297
|
+
const activationRaw = manifest.activation;
|
|
298
|
+
const activation = activationRaw === undefined
|
|
299
|
+
? null
|
|
300
|
+
: activationRaw.nonDeactivatable === true
|
|
301
|
+
? { kind: 'locked', reason: stringOr(activationRaw.reason, 'not stated') }
|
|
302
|
+
: {
|
|
303
|
+
kind: 'control',
|
|
304
|
+
settingCode: stringOr(activationRaw.settingCode, '(unnamed)'),
|
|
305
|
+
default: activationRaw.default === true,
|
|
306
|
+
};
|
|
307
|
+
const settings = manifest.settings;
|
|
308
|
+
const i18n = manifest.i18n;
|
|
309
|
+
return {
|
|
310
|
+
moduleId,
|
|
311
|
+
slug: slugForModule(moduleId),
|
|
312
|
+
name: stringOr(manifest.name, moduleId),
|
|
313
|
+
version: stringOr(manifest.version, '0.0.0'),
|
|
314
|
+
shipsFrom,
|
|
315
|
+
license: publishedLicense,
|
|
316
|
+
activation,
|
|
317
|
+
dependencies: [...arrayOf(manifest.dependencies).map(String)].sort(),
|
|
318
|
+
acknowledgedDependencies: arrayOf(manifest.acknowledgedDependencies)
|
|
319
|
+
.map((entry) => ({
|
|
320
|
+
moduleId: stringOr(entry.moduleId, '?'),
|
|
321
|
+
port: stringOr(entry.port, '?'),
|
|
322
|
+
reason: stringOr(entry.reason, ''),
|
|
323
|
+
}))
|
|
324
|
+
.sort((a, b) => `${a.moduleId}${a.port}`.localeCompare(`${b.moduleId}${b.port}`)),
|
|
325
|
+
nonBindingDependencies: arrayOf(manifest.nonBindingDependencies)
|
|
326
|
+
.map((entry) => ({
|
|
327
|
+
moduleId: stringOr(entry.moduleId, '?'),
|
|
328
|
+
name: stringOr(entry.name, '?'),
|
|
329
|
+
kind: stringOr(entry.kind, '?'),
|
|
330
|
+
whenAbsent: stringOrNull(entry.whenAbsent),
|
|
331
|
+
}))
|
|
332
|
+
.sort((a, b) => `${a.moduleId}${a.name}`.localeCompare(`${b.moduleId}${b.name}`)),
|
|
333
|
+
permissions: arrayOf(manifest.permissions)
|
|
334
|
+
.map((entry) => ({
|
|
335
|
+
code: stringOr(entry.code, '?'),
|
|
336
|
+
label: stringOr(entry.label, ''),
|
|
337
|
+
requires: Array.isArray(entry.requires) ? entry.requires.map(String) : [],
|
|
338
|
+
}))
|
|
339
|
+
.sort((a, b) => a.code.localeCompare(b.code)),
|
|
340
|
+
actions: arrayOf(manifest.actions)
|
|
341
|
+
.map((entry) => ({
|
|
342
|
+
id: stringOr(entry.id, '?'),
|
|
343
|
+
targetRoute: stringOr(entry.targetRoute, '?'),
|
|
344
|
+
requiredPermission: stringOrNull(entry.requiredPermission),
|
|
345
|
+
}))
|
|
346
|
+
.sort((a, b) => a.id.localeCompare(b.id)),
|
|
347
|
+
settings: arrayOf(settings?.settings)
|
|
348
|
+
.map((entry) => ({
|
|
349
|
+
code: stringOr(entry.code, '?'),
|
|
350
|
+
name: stringOr(entry.name, ''),
|
|
351
|
+
valueType: stringOr(entry.valueType, '?'),
|
|
352
|
+
}))
|
|
353
|
+
.sort((a, b) => a.code.localeCompare(b.code)),
|
|
354
|
+
bundlesDir: i18n === undefined ? null : stringOr(i18n.bundlesDir, 'i18n'),
|
|
355
|
+
cliCommands: (loaded.cliCommands ?? [])
|
|
356
|
+
.map((entry) => ({
|
|
357
|
+
name: stringOr(entry.name, '?'),
|
|
358
|
+
summary: stringOr(entry.summary, ''),
|
|
359
|
+
}))
|
|
360
|
+
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
361
|
+
prosePage,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* The licence the package shipping a module is published under, read from the
|
|
366
|
+
* nearest `package.json` above its manifest — or `null` when that file is not
|
|
367
|
+
* the shipping package's (a host-owned module, `shipsFrom === 'core'`, whose
|
|
368
|
+
* nearest `package.json` is the host's) or declares none.
|
|
369
|
+
*
|
|
370
|
+
* The nearest file is the package's own in both trees this renders over: in
|
|
371
|
+
* this repository the manifest is `packages/modules/<id>/src/manifest.ts`, and
|
|
372
|
+
* in an instance it is the installed package's `dist/manifest.js`, whose
|
|
373
|
+
* `package.json` is exactly what the registry published. The name is checked
|
|
374
|
+
* rather than assumed, because a licence read off the wrong file is a public
|
|
375
|
+
* statement about the wrong package.
|
|
376
|
+
*/
|
|
377
|
+
export function publishedLicenseOf(manifestPath, shipsFrom) {
|
|
378
|
+
let dir = dirname(manifestPath);
|
|
379
|
+
for (;;) {
|
|
380
|
+
const candidate = join(dir, 'package.json');
|
|
381
|
+
if (existsSync(candidate)) {
|
|
382
|
+
const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
383
|
+
if (pkg.name !== shipsFrom)
|
|
384
|
+
return null;
|
|
385
|
+
return stringOrNull(pkg.license);
|
|
386
|
+
}
|
|
387
|
+
const parent = dirname(dir);
|
|
388
|
+
if (parent === dir)
|
|
389
|
+
return null;
|
|
390
|
+
dir = parent;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* The licence row's value. An SPDX expression is shown as declared; a
|
|
395
|
+
* `SEE LICENSE IN <file>` is shown as declared and says where the terms are —
|
|
396
|
+
* the file ships in the package. Flat on purpose: this is a statement of fact
|
|
397
|
+
* about a package, and `specs/conventions/commercial-data.md` §3 N4 holds it to
|
|
398
|
+
* *inform, never press*.
|
|
399
|
+
*/
|
|
400
|
+
function licenceCell(license) {
|
|
401
|
+
const own = license === null ? null : /^SEE LICENSE IN (.+)$/.exec(license);
|
|
402
|
+
if (own === null)
|
|
403
|
+
return code(license);
|
|
404
|
+
return `${code(license)} — the package's own terms, shipped in its ${code(own[1].trim())}`;
|
|
405
|
+
}
|
|
406
|
+
/** A markdown table, or the sentence that says there is nothing in it. */
|
|
407
|
+
function table(headings, rows) {
|
|
408
|
+
if (rows.length === 0)
|
|
409
|
+
return '_None._\n';
|
|
410
|
+
return (`| ${headings.join(' | ')} |\n` +
|
|
411
|
+
`| ${headings.map(() => '---').join(' | ')} |\n` +
|
|
412
|
+
rows.map((row) => `| ${row.map(markdownCell).join(' | ')} |`).join('\n') +
|
|
413
|
+
'\n');
|
|
414
|
+
}
|
|
415
|
+
/** A value the reader is meant to copy, or an em dash where there is none. */
|
|
416
|
+
function code(value) {
|
|
417
|
+
return value === null || value === '' ? '—' : `\`${value}\``;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* One module's reference page.
|
|
421
|
+
*
|
|
422
|
+
* Pure over the record, so a test drives every section on input this repository
|
|
423
|
+
* does not contain. Every list is sorted by the collector rather than by the
|
|
424
|
+
* walk that produced it, because `overlay:check` renders twice and compares:
|
|
425
|
+
* an ordering that came off a filesystem read would make the artefact
|
|
426
|
+
* non-deterministic in a way that only shows up on somebody else's machine.
|
|
427
|
+
*/
|
|
428
|
+
export function emitModuleReference(reference, header = COMPOSER_DOCS_PAGE_HEADER) {
|
|
429
|
+
const activation = reference.activation === null
|
|
430
|
+
? 'This module declares no activation control, so an operator cannot switch it off ' +
|
|
431
|
+
'from the Admin UI. Its presence is the platform-availability axis alone — the ' +
|
|
432
|
+
'lifecycle registry, changed by deployment tooling.\n'
|
|
433
|
+
: reference.activation.kind === 'locked'
|
|
434
|
+
? `**This module cannot be switched off.** ${reference.activation.reason}\n`
|
|
435
|
+
: `An operator switches this module on and off on **/platform/modules**. The choice ` +
|
|
436
|
+
`is the setting ${code(reference.activation.settingCode)}, and it defaults to ` +
|
|
437
|
+
`**${reference.activation.default ? 'on' : 'off'}**.\n`;
|
|
438
|
+
const dependencies = table(['Module', 'Binding', 'What it means'], [
|
|
439
|
+
...reference.dependencies.map((id) => [
|
|
440
|
+
`\`${id}\``,
|
|
441
|
+
'yes',
|
|
442
|
+
'installs and migrates after it, and an operator cannot switch it off underneath ' +
|
|
443
|
+
'this module',
|
|
444
|
+
]),
|
|
445
|
+
...reference.acknowledgedDependencies.map((entry) => [
|
|
446
|
+
`\`${entry.moduleId}\``,
|
|
447
|
+
'gating only',
|
|
448
|
+
`resolves \`${entry.port}\`; withheld from \`dependencies\` because the install ` +
|
|
449
|
+
'order cannot carry the edge',
|
|
450
|
+
]),
|
|
451
|
+
...reference.nonBindingDependencies.map((entry) => [
|
|
452
|
+
`\`${entry.moduleId}\``,
|
|
453
|
+
'no',
|
|
454
|
+
`${entry.kind} \`${entry.name}\`` +
|
|
455
|
+
(entry.whenAbsent === null ? '' : ` — ${entry.whenAbsent}`),
|
|
456
|
+
]),
|
|
457
|
+
]);
|
|
458
|
+
const summary = `Everything the \`${reference.moduleId}\` module's manifest declares: permissions, ` +
|
|
459
|
+
'palette actions, settings, activation and dependencies.';
|
|
460
|
+
return (`---\n` +
|
|
461
|
+
`title: ${yamlScalar(`${reference.moduleId} — module reference`)}\n` +
|
|
462
|
+
`sidebar_label: Reference\n` +
|
|
463
|
+
`description: ${yamlScalar(summary)}\n` +
|
|
464
|
+
`---\n\n` +
|
|
465
|
+
`${header}\n\n` +
|
|
466
|
+
`# \`${reference.moduleId}\` — module reference\n\n` +
|
|
467
|
+
`Rendered from the module's own manifest, and from nothing written by hand. ` +
|
|
468
|
+
(reference.prosePage === null
|
|
469
|
+
? `Nobody has written a page about what this module *does* yet; the ` +
|
|
470
|
+
`[module map](../${MODULES_CATEGORY}/${MODULE_MAP_ARTEFACT}) lists every module ` +
|
|
471
|
+
`the platform composes.\n\n`
|
|
472
|
+
: `What the module *does* is [its own page](${reference.prosePage}).\n\n`) +
|
|
473
|
+
`| | |\n| --- | --- |\n` +
|
|
474
|
+
`| Module id | ${code(reference.moduleId)} |\n` +
|
|
475
|
+
`| Name | ${markdownCell(reference.name)} |\n` +
|
|
476
|
+
`| Version | ${code(reference.version)} |\n` +
|
|
477
|
+
`| Ships from | ${code(reference.shipsFrom)} |\n` +
|
|
478
|
+
`| Licence | ${licenceCell(reference.license)} |\n\n` +
|
|
479
|
+
`## Activation\n\n${activation}\n` +
|
|
480
|
+
`## Dependencies\n\n${dependencies}\n` +
|
|
481
|
+
`## Permissions\n\n` +
|
|
482
|
+
table(['Code', 'Label', 'Also needs'], reference.permissions.map((entry) => [
|
|
483
|
+
`\`${entry.code}\``,
|
|
484
|
+
entry.label,
|
|
485
|
+
entry.requires.length === 0 ? '—' : entry.requires.map((c) => `\`${c}\``).join(', '),
|
|
486
|
+
])) +
|
|
487
|
+
`\n## Command palette\n\n` +
|
|
488
|
+
table(['Action', 'Opens', 'Permission'], reference.actions.map((entry) => [
|
|
489
|
+
`\`${entry.id}\``,
|
|
490
|
+
`\`${entry.targetRoute}\``,
|
|
491
|
+
code(entry.requiredPermission),
|
|
492
|
+
])) +
|
|
493
|
+
`\n## Settings\n\n` +
|
|
494
|
+
table(['Code', 'Name', 'Type'], reference.settings.map((entry) => [`\`${entry.code}\``, entry.name, `\`${entry.valueType}\``])) +
|
|
495
|
+
`\n## Translations\n\n` +
|
|
496
|
+
(reference.bundlesDir === null
|
|
497
|
+
? 'This module declares no translation bundles.\n'
|
|
498
|
+
: `Bundles at ${code(reference.bundlesDir)} inside the module, one file per shipped ` +
|
|
499
|
+
'language.\n') +
|
|
500
|
+
`\n## Operator commands\n\n` +
|
|
501
|
+
table(['Command', 'What it does'], reference.cliCommands.map((entry) => [
|
|
502
|
+
`\`pnpm --filter backend run cli -- ${reference.moduleId} ${entry.name}\``,
|
|
503
|
+
entry.summary,
|
|
504
|
+
])));
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* A module's documentation layer, from its manifest's own source text.
|
|
508
|
+
*
|
|
509
|
+
* A **computed** `dir` throws rather than reading as "declares nothing" (issue
|
|
510
|
+
* #113): a directory this walk cannot place is a module whose pages would
|
|
511
|
+
* simply not be collected, with no error anywhere.
|
|
512
|
+
*/
|
|
513
|
+
export function docsRootOf(id, source, moduleRoot) {
|
|
514
|
+
const declaration = docsDeclarationIn(source);
|
|
515
|
+
if (declaration === undefined || declaration === false)
|
|
516
|
+
return null;
|
|
517
|
+
if (declaration === 'unreadable') {
|
|
518
|
+
throw new ModulePackageError(`[composer] ${id}'s manifest declares \`docs\` with no literal \`dir\`. The directory is ` +
|
|
519
|
+
'the anchor the platform joins to the module\'s own root, so a value this generator ' +
|
|
520
|
+
'cannot read is a documentation layer nothing collects and nothing reports.');
|
|
521
|
+
}
|
|
522
|
+
return join(moduleRoot, declaration.dir);
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Every page the platform can see, the site's own tree and the modules' alike.
|
|
526
|
+
*
|
|
527
|
+
* A **mixed** tree is the supported state and not a transitional accident: a
|
|
528
|
+
* page in the site tree and a page in a module package are attributed by the
|
|
529
|
+
* same derivation, which is what lets Phase 2 land one batch at a time
|
|
530
|
+
* (`plan.md` § Phasing). `_lifecycle` is the standing resident of the site half
|
|
531
|
+
* — its manifest resolves inside the platform package's **build output**, and
|
|
532
|
+
* documentation is not a compiled asset, so it has no package root to ship
|
|
533
|
+
* from.
|
|
534
|
+
*
|
|
535
|
+
* A declared directory that is **not on disk** is a refusal naming the module
|
|
536
|
+
* (FR-017), and two sources claiming one doc id is a refusal too: the copy
|
|
537
|
+
* would write one over the other and whichever ran second would win, making the
|
|
538
|
+
* site's content depend on a directory read order.
|
|
539
|
+
*/
|
|
540
|
+
function collectAllDocPages(layout, manifests) {
|
|
541
|
+
const sources = [];
|
|
542
|
+
const missing = [];
|
|
543
|
+
for (const manifest of manifests) {
|
|
544
|
+
if (manifest.docsRoot === null)
|
|
545
|
+
continue;
|
|
546
|
+
if (!isDirectory(manifest.docsRoot)) {
|
|
547
|
+
missing.push(`${manifest.id} -> ${manifest.docsRoot}`);
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
sources.push({ moduleId: manifest.id, root: manifest.docsRoot });
|
|
551
|
+
}
|
|
552
|
+
if (missing.length > 0) {
|
|
553
|
+
throw new ModulePackageError(`[composer] ${missing.length} module(s) declare a documentation directory that is not ` +
|
|
554
|
+
`on disk:\n${missing.map((entry) => ` - ${entry}`).join('\n')}\n` +
|
|
555
|
+
'A declared directory that is absent is a refusal and never "this module ships no ' +
|
|
556
|
+
'documentation" — declare `docs: false` if that is the decision, or ship the directory.');
|
|
557
|
+
}
|
|
558
|
+
const modulePages = collectModuleDocPages(sources);
|
|
559
|
+
const copies = new Set(modulePages.map((page) => copyTargetOf(page, layout.modulesRoot)));
|
|
560
|
+
const pages = [...collectDocPages(layout.modulesRoot, copies), ...modulePages];
|
|
561
|
+
const duplicates = duplicateDocIds(pages);
|
|
562
|
+
if (duplicates.length > 0) {
|
|
563
|
+
throw new ModulePackageError(`[composer] ${duplicates.length} documentation page(s) are claimed twice:\n` +
|
|
564
|
+
duplicates
|
|
565
|
+
.map((entry) => ` - ${entry.docId}\n ${entry.paths.join('\n ')}`)
|
|
566
|
+
.join('\n'));
|
|
567
|
+
}
|
|
568
|
+
return pages.sort((a, b) => a.docId.localeCompare(b.docId));
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Where each module's generated reference page lands, by module id.
|
|
572
|
+
*
|
|
573
|
+
* Synchronous: *which* pages exist is a question about the manifests' `docs`
|
|
574
|
+
* declarations, which the caller's walk has already read, and only their
|
|
575
|
+
* **content** needs the import.
|
|
576
|
+
*
|
|
577
|
+
* Two modules folding onto one slug is a refusal rather than a page written
|
|
578
|
+
* twice — `slugForModule` strips a leading underscore, so a hypothetical
|
|
579
|
+
* `i18n` beside `_i18n` would have one of the two silently overwrite the other,
|
|
580
|
+
* and which one would depend on the order the manifest walk happened to
|
|
581
|
+
* produce.
|
|
582
|
+
*/
|
|
583
|
+
export function referencePagePaths(layout, manifests) {
|
|
584
|
+
const paths = new Map();
|
|
585
|
+
const bySlug = new Map();
|
|
586
|
+
for (const manifest of manifests) {
|
|
587
|
+
if (manifest.declaresNoDocs)
|
|
588
|
+
continue;
|
|
589
|
+
const slug = slugForModule(manifest.id);
|
|
590
|
+
const clash = bySlug.get(slug);
|
|
591
|
+
if (clash !== undefined) {
|
|
592
|
+
throw new ModulePackageError(`[composer] '${clash}' and '${manifest.id}' both document at the reference slug ` +
|
|
593
|
+
`'${slug}'. One page would be written over the other and which one survived would ` +
|
|
594
|
+
'depend on the order the manifest walk produced.');
|
|
595
|
+
}
|
|
596
|
+
bySlug.set(slug, manifest.id);
|
|
597
|
+
paths.set(manifest.id, join(layout.contentRoot, MODULE_REFERENCE_CATEGORY, `${slug}.md`));
|
|
598
|
+
}
|
|
599
|
+
return paths;
|
|
600
|
+
}
|
|
601
|
+
/** Pages on disk in the reference category that this run does not write. */
|
|
602
|
+
export function strayReferencePages(layout, expected) {
|
|
603
|
+
const directory = join(layout.contentRoot, MODULE_REFERENCE_CATEGORY);
|
|
604
|
+
if (!isDirectory(directory))
|
|
605
|
+
return [];
|
|
606
|
+
return readdirSync(directory)
|
|
607
|
+
.filter((name) => PAGE_EXTENSIONS.some((extension) => name.endsWith(extension)))
|
|
608
|
+
.map((name) => join(directory, name))
|
|
609
|
+
.filter((path) => !expected.has(path))
|
|
610
|
+
.sort();
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* The documentation population of a **client's instance** — the module packages
|
|
614
|
+
* it installed, and nothing else.
|
|
615
|
+
*
|
|
616
|
+
* The workspace host's population comes off the generated manifest index, which
|
|
617
|
+
* an instance does not have and does not want: what an instance composes is what
|
|
618
|
+
* `node_modules` holds (D-119/D-155), and the packages have already been scanned
|
|
619
|
+
* by the caller. Each module's manifest is located by its own `exports` map's
|
|
620
|
+
* `.` subpath, so the docs declaration is read out of the file
|
|
621
|
+
* `import '<name>'` loads — never out of a `manifest.ts`, which a published
|
|
622
|
+
* package does not ship.
|
|
623
|
+
*/
|
|
624
|
+
export function installedDocsModules(packages) {
|
|
625
|
+
return packages
|
|
626
|
+
.map((pkg) => {
|
|
627
|
+
const { manifestPath } = publishedManifestEntryOf(pkg);
|
|
628
|
+
const source = readFileSync(manifestPath, 'utf8');
|
|
629
|
+
return {
|
|
630
|
+
id: pkg.moduleId,
|
|
631
|
+
// The **package** directory, not the manifest file's: a module
|
|
632
|
+
// package's `docs/` sits at the package root beside `i18n/`, outside
|
|
633
|
+
// `src/`, and travels in the `files` list.
|
|
634
|
+
docsRoot: docsRootOf(pkg.moduleId, source, pkg.dir),
|
|
635
|
+
declaresNoDocs: docsDeclarationIn(source) === false,
|
|
636
|
+
manifestPath,
|
|
637
|
+
};
|
|
638
|
+
})
|
|
639
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* The one read every documentation artefact is rendered from.
|
|
643
|
+
*
|
|
644
|
+
* The site is a **parameter** and so is the module set: a host that walked for
|
|
645
|
+
* itself in here would be a second derivation of a population its caller has
|
|
646
|
+
* already decided, which is the state feature 100 ended for three hand-written
|
|
647
|
+
* lists and the state R3.5 forbids one artefact family over.
|
|
648
|
+
*/
|
|
649
|
+
export function docsRegistryOf(layout, modules, packages) {
|
|
650
|
+
const ids = modules.map((module) => module.id);
|
|
651
|
+
const pages = collectAllDocPages(layout, modules);
|
|
652
|
+
const attribution = attributeDocs(pages, ids);
|
|
653
|
+
const declinedDocs = new Set(modules.filter((module) => module.declaresNoDocs).map((module) => module.id));
|
|
654
|
+
return {
|
|
655
|
+
layout,
|
|
656
|
+
modules,
|
|
657
|
+
pages,
|
|
658
|
+
attribution,
|
|
659
|
+
entries: collectDocsRegistry(ids, attribution, packages, declinedDocs),
|
|
660
|
+
entrySources: new Map(pages
|
|
661
|
+
.filter((page) => page.origin.kind === 'module')
|
|
662
|
+
.map((page) => [copyTargetOf(page, layout.modulesRoot), page.path])),
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Pure render — the target path + expected content of the sidebar fragment.
|
|
667
|
+
*
|
|
668
|
+
* The path is derived from the workspace member holding the Docusaurus
|
|
669
|
+
* configuration, never written down (D-100), exactly as the admin registry's is
|
|
670
|
+
* derived from the member declaring the `"@/*"` alias.
|
|
671
|
+
*/
|
|
672
|
+
export function renderDocsSidebarFrom(registry, header = COMPOSER_DOCS_HEADER) {
|
|
673
|
+
return {
|
|
674
|
+
outputPath: join(registry.layout.member.dir, DOCS_SIDEBAR_ARTEFACT),
|
|
675
|
+
content: emitDocsSidebar(registry.entries, header),
|
|
676
|
+
entryRoot: registry.layout.contentRoot,
|
|
677
|
+
entrySources: registry.entrySources,
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
/** Pure render — the target path + expected content of the module map. */
|
|
681
|
+
export function renderModuleMapFrom(registry, header = COMPOSER_DOCS_PAGE_HEADER) {
|
|
682
|
+
return {
|
|
683
|
+
outputPath: join(registry.layout.modulesRoot, MODULE_MAP_ARTEFACT),
|
|
684
|
+
content: emitModuleMap(registry.entries, header),
|
|
685
|
+
entryRoot: registry.layout.contentRoot,
|
|
686
|
+
entrySources: registry.entrySources,
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
export async function renderModuleReferencesFrom(registry, options = {}) {
|
|
690
|
+
const header = options.header ?? COMPOSER_DOCS_PAGE_HEADER;
|
|
691
|
+
const { layout, modules, entries, entrySources } = registry;
|
|
692
|
+
const paths = referencePagePaths(layout, modules);
|
|
693
|
+
const byModule = new Map(entries.map((entry) => [entry.moduleId, entry]));
|
|
694
|
+
const rendered = [];
|
|
695
|
+
for (const module of modules) {
|
|
696
|
+
const outputPath = paths.get(module.id);
|
|
697
|
+
if (outputPath === undefined)
|
|
698
|
+
continue;
|
|
699
|
+
const entry = byModule.get(module.id);
|
|
700
|
+
const loaded = (await import(pathToFileURL(module.manifestPath).href));
|
|
701
|
+
const shipsFrom = entry?.shipsFrom ?? 'core';
|
|
702
|
+
const reference = referenceOf(module.id, loaded, shipsFrom, entry?.docs == null ? null : `../${MODULES_CATEGORY}/${entry.docs.entry.relativePath}`, publishedLicenseOf(module.manifestPath, shipsFrom));
|
|
703
|
+
rendered.push({
|
|
704
|
+
label: `module-reference (${module.id})`,
|
|
705
|
+
outputPath,
|
|
706
|
+
content: emitModuleReference(reference, header),
|
|
707
|
+
entryRoot: layout.contentRoot,
|
|
708
|
+
entrySources,
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
const expected = new Set(rendered.map((artefact) => artefact.outputPath));
|
|
712
|
+
const stray = strayReferencePages(layout, expected);
|
|
713
|
+
if (stray.length > 0 && (options.stray ?? 'refuse') === 'refuse') {
|
|
714
|
+
throw new StrayReferencePageError(`[composer] ${stray.length} page(s) under ${MODULE_REFERENCE_CATEGORY}/ belong to no ` +
|
|
715
|
+
`registered module:\n${stray.map((path) => ` - ${path}`).join('\n')}\n` +
|
|
716
|
+
'Every page in that category is generated from a manifest, so one nothing renders is ' +
|
|
717
|
+
'a module that has gone: `git rm` it. It is refused rather than swept because a ' +
|
|
718
|
+
'committed file this generator deleted is a change no reviewer asked for.');
|
|
719
|
+
}
|
|
720
|
+
if (options.stray === 'sweep')
|
|
721
|
+
for (const page of stray)
|
|
722
|
+
rmSync(page);
|
|
723
|
+
return { pages: rendered, swept: stray };
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* The record of what the last collection wrote, so the next one can undo it.
|
|
727
|
+
*
|
|
728
|
+
* The copies are **not committed** (`.gitignore`), which is what keeps 10,311
|
|
729
|
+
* lines of prose from existing twice in this repository — one editable copy and
|
|
730
|
+
* one that looks editable and is not. The consequence is that nothing else
|
|
731
|
+
* knows which files under the modules category are copies, and a page a module
|
|
732
|
+
* deletes would otherwise be served for ever. The stamp answers exactly that
|
|
733
|
+
* and nothing else: a run removes the files the previous run wrote and no
|
|
734
|
+
* longer writes, and a fresh checkout with no stamp removes nothing, which is
|
|
735
|
+
* correct because it has copied nothing.
|
|
736
|
+
*/
|
|
737
|
+
const COPY_STAMP = '.module-docs-copies.json';
|
|
738
|
+
/**
|
|
739
|
+
* Copy every module-owned page into the site's tree, preserving its address.
|
|
740
|
+
*
|
|
741
|
+
* **Copy, never symlink** (research D-8): Docusaurus resolves `docs.path` and
|
|
742
|
+
* its `include` globs against the site directory, and a symlinked subtree makes
|
|
743
|
+
* the file watcher, webpack's module graph and the markdown link resolver
|
|
744
|
+
* disagree about where a page is — issue #255's finding, one tool reading one
|
|
745
|
+
* tree while another reads a second.
|
|
746
|
+
*
|
|
747
|
+
* The target is the page's own relative path inside the category, so the copy
|
|
748
|
+
* preserves the doc id, the permalink and every relative link written against
|
|
749
|
+
* it (`module-documentation-layer.md` R5.2). That is what makes the move
|
|
750
|
+
* invisible to a reader and to an inbound link alike.
|
|
751
|
+
*/
|
|
752
|
+
export function collectDocsIntoSiteFrom(registry) {
|
|
753
|
+
const { layout, pages } = registry;
|
|
754
|
+
const stampPath = join(layout.member.dir, COPY_STAMP);
|
|
755
|
+
const previous = existsSync(stampPath)
|
|
756
|
+
? JSON.parse(readFileSync(stampPath, 'utf8'))
|
|
757
|
+
: [];
|
|
758
|
+
const copied = [];
|
|
759
|
+
for (const page of pages) {
|
|
760
|
+
if (page.origin.kind !== 'module')
|
|
761
|
+
continue;
|
|
762
|
+
const target = copyTargetOf(page, layout.modulesRoot);
|
|
763
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
764
|
+
copyFileSync(page.path, target);
|
|
765
|
+
copied.push(relative(layout.member.dir, target));
|
|
766
|
+
}
|
|
767
|
+
const current = new Set(copied);
|
|
768
|
+
const removed = [];
|
|
769
|
+
for (const stale of previous) {
|
|
770
|
+
if (current.has(stale))
|
|
771
|
+
continue;
|
|
772
|
+
const target = join(layout.member.dir, stale);
|
|
773
|
+
if (!existsSync(target))
|
|
774
|
+
continue;
|
|
775
|
+
rmSync(target);
|
|
776
|
+
removed.push(stale);
|
|
777
|
+
}
|
|
778
|
+
writeFileSync(stampPath, `${JSON.stringify([...copied].sort(), null, 2)}\n`, 'utf8');
|
|
779
|
+
return { modulesRoot: layout.modulesRoot, copied: copied.sort(), removed: removed.sort() };
|
|
780
|
+
}
|
|
781
|
+
//# sourceMappingURL=docs-artefacts.js.map
|