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