@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,749 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CI check — a module reaches the platform only through what the platform
|
|
3
|
+
* publishes (feature 080, T042d; ruling **D-160.8**).
|
|
4
|
+
*
|
|
5
|
+
* ## Why it exists
|
|
6
|
+
*
|
|
7
|
+
* `specs/080-f4-real-scope/contracts/host-package.md` §1 classifies all 53
|
|
8
|
+
* platform files a module reaches as **P** (published API), **A** (accidental
|
|
9
|
+
* reach — host-internal, harness-only or orchestrator-only) or **O** (the
|
|
10
|
+
* module is reaching for a class where a sanctioned port exists). Three
|
|
11
|
+
* delivered rows now build on that classification, and **nothing in the
|
|
12
|
+
* repository enforced it**: `check:module-boundary` is module→module,
|
|
13
|
+
* `check:kernel-boundary` is ORM relations, and the `exports` map D-160.7 rules
|
|
14
|
+
* enforces nothing at all for the modules still in `backend/src` — which is all
|
|
15
|
+
* of them. A classification with no ratchet rots at the speed of the tree, and
|
|
16
|
+
* this one is load-bearing for the package split.
|
|
17
|
+
*
|
|
18
|
+
* ## The population, and what it misses
|
|
19
|
+
*
|
|
20
|
+
* Every import specifier written in a module's own sources — core under the
|
|
21
|
+
* application's module roots, packaged under `packages/modules/<id>`, overlay
|
|
22
|
+
* under `src/apps` — that names the platform. Two spellings, and they are the
|
|
23
|
+
* same reach:
|
|
24
|
+
*
|
|
25
|
+
* * a **relative** specifier that resolves to a file outside the module tree.
|
|
26
|
+
* "Outside the module tree" is the whole predicate: the host is everything
|
|
27
|
+
* that is not a module, so a reach into `src/seeds` or `src/composition.ts`
|
|
28
|
+
* is judged by the same rule as one into `src/kernel`, with no directory
|
|
29
|
+
* list to keep current.
|
|
30
|
+
* * a **bare** specifier into the host package — `<host>/kernel` — which is
|
|
31
|
+
* the only spelling a module outside the application tree has (feature 080,
|
|
32
|
+
* T060). It resolves through the host's own `exports` map to the same
|
|
33
|
+
* barrel, so `@endora-commerce/platform/http` and `../../http/index.js` are
|
|
34
|
+
* one answer. Neither the package name nor the subpath list is written
|
|
35
|
+
* here: both are read off the manifest of the member declaring
|
|
36
|
+
* `endora.type: "platform"`, so the D-161 scope rename and a sixth
|
|
37
|
+
* published directory arrive by being authored once.
|
|
38
|
+
*
|
|
39
|
+
* A declared subpath lands on a **barrel**, and reaching a barrel is reaching
|
|
40
|
+
* the published surface entire — so a bare reach is counted, attributed and
|
|
41
|
+
* cleared, and the symbol-level verdicts below are the relative spelling's. That
|
|
42
|
+
* is not a weaker rule for a packaged module: a name the barrel does not carry
|
|
43
|
+
* is a `tsc` error at the import, which is the same answer sooner. What the bare
|
|
44
|
+
* spelling *can* be wrong about is the **subpath**, and that is the finding it
|
|
45
|
+
* gets.
|
|
46
|
+
*
|
|
47
|
+
* Every specifier shape `scripts/lib/specifiers.ts` knows is read, `import type`
|
|
48
|
+
* and the one type-position `import('…')` included (§0b: a `from '…'`-only scan
|
|
49
|
+
* does not see it, and a module would move with an unrewritten specifier that
|
|
50
|
+
* `tsc` resolves and `node` never sees).
|
|
51
|
+
*
|
|
52
|
+
* **The second bullet is T060 and it is a repair, not a widening.** The
|
|
53
|
+
* population was relative specifiers alone, on the reasoning — written into
|
|
54
|
+
* this header — that "there is no host package yet, and when there is, its
|
|
55
|
+
* `exports` map refuses a deep path at resolution time and this check's
|
|
56
|
+
* population shrinks to nothing on its own". Both halves were wrong by the time
|
|
57
|
+
* the first module moved. The map refuses a *deep* path and licenses everything
|
|
58
|
+
* a widened map would license, which is precisely the reach D-160.8 exists to
|
|
59
|
+
* refuse; and a population that shrinks as the sweep proceeds is a check that
|
|
60
|
+
* reports green because it stopped looking. Measured: batch one took **104**
|
|
61
|
+
* reaches out of the walk and modules #1–#5 another 112, while the recorded
|
|
62
|
+
* read size absorbed one module's worth of the fall inside its own floor. That
|
|
63
|
+
* is why {@link hostDependentCoverage} exists beside the resolution — the
|
|
64
|
+
* repair without the floor is one edit away from happening again.
|
|
65
|
+
*
|
|
66
|
+
* **What that misses, stated rather than discovered later.** A specifier is not
|
|
67
|
+
* the only way to reach something, and `check:module-boundary` learned it the
|
|
68
|
+
* expensive way — it had to grow a SQL-table predicate for 121 reaches that name
|
|
69
|
+
* no specifier at all. The same three doors are open here and none of them is
|
|
70
|
+
* this check's:
|
|
71
|
+
*
|
|
72
|
+
* * **A container name.** `lazyPort('settingsReadPort')` reaches the platform
|
|
73
|
+
* through a string. That edge is `check:port-dependencies`' and
|
|
74
|
+
* `check:port-shape`'s, which between them own the port surface;
|
|
75
|
+
* `PLATFORM_OWNED_NAMES` is where a platform-owned name is declared.
|
|
76
|
+
* * **A platform-owned table.** `settings`, `audit_logs`,
|
|
77
|
+
* `module_registrations` and `sales_channels` are the host's, and SQL naming
|
|
78
|
+
* one names no specifier. That door is `check:module-boundary`'s and it is
|
|
79
|
+
* **open**: its owner map attributes those four to `kernel` off the
|
|
80
|
+
* platform-relative path of the file declaring them, so a module's SQL
|
|
81
|
+
* naming one is reported like any other cross-owner reach. `catalog`'s
|
|
82
|
+
* `sales_channel_products` join is ledgered under that attribution today,
|
|
83
|
+
* and `admin_actions`' `module_registrations` join was until feature 080
|
|
84
|
+
* retired it. This paragraph read *"a module→platform table reach is
|
|
85
|
+
* currently nobody's"* and was measured false while that second entry was
|
|
86
|
+
* being drained — it was written before the platform relocation taught the
|
|
87
|
+
* other check to attribute a `packages/platform/…` path to the kernel.
|
|
88
|
+
* What is still not this check's is the **verdict**: a table has no barrel,
|
|
89
|
+
* so there is no published-symbol question to ask about one.
|
|
90
|
+
* * **A module package's own layout.** Where inside a package a subpath
|
|
91
|
+
* leads is that package's `exports` map, and this check reads only the
|
|
92
|
+
* *host's*. A module that reached another module by bare specifier is
|
|
93
|
+
* `check:module-boundary`'s, which resolves module package names for
|
|
94
|
+
* exactly that reason.
|
|
95
|
+
*
|
|
96
|
+
* ## The granularity: per symbol, of a named file
|
|
97
|
+
*
|
|
98
|
+
* A finding is a **(module file, platform file, symbol)** triple, and the
|
|
99
|
+
* verdict is "does that platform file's barrel publish that name". Not per file,
|
|
100
|
+
* and the difference is not academic — !883 measured it: sixteen symbols of
|
|
101
|
+
* **P** files are reached by nobody and are deliberately unpublished, so
|
|
102
|
+
* "the file is P" licenses `SettingsCache`, `decryptSecretValue` and the LRU
|
|
103
|
+
* tuning constants along with the symbols the classification actually names.
|
|
104
|
+
*
|
|
105
|
+
* **What per-*file* granularity would catch that this will not.** A per-file
|
|
106
|
+
* check would refuse every reach into an **A** or **O** file outright, symbol or
|
|
107
|
+
* no symbol — so it would flag `SettingNotRegistered` imported from
|
|
108
|
+
* `settings.service.ts` (row 8, **O**), `parseHostMap` from
|
|
109
|
+
* `sales-channel-resolver.service.ts` (row 24, **O**) and
|
|
110
|
+
* `MembershipMutationResult` from `sales-channel-membership.service.ts` (row 15,
|
|
111
|
+
* **O**). Eight such symbols stand today and §8 rules every one of them
|
|
112
|
+
* *published* on purpose — a `@throws` a caller cannot name is a method a caller
|
|
113
|
+
* cannot call. So the per-file verdict would be eight false findings, and its
|
|
114
|
+
* one genuine catch is a different question: *is this symbol rightly on the
|
|
115
|
+
* barrel at all?* That question belongs to
|
|
116
|
+
* `test/unit/kernel/published-surface.test.ts`, which holds each barrel to
|
|
117
|
+
* §1.3's own symbol column in both directions. The two are deliberately not
|
|
118
|
+
* duplicates: that test asks whether the barrel is right, this check asks
|
|
119
|
+
* whether the tree obeys it, and both read the barrel through
|
|
120
|
+
* `scripts/lib/platform-surface.ts` so they cannot disagree about what it says.
|
|
121
|
+
*
|
|
122
|
+
* **That gap is closed** (T042f). It read, until then: `!883` prunes and
|
|
123
|
+
* ratchets three of the five barrels, and `tenancy/index.ts` and
|
|
124
|
+
* `commands/index.ts` are published surface by D-160.7 with no expected set at
|
|
125
|
+
* all — so for two of this check's five subpaths its authority was a barrel
|
|
126
|
+
* nothing held to §1.3. Both are now pruned to their **P** columns (§1.3 rows
|
|
127
|
+
* 2, 5, 12, 21, 28, 30, 33) and ratcheted two-way, and
|
|
128
|
+
* `published-surface.test.ts` derives its own population from
|
|
129
|
+
* {@link PUBLISHED_SUBPATHS} rather than a list, so a sixth published directory
|
|
130
|
+
* cannot arrive unratcheted the way these two did.
|
|
131
|
+
*
|
|
132
|
+
* Worth recording, because a green that moves nothing is the outcome most
|
|
133
|
+
* likely to be misread: closing it moved **no** ledger key and no finding. The
|
|
134
|
+
* 37 names the two barrels shed are reached by no module, and every name a
|
|
135
|
+
* module reaches stayed published — so nothing had been hiding behind the
|
|
136
|
+
* unratcheted barrels. The value bought is prospective: a 38th name added to
|
|
137
|
+
* either now has to move an expected set.
|
|
138
|
+
*
|
|
139
|
+
* ## Five findings
|
|
140
|
+
*
|
|
141
|
+
* * `unpublished-symbol` — the rule itself.
|
|
142
|
+
* * `whole-file-reach` — `import * as`, a side-effect import, `export *`, a
|
|
143
|
+
* `require()` or a dynamic `import()` with no named binding, at a file that
|
|
144
|
+
* is not a barrel. The symbol set is not knowable from the specifier, so the
|
|
145
|
+
* reach is the file's *whole* surface, internals included.
|
|
146
|
+
* * `unresolvable-reach` — a relative specifier that names no file the walk
|
|
147
|
+
* found. It is a finding and not a skip: !879 found #215 one layer in, where
|
|
148
|
+
* a walk of the right length had its *result* discarded downstream and
|
|
149
|
+
* reported clean behind a full-length `read:` line.
|
|
150
|
+
* * `unattributed-source` — a file under a module walk root that no module
|
|
151
|
+
* owns. Same reason: a file walked and not judged is worse than one not
|
|
152
|
+
* walked, because the `read:` line counts it.
|
|
153
|
+
* * `unpublished-subpath` — a bare specifier into the host package naming a
|
|
154
|
+
* subpath its `exports` map does not declare, the host's root among them
|
|
155
|
+
* (D-160.7 publishes no root export). Today `node` and `tsc` refuse it too,
|
|
156
|
+
* which is not a reason to leave it unjudged: what makes it a *finding* is
|
|
157
|
+
* that widening the map is the obvious repair, and the whole of D-160.8 is
|
|
158
|
+
* that widening the map is the thing an author must not do quietly.
|
|
159
|
+
* * `host-internal-subpath` — a bare specifier naming a subpath the `exports`
|
|
160
|
+
* map **does** declare and no barrel carries (D-160.14, feature 109). It is
|
|
161
|
+
* a third state and not a shade of the two above, and it is the only one of
|
|
162
|
+
* the six that `node` and `tsc` both accept: `./composition` resolves, so
|
|
163
|
+
* nothing but this check stands between a module and 27 composition symbols
|
|
164
|
+
* — `composeModules`, `createRootContainer`, `registerOrm` — that no module
|
|
165
|
+
* may name, production source or test. A module's server-bound test composes
|
|
166
|
+
* through the test kit's `composeTestServer`.
|
|
167
|
+
*
|
|
168
|
+
* It could not be a sixth {@link PUBLISHED_SUBPATHS} entry, measured:
|
|
169
|
+
* {@link PlatformSurface.published} is keyed by target file with no subpath
|
|
170
|
+
* dimension, so that entry would publish `composeModules` out of
|
|
171
|
+
* `kernel/compose.ts` for a module's *relative* reach as well — and, this
|
|
172
|
+
* check reporting `violations=0`, would change nothing it prints. The two
|
|
173
|
+
* lists answering differently is the mechanism, not a drift to reconcile:
|
|
174
|
+
* {@link HostPackage.declaredSubpaths} is the manifest's answer,
|
|
175
|
+
* {@link PUBLISHED_SUBPATHS} is the ruling's.
|
|
176
|
+
*
|
|
177
|
+
* ## One key space, and it is the repository's
|
|
178
|
+
*
|
|
179
|
+
* Every path here — a module source, a platform file, a barrel, a ledger key —
|
|
180
|
+
* is written relative to the **repository root**. That is more verbose than the
|
|
181
|
+
* `modules/blog/x.ts` shape the other ledgers use, and it is not a style
|
|
182
|
+
* choice: this check *resolves* specifiers, and a specifier resolves in exactly
|
|
183
|
+
* one namespace. `layout.keyOf` deliberately has two bases — an application
|
|
184
|
+
* file is keyed inside `backend/src`, a packaged module's file relative to the
|
|
185
|
+
* checkout, because there is no application prefix that would be true of the
|
|
186
|
+
* second — and a resolver straddling both is wrong for every reach that crosses
|
|
187
|
+
* between them. Measured on the split-tree fixture, where six modules live in
|
|
188
|
+
* `packages/modules/<id>/src`: 90 reaches resolved to nothing, and the whole
|
|
189
|
+
* point of that fixture is that a check keeps working while the layout moves.
|
|
190
|
+
*
|
|
191
|
+
* Plus the **refusals**, each over an input whose silent absence would narrow
|
|
192
|
+
* the answer rather than fail it:
|
|
193
|
+
*
|
|
194
|
+
* * a barrel this parse cannot read in full (an `export *`, a namespace
|
|
195
|
+
* re-export, an `export { … }` with no `from`) is exit 2, never a narrower
|
|
196
|
+
* published set. A short surface turns correct reaches into findings, and
|
|
197
|
+
* the obvious "repair" for one of those is to widen the barrel;
|
|
198
|
+
* * a workspace with no platform member, or a platform member publishing
|
|
199
|
+
* under no name — every bare reach would then be judged by nothing;
|
|
200
|
+
* * a module package whose `package.json` will not parse, which is what says
|
|
201
|
+
* whether it reaches the host at all;
|
|
202
|
+
* * and {@link hostDependentCoverage}'s shortfall: a module package that
|
|
203
|
+
* declares the host and contributed no host reach to this walk. That is
|
|
204
|
+
* issue #215's predicate over the population T060 restored, and it is the
|
|
205
|
+
* one derivation that would have caught T060's own defect — `manifest-index`
|
|
206
|
+
* counts modules that produced a *file*, which a packaged module does
|
|
207
|
+
* plentifully, and `platform-barrels` counts barrels, which a module move
|
|
208
|
+
* does not touch.
|
|
209
|
+
*
|
|
210
|
+
* ## The second consumer population (feature 115, D115-5)
|
|
211
|
+
*
|
|
212
|
+
* The rule above is *a reach into the host names a published subpath or a
|
|
213
|
+
* declared host-internal one, never a file inside the package by relative path*,
|
|
214
|
+
* and it was asked of **modules** only. The **application** writes the same
|
|
215
|
+
* reach — 84 of them, every one of which resolves in this checkout and in no
|
|
216
|
+
* instance built from published packages, which is the defect D-207 names — and
|
|
217
|
+
* was outside the population by construction: `moduleIdOf` answers `null` for
|
|
218
|
+
* every one of its files, so a green was honest about a population that did not
|
|
219
|
+
* contain them. {@link scanApplicationReaches} is that half, `relative-host-reach`
|
|
220
|
+
* is its finding, and {@link LedgeredHostReach} is its ledger's entry.
|
|
221
|
+
*
|
|
222
|
+
* It is one rule with two populations and not two rules: this analysis already
|
|
223
|
+
* derives the four inputs a second one would re-derive, and two derivations of
|
|
224
|
+
* one population are two answers waiting to disagree (D-100). Normative:
|
|
225
|
+
* `specs/115-lifecycle-container-move/contracts/host-reach-check.md`.
|
|
226
|
+
*
|
|
227
|
+
* Usage: `tsx scripts/check-platform-surface.ts [--list]`
|
|
228
|
+
* Exit 0 = every reach into the platform is published, declared or ledgered;
|
|
229
|
+
* exit 1 = at least one is not, or a ledger entry is stale;
|
|
230
|
+
* exit 2 = the walk, the index, a barrel or the application tree could not be read.
|
|
231
|
+
*/
|
|
232
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
233
|
+
import { join, relative } from 'node:path';
|
|
234
|
+
import { moduleIdOf } from '../lib/module-population.js';
|
|
235
|
+
import { resolutionCandidates, resolveHostSpecifier, resolveRelative, } from '../lib/platform-surface.js';
|
|
236
|
+
import {} from '../lib/read-size.js';
|
|
237
|
+
import { namedSpecifiers } from '../lib/specifiers.js';
|
|
238
|
+
/** The symbol token recorded for a reach that names no symbol at all. */
|
|
239
|
+
export const WHOLE_FILE = '*';
|
|
240
|
+
/** The symbol token recorded for a finding that is about the file, not a name. */
|
|
241
|
+
export const NO_SYMBOL = '?';
|
|
242
|
+
/** The target token recorded for a source file no module owns. */
|
|
243
|
+
export const NO_MODULE = '(unattributed)';
|
|
244
|
+
/**
|
|
245
|
+
* The floor that follows the sweep: every module package whose manifest
|
|
246
|
+
* declares the host must have contributed a host reach to this walk.
|
|
247
|
+
*
|
|
248
|
+
* This is issue #215's predicate over the population T060 restored, and it is
|
|
249
|
+
* the one derivation that would have caught the defect. The other two cannot:
|
|
250
|
+
* `manifest-index` counts modules that produced a **file**, and a packaged
|
|
251
|
+
* module produces plenty; `platform-barrels` counts barrels, which the move
|
|
252
|
+
* does not touch. What fell was the *reaches*, and the number that recorded
|
|
253
|
+
* them was a snapshot in `test/helpers/check-read-sizes.ts` whose −10% floor
|
|
254
|
+
* absorbed one module's worth of the fall without a word.
|
|
255
|
+
*
|
|
256
|
+
* The declaration is a second author's, which is what makes it worth
|
|
257
|
+
* reconciling against: `manifests:generate` renders a module package's
|
|
258
|
+
* `peerDependencies` from the bare specifiers its sources import, and
|
|
259
|
+
* `manifests:check` fails on drift. So "the manifest says this package reaches
|
|
260
|
+
* the host" and "the walk read a reach from this package" are two derivations
|
|
261
|
+
* of one fact, and a walk that stopped reading bare specifiers makes them
|
|
262
|
+
* disagree in the same run.
|
|
263
|
+
*
|
|
264
|
+
* `null` rather than `expected: 0` for a tree with no module package that
|
|
265
|
+
* declares the host — every tree in this repository until !910, and every
|
|
266
|
+
* fixture workspace. An expectation of zero is itself a refusal in this
|
|
267
|
+
* grammar, and rightly: a floor that expects nothing is switched off.
|
|
268
|
+
*/
|
|
269
|
+
export function hostDependentCoverage(packages, hostReachModules) {
|
|
270
|
+
const declaring = packages.filter((pkg) => pkg.dependsOnHost);
|
|
271
|
+
if (declaring.length === 0)
|
|
272
|
+
return null;
|
|
273
|
+
return {
|
|
274
|
+
source: 'host-dependents',
|
|
275
|
+
expected: declaring.length,
|
|
276
|
+
covered: declaring.filter((pkg) => hostReachModules.has(pkg.moduleId)).length,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
/** `<module file>|<platform file>` — the ledger key and the identity of a reach. */
|
|
280
|
+
export function keyOf(finding) {
|
|
281
|
+
return `${finding.file}|${finding.target}`;
|
|
282
|
+
}
|
|
283
|
+
/** The file a specifier names, or `null` when the walk found no such file. */
|
|
284
|
+
/**
|
|
285
|
+
* A specifier's target file, or `null`. Exported because both hosts build the
|
|
286
|
+
* platform's published surface with it and a second resolver would be a second
|
|
287
|
+
* answer to "which file does this name".
|
|
288
|
+
*/
|
|
289
|
+
export function resolveTarget(fromKey, specifier, files) {
|
|
290
|
+
const joined = resolveRelative(fromKey, specifier);
|
|
291
|
+
if (joined === null)
|
|
292
|
+
return null;
|
|
293
|
+
for (const candidate of resolutionCandidates(joined)) {
|
|
294
|
+
if (files.has(candidate))
|
|
295
|
+
return candidate;
|
|
296
|
+
}
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Every module reach into the platform, judged against the published surface.
|
|
301
|
+
*
|
|
302
|
+
* Pure over source text, file keys and barrel-derived surface, so a fixture
|
|
303
|
+
* enters exactly where a run does — including the specifier extraction and the
|
|
304
|
+
* `.js` → `.ts` resolution, which is where a resolution bug would hide (issue
|
|
305
|
+
* #130).
|
|
306
|
+
*/
|
|
307
|
+
export function scanPlatformSurface(input) {
|
|
308
|
+
const attribute = input.moduleIdOf ?? moduleIdOf;
|
|
309
|
+
const canonical = input.canonicalTargetOf ?? ((key) => key);
|
|
310
|
+
const host = input.host ?? null;
|
|
311
|
+
const platformSourceRoot = input.platformSourceRoot ?? null;
|
|
312
|
+
const withinPlatform = (key) => platformSourceRoot !== null &&
|
|
313
|
+
(key === platformSourceRoot || key.startsWith(`${platformSourceRoot}/`));
|
|
314
|
+
const findings = [];
|
|
315
|
+
const hostReachModules = new Set();
|
|
316
|
+
let reaches = 0;
|
|
317
|
+
for (const [file, text] of [...input.sources].sort(([a], [b]) => a.localeCompare(b))) {
|
|
318
|
+
const moduleId = attribute(file);
|
|
319
|
+
if (moduleId === null) {
|
|
320
|
+
// A file the walk opened and could not attribute. Skipping it would leave
|
|
321
|
+
// it counted in `read: files=` and judged by nothing, which is #215 one
|
|
322
|
+
// layer in (!879).
|
|
323
|
+
findings.push({
|
|
324
|
+
kind: 'unattributed-source',
|
|
325
|
+
file,
|
|
326
|
+
line: 1,
|
|
327
|
+
moduleId: null,
|
|
328
|
+
target: NO_MODULE,
|
|
329
|
+
symbol: NO_SYMBOL,
|
|
330
|
+
specifier: '',
|
|
331
|
+
});
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
// Attributed, and then exempt: a module inside the host package reaches the
|
|
335
|
+
// platform by relative path within one package — see `platformSourceRoot`.
|
|
336
|
+
if (withinPlatform(file))
|
|
337
|
+
continue;
|
|
338
|
+
for (const specifier of namedSpecifiers(text, file)) {
|
|
339
|
+
// A bare specifier into the host package is the same reach a module in
|
|
340
|
+
// the application tree writes relatively (feature 080, T060). Asked
|
|
341
|
+
// first, because a specifier that names the host is never a relative one
|
|
342
|
+
// and the two answers must not both be consulted.
|
|
343
|
+
const hostReach = resolveHostSpecifier(specifier.text, host);
|
|
344
|
+
if (hostReach !== null)
|
|
345
|
+
hostReachModules.add(moduleId);
|
|
346
|
+
if (hostReach?.kind === 'undeclared-subpath' || hostReach?.kind === 'host-internal-subpath') {
|
|
347
|
+
findings.push({
|
|
348
|
+
// The two are one branch and two verdicts on purpose: both are about
|
|
349
|
+
// the *subpath* rather than a symbol, and only one of them names a
|
|
350
|
+
// path the host's `exports` map resolves.
|
|
351
|
+
kind: hostReach.kind === 'undeclared-subpath'
|
|
352
|
+
? 'unpublished-subpath'
|
|
353
|
+
: 'host-internal-subpath',
|
|
354
|
+
file,
|
|
355
|
+
line: specifier.line,
|
|
356
|
+
moduleId,
|
|
357
|
+
target: specifier.text,
|
|
358
|
+
symbol: NO_SYMBOL,
|
|
359
|
+
specifier: specifier.text,
|
|
360
|
+
});
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
// Someone else's package: `zod`, `@mikro-orm/core`, another module's.
|
|
364
|
+
if (hostReach === null && !specifier.text.startsWith('.'))
|
|
365
|
+
continue;
|
|
366
|
+
const resolved = hostReach === null ? resolveTarget(file, specifier.text, input.files) : hostReach.target;
|
|
367
|
+
const target = resolved === null ? null : canonical(resolved);
|
|
368
|
+
if (target === null) {
|
|
369
|
+
findings.push({
|
|
370
|
+
kind: 'unresolvable-reach',
|
|
371
|
+
file,
|
|
372
|
+
line: specifier.line,
|
|
373
|
+
moduleId,
|
|
374
|
+
target: specifier.text,
|
|
375
|
+
symbol: NO_SYMBOL,
|
|
376
|
+
specifier: specifier.text,
|
|
377
|
+
});
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
// Module → module is `check:module-boundary`'s rule, not this one's.
|
|
381
|
+
if (attribute(target) !== null)
|
|
382
|
+
continue;
|
|
383
|
+
const named = specifier.bindings.filter((binding) => !binding.startsWith('* as'));
|
|
384
|
+
const wholeFile = specifier.bindings.length === 0 || named.length < specifier.bindings.length;
|
|
385
|
+
if (wholeFile) {
|
|
386
|
+
reaches += 1;
|
|
387
|
+
// Reaching a barrel *is* reaching the published surface, whole or not.
|
|
388
|
+
if (!input.surface.barrels.has(target)) {
|
|
389
|
+
findings.push({
|
|
390
|
+
kind: 'whole-file-reach',
|
|
391
|
+
file,
|
|
392
|
+
line: specifier.line,
|
|
393
|
+
moduleId,
|
|
394
|
+
target,
|
|
395
|
+
symbol: WHOLE_FILE,
|
|
396
|
+
specifier: specifier.text,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const published = input.surface.published.get(target) ?? new Set();
|
|
401
|
+
for (const name of named) {
|
|
402
|
+
reaches += 1;
|
|
403
|
+
if (input.surface.barrels.has(target))
|
|
404
|
+
continue;
|
|
405
|
+
if (published.has(name))
|
|
406
|
+
continue;
|
|
407
|
+
findings.push({
|
|
408
|
+
kind: 'unpublished-symbol',
|
|
409
|
+
file,
|
|
410
|
+
line: specifier.line,
|
|
411
|
+
moduleId,
|
|
412
|
+
target,
|
|
413
|
+
symbol: name,
|
|
414
|
+
specifier: specifier.text,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
findings.sort((a, b) => a.file === b.file
|
|
420
|
+
? a.target === b.target
|
|
421
|
+
? a.symbol.localeCompare(b.symbol)
|
|
422
|
+
: a.target.localeCompare(b.target)
|
|
423
|
+
: a.file.localeCompare(b.file));
|
|
424
|
+
return { reaches, findings, hostReachModules };
|
|
425
|
+
}
|
|
426
|
+
export function checkPlatformSurface(input, ledger) {
|
|
427
|
+
const scan = scanPlatformSurface(input);
|
|
428
|
+
const seen = new Map();
|
|
429
|
+
for (const finding of scan.findings) {
|
|
430
|
+
const symbols = seen.get(keyOf(finding)) ?? new Set();
|
|
431
|
+
symbols.add(finding.symbol);
|
|
432
|
+
seen.set(keyOf(finding), symbols);
|
|
433
|
+
}
|
|
434
|
+
const covers = (finding) => ledger[keyOf(finding)]?.symbols.includes(finding.symbol) === true;
|
|
435
|
+
const staleKeys = [];
|
|
436
|
+
const staleSymbols = [];
|
|
437
|
+
for (const [key, entry] of Object.entries(ledger)) {
|
|
438
|
+
const symbols = seen.get(key);
|
|
439
|
+
if (symbols === undefined) {
|
|
440
|
+
staleKeys.push(key);
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
for (const symbol of entry.symbols) {
|
|
444
|
+
if (!symbols.has(symbol))
|
|
445
|
+
staleSymbols.push(`${key}#${symbol}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
reaches: scan.reaches,
|
|
450
|
+
findings: scan.findings,
|
|
451
|
+
violations: scan.findings.filter((finding) => !covers(finding)),
|
|
452
|
+
ledgered: scan.findings.filter(covers),
|
|
453
|
+
staleKeys: staleKeys.sort(),
|
|
454
|
+
staleSymbols: staleSymbols.sort(),
|
|
455
|
+
unreadable: input.surface.unreadable,
|
|
456
|
+
hostReachModules: scan.hostReachModules,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* The platform **source** file a specifier landing inside the member names, or
|
|
461
|
+
* `null`.
|
|
462
|
+
*
|
|
463
|
+
* The canonicalisation drops the member-relative path's **first segment** —
|
|
464
|
+
* whatever it is — and re-roots the remainder at the platform's source root. So
|
|
465
|
+
* `dist/lifecycle/manifest.js` and `src/lifecycle/manifest.ts` both canonicalise
|
|
466
|
+
* to `packages/platform/src/lifecycle/manifest.ts`, and the word `dist` appears
|
|
467
|
+
* nowhere: a build directory renamed in the package's own `tsconfig.build.json`
|
|
468
|
+
* arrives here by being renamed, not by anybody remembering this file (D-100).
|
|
469
|
+
*
|
|
470
|
+
* `null` for a specifier that lands inside the member and resolves to no source
|
|
471
|
+
* file. That is deliberate and is the contract's own ruling (§3): a relative
|
|
472
|
+
* specifier resolving to nothing inside the platform is not a host reach at all,
|
|
473
|
+
* and reporting it here would be this check answering `tsc`'s question. The
|
|
474
|
+
* risk it carries — a canonicalisation bug silently resolving everything to
|
|
475
|
+
* `null` — is not silent: the ledger is two-way, so a walk that stopped
|
|
476
|
+
* resolving reports every entry it holds as stale and exits 1.
|
|
477
|
+
*/
|
|
478
|
+
export function canonicalPlatformFile(joined, input) {
|
|
479
|
+
const prefix = `${input.platformMemberRoot}/`;
|
|
480
|
+
if (!joined.startsWith(prefix))
|
|
481
|
+
return null;
|
|
482
|
+
const withinMember = joined.slice(prefix.length);
|
|
483
|
+
const cut = withinMember.indexOf('/');
|
|
484
|
+
if (cut <= 0)
|
|
485
|
+
return null;
|
|
486
|
+
const rest = withinMember.slice(cut + 1);
|
|
487
|
+
if (rest.length === 0)
|
|
488
|
+
return null;
|
|
489
|
+
for (const candidate of resolutionCandidates(`${input.platformSourceRoot}/${rest}`)) {
|
|
490
|
+
if (input.files.has(candidate))
|
|
491
|
+
return candidate;
|
|
492
|
+
}
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
/** The bare specifier carrying a platform file, or `null` when none does. */
|
|
496
|
+
function publishedSpecifierFor(target, input) {
|
|
497
|
+
const carriers = input.surface.publishedBy.get(target) ?? new Set();
|
|
498
|
+
for (const [subpath, barrel] of input.host.subpathTargets) {
|
|
499
|
+
// Reaching a barrel *is* reaching its subpath, whole or not.
|
|
500
|
+
if (barrel === target || carriers.has(barrel))
|
|
501
|
+
return `${input.host.name}/${subpath}`;
|
|
502
|
+
}
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Every application reach into the platform written as a relative path.
|
|
507
|
+
*
|
|
508
|
+
* Pure over source text, file keys, barrel-derived surface and the two platform
|
|
509
|
+
* roots — so a fixture enters exactly where a run does, specifier extraction and
|
|
510
|
+
* `.js` → `.ts` resolution included (issue #130, and the `check-entry-scope`
|
|
511
|
+
* fixture that entered below its own classifier is the cautionary example).
|
|
512
|
+
*
|
|
513
|
+
* **A bare specifier into the host produces no finding here, and that is the
|
|
514
|
+
* discrimination the rule turns on.** `@endora-commerce/platform/kernel` and
|
|
515
|
+
* `@endora-commerce/platform/composition` are both correct from the application:
|
|
516
|
+
* the first is published and the second is the host-internal address the host is
|
|
517
|
+
* entitled to (D-160.14). The three-way answer for a bare specifier is a
|
|
518
|
+
* *module's* — `resolveHostSpecifier` in {@link scanPlatformSurface} — and asking
|
|
519
|
+
* it twice, with two populations and two verdicts, is two answers waiting to
|
|
520
|
+
* disagree.
|
|
521
|
+
*/
|
|
522
|
+
export function scanApplicationReaches(input) {
|
|
523
|
+
const findings = [];
|
|
524
|
+
let reaches = 0;
|
|
525
|
+
for (const [file, text] of [...input.sources].sort(([a], [b]) => a.localeCompare(b))) {
|
|
526
|
+
for (const specifier of namedSpecifiers(text, file)) {
|
|
527
|
+
if (!specifier.text.startsWith('.'))
|
|
528
|
+
continue;
|
|
529
|
+
const joined = resolveRelative(file, specifier.text);
|
|
530
|
+
if (joined === null)
|
|
531
|
+
continue;
|
|
532
|
+
const target = canonicalPlatformFile(joined, input);
|
|
533
|
+
if (target === null)
|
|
534
|
+
continue;
|
|
535
|
+
reaches += 1;
|
|
536
|
+
findings.push({
|
|
537
|
+
kind: 'relative-host-reach',
|
|
538
|
+
file,
|
|
539
|
+
line: specifier.line,
|
|
540
|
+
moduleId: null,
|
|
541
|
+
target,
|
|
542
|
+
symbol: NO_SYMBOL,
|
|
543
|
+
specifier: specifier.text,
|
|
544
|
+
publishedAs: publishedSpecifierFor(target, input),
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
findings.sort((a, b) => a.file === b.file ? a.target.localeCompare(b.target) : a.file.localeCompare(b.file));
|
|
549
|
+
return { reaches, findings };
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* The application's reaches, judged against the ledger — both directions
|
|
553
|
+
* (host-reach-check.md §4 R4.3).
|
|
554
|
+
*
|
|
555
|
+
* One reach may appear twice under one key when a file names the same platform
|
|
556
|
+
* file from two specifiers; the ledger is keyed `(file, target)` and covers
|
|
557
|
+
* both, which is right: the entry's subject is the coupling, not the line.
|
|
558
|
+
*/
|
|
559
|
+
export function checkApplicationReaches(input, ledger) {
|
|
560
|
+
const scan = scanApplicationReaches(input);
|
|
561
|
+
const seen = new Set(scan.findings.map(keyOf));
|
|
562
|
+
const covers = (finding) => ledger[keyOf(finding)] !== undefined;
|
|
563
|
+
return {
|
|
564
|
+
...scan,
|
|
565
|
+
violations: scan.findings.filter((finding) => !covers(finding)),
|
|
566
|
+
ledgered: scan.findings.filter(covers),
|
|
567
|
+
staleKeys: Object.keys(ledger)
|
|
568
|
+
.filter((key) => !seen.has(key))
|
|
569
|
+
.sort(),
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* The coverage floor for the application half: the ledger's own still-on-disk
|
|
574
|
+
* file set, against what the walk opened of it.
|
|
575
|
+
*
|
|
576
|
+
* host-reach-check.md §5's fourth refusal, and it is `check:module-boundary`'s
|
|
577
|
+
* admin-host floor one package over — a floor derived from *the ledger* rather
|
|
578
|
+
* than from a count, so a walk that stopped reaching the files it is ledgered
|
|
579
|
+
* over is a refusal rather than a drained ledger. The ledger is a second
|
|
580
|
+
* author's answer to "which application files reach the platform": it was
|
|
581
|
+
* written by whoever measured the debt, and it goes stale loudly rather than
|
|
582
|
+
* quietly.
|
|
583
|
+
*
|
|
584
|
+
* `null` rather than `expected: 0` once the ledger empties, which is what this
|
|
585
|
+
* ledger is for: an expectation of zero is itself a refusal in this grammar
|
|
586
|
+
* (`readSizeRefusal`'s `no-expectation`), and rightly — a floor that expects
|
|
587
|
+
* nothing is switched off.
|
|
588
|
+
*/
|
|
589
|
+
export function hostReachCoverage(ledger, onDisk, opened) {
|
|
590
|
+
const ledgeredFiles = new Set(Object.keys(ledger).map((key) => key.split('|')[0] ?? ''));
|
|
591
|
+
const expected = [...ledgeredFiles].filter((file) => onDisk(file));
|
|
592
|
+
if (expected.length === 0)
|
|
593
|
+
return null;
|
|
594
|
+
return {
|
|
595
|
+
source: 'host-reaches',
|
|
596
|
+
expected: expected.length,
|
|
597
|
+
covered: expected.filter((file) => opened.has(file)).length,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Why this run may not report on the application's reaches, or `null`.
|
|
602
|
+
*
|
|
603
|
+
* Two of host-reach-check.md §5's four refusals — the two that are facts about
|
|
604
|
+
* *this* half's inputs. The first ("no platform root") is already this check's
|
|
605
|
+
* own first refusal and stays there; the fourth is the ledger-derived coverage
|
|
606
|
+
* floor and belongs to `read-size.ts`, which is the one place in the estate that
|
|
607
|
+
* owns a short walk.
|
|
608
|
+
*
|
|
609
|
+
* Both fail in the direction that produces a **clean** result, which is why they
|
|
610
|
+
* are exit 2 and not a finding. An empty canonical-target map reports every
|
|
611
|
+
* relative reach as reaching nothing; an application walk that opened no file
|
|
612
|
+
* reports no reach at all — and this check's module half would go on printing
|
|
613
|
+
* `files=2069` beside it, because 1364 of `backend/src`'s files are a module's
|
|
614
|
+
* and a broken *application* walk leaves every one of those numbers healthy
|
|
615
|
+
* (issue #215's shape over this population).
|
|
616
|
+
*/
|
|
617
|
+
export function applicationReachRefusal(input) {
|
|
618
|
+
if (input.canonicalTargets === 0) {
|
|
619
|
+
return ('the platform walk produced no source file — a relative reach into the platform is ' +
|
|
620
|
+
'canonicalised against that walk, so every one of them would resolve to nothing and ' +
|
|
621
|
+
'the application half would report clean over a population it never saw');
|
|
622
|
+
}
|
|
623
|
+
if (input.applicationFiles === 0) {
|
|
624
|
+
return ('the application walk opened no file — the module half of this check reads most of ' +
|
|
625
|
+
"`backend/src` and would keep printing a healthy `files=`, so a broken application " +
|
|
626
|
+
'walk is a silence rather than a number that moved (issue #215)');
|
|
627
|
+
}
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Why this run may not judge a reach against the surface it read, or `null`.
|
|
632
|
+
*
|
|
633
|
+
* Pure over the two things that can be silently missing — a barrel that is not
|
|
634
|
+
* there, and a re-export the parse cannot enumerate — because both fail in the
|
|
635
|
+
* same direction and it is the wrong one: a **short** published set turns
|
|
636
|
+
* correct reaches into findings, and the obvious repair for one of those is to
|
|
637
|
+
* widen the barrel. So it is exit 2, in the idiom of `readSizeRefusal`, and a
|
|
638
|
+
* proof enters where a run enters (issue #130).
|
|
639
|
+
*/
|
|
640
|
+
export function platformSurfaceRefusal(input) {
|
|
641
|
+
if (input.missingBarrels.length > 0) {
|
|
642
|
+
return (`${input.missingBarrels.join(', ')} — a published subpath (D-160.7) whose barrel is ` +
|
|
643
|
+
'not there. The published surface would come back short and every reach into that ' +
|
|
644
|
+
'directory would read as a violation; refusing to report on it');
|
|
645
|
+
}
|
|
646
|
+
if (input.surface.unreadable.length > 0) {
|
|
647
|
+
const named = input.surface.unreadable
|
|
648
|
+
.map((entry) => `\n - ${entry.barrel}:${entry.line} ${entry.reason}`)
|
|
649
|
+
.join('');
|
|
650
|
+
return ('a published barrel holds a re-export this parse cannot enumerate, so the published ' +
|
|
651
|
+
'surface would come back short — and the obvious "repair" for a reach it wrongly ' +
|
|
652
|
+
`refused is to widen the barrel:${named}`);
|
|
653
|
+
}
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Every TypeScript source under `dir`, with the rule's own prunes applied.
|
|
658
|
+
*
|
|
659
|
+
* Exported because the population is part of the rule: two hosts computing
|
|
660
|
+
* "which files this check reads" two ways is the shape that lets one of them go
|
|
661
|
+
* half-blind.
|
|
662
|
+
*/
|
|
663
|
+
export function collectPlatformSurfaceSources(dir, out = []) {
|
|
664
|
+
if (!existsSync(dir))
|
|
665
|
+
return out;
|
|
666
|
+
for (const name of readdirSync(dir)) {
|
|
667
|
+
const full = join(dir, name);
|
|
668
|
+
if (statSync(full).isDirectory()) {
|
|
669
|
+
if (name === 'node_modules' || name === 'dist')
|
|
670
|
+
continue;
|
|
671
|
+
collectPlatformSurfaceSources(full, out);
|
|
672
|
+
}
|
|
673
|
+
else if (
|
|
674
|
+
// `.tsx` since feature 091: a module package's `./admin` layer is React
|
|
675
|
+
// components, and a walk that cannot see them reports every relative
|
|
676
|
+
// reach into one as `unresolvable-reach` — a finding about the walk.
|
|
677
|
+
(name.endsWith('.ts') || name.endsWith('.tsx')) &&
|
|
678
|
+
!name.endsWith('.test.ts') &&
|
|
679
|
+
!name.endsWith('.test.tsx') &&
|
|
680
|
+
!name.endsWith('.d.ts')) {
|
|
681
|
+
out.push(full);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return out;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* A module package's own tooling configuration, which is not module source.
|
|
688
|
+
*
|
|
689
|
+
* `vitest.config.ts` is the first `.ts` file a module package holds that its
|
|
690
|
+
* build does not compile (feature 089, Phase 1): `tsconfig.build.json` roots the
|
|
691
|
+
* emit at `src/`, and the manifest's `files` ships `dist` and the asset
|
|
692
|
+
* directories, so nothing a consumer installs contains it and no `exports`
|
|
693
|
+
* subpath can name it. The other configurations a package carries are `.json`
|
|
694
|
+
* and were therefore never in this walk at all.
|
|
695
|
+
*
|
|
696
|
+
* It has to leave the population rather than be made to resolve, because the
|
|
697
|
+
* file exists to `mergeConfig` the repository root's `vitest.config.base.ts` —
|
|
698
|
+
* which is where issue #255's foreign-workspace-link refusal lives, so the reach
|
|
699
|
+
* is mandatory — and that root is in no module walk root and no source root. A
|
|
700
|
+
* reach the walk cannot resolve is `unresolvable-reach`, deliberately fail-closed
|
|
701
|
+
* (#215 one layer in), and the honest answer here is that this file is not a
|
|
702
|
+
* module reach at all.
|
|
703
|
+
*
|
|
704
|
+
* Narrow on purpose: only a `*.config.ts` sitting **directly** at a module walk
|
|
705
|
+
* root, which is a package's own root or a module directory. A `config.ts` under
|
|
706
|
+
* `src/` stays module source, and a directory named `config/` is untouched.
|
|
707
|
+
*/
|
|
708
|
+
export function isPackageToolingConfig(root, file) {
|
|
709
|
+
const within = relative(root, file).split('\\').join('/');
|
|
710
|
+
return !within.includes('/') && within.endsWith('.config.ts');
|
|
711
|
+
}
|
|
712
|
+
/** The remedy sentence a finding gets, by kind. */
|
|
713
|
+
/**
|
|
714
|
+
* The remedy paragraph for a finding.
|
|
715
|
+
*
|
|
716
|
+
* Exported because the remedy is part of the rule and not part of a host: an
|
|
717
|
+
* author outside this checkout reads the same sentence this repository's run
|
|
718
|
+
* prints.
|
|
719
|
+
*/
|
|
720
|
+
export function remedyOf(finding) {
|
|
721
|
+
switch (finding.kind) {
|
|
722
|
+
case 'unpublished-symbol':
|
|
723
|
+
return `\`${finding.symbol}\` is not published out of ${finding.target}`;
|
|
724
|
+
case 'whole-file-reach':
|
|
725
|
+
return `reaches every export of ${finding.target}, internals included`;
|
|
726
|
+
case 'unresolvable-reach':
|
|
727
|
+
return `\`${finding.specifier}\` resolves to no file the walk found`;
|
|
728
|
+
case 'unattributed-source':
|
|
729
|
+
return 'the walk opened this file and no module owns it';
|
|
730
|
+
case 'unpublished-subpath':
|
|
731
|
+
return (`\`${finding.specifier}\` names no subpath the host publishes — its \`exports\` map ` +
|
|
732
|
+
'refuses the path at resolution time');
|
|
733
|
+
case 'relative-host-reach':
|
|
734
|
+
return (`\`${finding.specifier}\` names ${finding.target} by relative path. ` +
|
|
735
|
+
(finding.publishedAs === null || finding.publishedAs === undefined
|
|
736
|
+
? 'the platform declares no subpath carrying this file — the remedy is to declare ' +
|
|
737
|
+
'one (host-internal unless a module needs it) or to stop reaching it'
|
|
738
|
+
: `the address is \`${finding.publishedAs}\``) +
|
|
739
|
+
'. A relative path into the package resolves in this checkout and in no instance, ' +
|
|
740
|
+
'which is what stops a build made of published packages from working');
|
|
741
|
+
case 'host-internal-subpath':
|
|
742
|
+
return (`\`${finding.specifier}\` names a subpath the host declares for its own composition ` +
|
|
743
|
+
'and publishes to nobody (host-package.md §2.7). It resolves, which is why this is ' +
|
|
744
|
+
'the one finding here that neither `node` nor `tsc` would raise — and no module may ' +
|
|
745
|
+
'name it, production source or test alike. A server-bound test composes through the ' +
|
|
746
|
+
'test kit, never through `composeModules`');
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
//# sourceMappingURL=platform-surface.js.map
|