@starci/hfs 4.7.32 → 4.7.34
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/CHANGELOG.md +10 -0
- package/README.md +13 -6
- package/emit/contracts.mjs +4 -3
- package/emit/form-bounds-reader.ts.partial +58 -0
- package/emit/form-bounds.mjs +155 -0
- package/emit/graphql-input-bounds.mjs +97 -0
- package/emit/input-bounds.mjs +44 -0
- package/emit/operation-schemas.mjs +137 -0
- package/emit/operations-worker.mjs +2 -2
- package/emit/operations.mjs +52 -16
- package/emit/schema-worker.mjs +17 -4
- package/emit/type-schema.mjs +17 -5
- package/package.json +9 -2
- package/release/gate.mjs +18 -23
- package/release/index.mjs +1 -1
- package/release/push.mjs +4 -5
- package/runtime/engine/admission.mjs +331 -0
- package/runtime/engine/canonical-json.mjs +14 -0
- package/runtime/engine/db/blob.mjs +380 -0
- package/runtime/engine/db/event-compact.mjs +73 -0
- package/runtime/engine/db/ledger-open.mjs +38 -0
- package/runtime/engine/db/ledger-paths.mjs +87 -0
- package/runtime/engine/db/ledger.mjs +1068 -0
- package/runtime/engine/db/machine-connection.mjs +150 -0
- package/runtime/engine/db/machine-open.mjs +60 -0
- package/runtime/engine/db/machine-schema.mjs +105 -0
- package/runtime/engine/db/machine.mjs +1317 -0
- package/runtime/engine/db/provider-reservations.mjs +127 -0
- package/runtime/engine/db/provision-authority.mjs +76 -0
- package/runtime/engine/db/provision-lock.mjs +29 -0
- package/runtime/engine/db/provision-operations.mjs +146 -0
- package/runtime/engine/db/ref-value.mjs +67 -0
- package/runtime/engine/db/schema/machine.sql +920 -0
- package/runtime/engine/db/schema/runtime.sql +1113 -0
- package/runtime/engine/refuse.mjs +11 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +65 -9
- package/runtime/knowledge/hfs/design-truths.yaml +149 -0
- package/runtime/knowledge/hfs/fe-stack.yaml +23 -5
- package/runtime/knowledge/hfs/rules.yaml +269 -48
- package/runtime/knowledge/hfs/slots.yaml +113 -53
- package/runtime/knowledge/patterns/be/api.yaml +46 -20
- package/runtime/knowledge/patterns/be/event-bus.yaml +19 -12
- package/runtime/knowledge/patterns/be/idempotency.yaml +265 -0
- package/runtime/knowledge/patterns/be/jobs.yaml +17 -12
- package/runtime/knowledge/patterns/be/projections.yaml +2 -2
- package/runtime/knowledge/patterns/be/queues.yaml +57 -22
- package/runtime/knowledge/patterns/be/reactors.yaml +1 -1
- package/runtime/knowledge/patterns/be/realtime.yaml +326 -27
- package/runtime/knowledge/patterns/be/saga.yaml +2 -2
- package/runtime/knowledge/sonar-gate.yaml +1 -1
- package/runtime/modules/kernel/command-policy.yaml +249 -0
- package/runtime/modules/kernel/failure-codes.yaml +211 -52
- package/runtime/modules/kernel/hook-input.yaml +6 -0
- package/runtime/modules/kernel/protected-zone.yaml +81 -0
- package/runtime/modules/kernel/removed-vocabulary.yaml +62 -0
- package/runtime/modules/kernel/roles.yaml +405 -0
- package/runtime/modules/models/registry.yaml +3 -1
- package/runtime/modules/models/runtimes.yaml +2 -0
- package/runtime/modules/schemas/application-stacks.schema.yaml +10 -2
- package/runtime/packages/cli/src/catalog.generated.mjs +46 -59
- package/runtime/packages/grammar/scripts/build-stamp.mjs +98 -0
- package/runtime/scripts/agent/codex-app-server.mjs +79 -0
- package/runtime/scripts/agent/codex-mirror-source.mjs +34 -0
- package/runtime/scripts/agent/host-agents.mjs +80 -0
- package/runtime/scripts/agent/launch-trust-policy.mjs +53 -0
- package/runtime/scripts/agent/trust-launch.mjs +191 -0
- package/runtime/scripts/agent/trust.mjs +523 -0
- package/runtime/scripts/api/fs/rename-over.mjs +21 -0
- package/runtime/scripts/api/fs/temp-path.mjs +8 -0
- package/runtime/scripts/api/fs/write-json-file.mjs +10 -0
- package/runtime/scripts/api/git/diff-names.mjs +8 -0
- package/runtime/scripts/api/git/head-time.mjs +10 -0
- package/runtime/scripts/api/git/index-lock.mjs +78 -0
- package/runtime/scripts/api/git/is-ancestor.mjs +5 -0
- package/runtime/scripts/api/git/log.mjs +5 -0
- package/runtime/scripts/api/git/merge-base-query.mjs +5 -0
- package/runtime/scripts/api/git/merge-tree.mjs +5 -0
- package/runtime/scripts/api/git/rev-list.mjs +5 -0
- package/runtime/scripts/api/git/rev-parse.mjs +13 -0
- package/runtime/scripts/api/git/status-query.mjs +5 -0
- package/runtime/scripts/api/git/worktree-list-query.mjs +5 -0
- package/runtime/scripts/api/node/spawn-node.mjs +6 -0
- package/runtime/scripts/api/npm/dist-tag.mjs +8 -0
- package/runtime/scripts/api/npm/lib.mjs +32 -0
- package/runtime/scripts/api/npm/pack-dry-run.mjs +9 -0
- package/runtime/scripts/api/npm/run-npm.mjs +6 -0
- package/runtime/scripts/api/npm/view.mjs +30 -0
- package/runtime/scripts/api/npm/whoami.mjs +8 -0
- package/runtime/scripts/api/process/hide-child-windows.mjs +31 -0
- package/runtime/scripts/api/process/kill-tree.mjs +22 -0
- package/runtime/scripts/api/process/lib.mjs +125 -0
- package/runtime/scripts/api/process/probe-guard-command.mjs +74 -0
- package/runtime/scripts/api/process/process-list.mjs +28 -0
- package/runtime/scripts/api/process/system-tool.mjs +37 -0
- package/runtime/scripts/gates/canon-digest.mjs +81 -0
- package/runtime/scripts/gates/canon-pins.mjs +16 -0
- package/runtime/scripts/gates/commit-proof.mjs +53 -0
- package/runtime/scripts/gates/gate-input.mjs +98 -0
- package/runtime/scripts/gates/gate.mjs +669 -0
- package/runtime/scripts/gates/grammar-dist.mjs +206 -0
- package/runtime/scripts/gates/runtime-package-selection.mjs +27 -0
- package/runtime/scripts/gates/test-world-run.mjs +254 -0
- package/runtime/scripts/gates/type-impact.mjs +277 -0
- package/runtime/scripts/gates/type-program.mjs +113 -0
- package/runtime/scripts/guards/assigned-command.mjs +12 -0
- package/runtime/scripts/guards/call-rights.mjs +47 -0
- package/runtime/scripts/guards/command-guard.mjs +468 -0
- package/runtime/scripts/guards/command-policy.mjs +342 -0
- package/runtime/scripts/guards/critic-reach.mjs +82 -0
- package/runtime/scripts/guards/deps-guard.mjs +150 -0
- package/runtime/scripts/guards/env-dump-verdict.mjs +89 -0
- package/runtime/scripts/guards/git-pathspec.mjs +225 -0
- package/runtime/scripts/guards/git-policy.mjs +355 -0
- package/runtime/scripts/guards/guards-root.mjs +27 -0
- package/runtime/scripts/guards/hook-install.mjs +343 -0
- package/runtime/scripts/guards/hook-io.mjs +76 -0
- package/runtime/scripts/guards/hook-verdict.mjs +12 -0
- package/runtime/scripts/guards/install-verdict.mjs +41 -0
- package/runtime/scripts/guards/kernel-seat.mjs +63 -0
- package/runtime/scripts/guards/launch-verdict.mjs +74 -0
- package/runtime/scripts/guards/op-starci.mjs +16 -0
- package/runtime/scripts/guards/op-write-scope.mjs +27 -0
- package/runtime/scripts/guards/orca-self-lifecycle.mjs +39 -0
- package/runtime/scripts/guards/process-kill-verdict.mjs +106 -0
- package/runtime/scripts/guards/protected-zone.mjs +56 -0
- package/runtime/scripts/guards/refusals.mjs +27 -0
- package/runtime/scripts/guards/release-definition.mjs +185 -0
- package/runtime/scripts/guards/release-push-gate.mjs +72 -0
- package/runtime/scripts/guards/release-record.mjs +58 -0
- package/runtime/scripts/guards/release-suite-mode.mjs +15 -0
- package/runtime/scripts/guards/rights.mjs +268 -0
- package/runtime/scripts/guards/seat-tools.mjs +47 -0
- package/runtime/scripts/guards/shell-commands.mjs +328 -0
- package/runtime/scripts/guards/supervisor-seat.mjs +80 -0
- package/runtime/scripts/guards/verdict-process.mjs +81 -0
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +10 -10
- package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +1 -1
- package/runtime/scripts/hfs/architecture/operation-contract-roots.mjs +53 -0
- package/runtime/scripts/hfs/architecture/reachability.mjs +1 -1
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +17 -6
- package/runtime/scripts/hfs/architecture/sql-token-forms.mjs +105 -0
- package/runtime/scripts/hfs/architecture/sql-tokens.mjs +38 -52
- package/runtime/scripts/hfs/architecture/supabase-keys.mjs +261 -0
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +49 -3
- package/runtime/scripts/hfs/architecture/typescript.mjs +1 -1
- package/runtime/scripts/hfs/check.mjs +6 -4
- package/runtime/scripts/hfs/coverage-scope.mjs +28 -1
- package/runtime/scripts/hfs/db-restamp-verb.mjs +56 -16
- package/runtime/scripts/hfs/declaration-shape.mjs +4 -4
- package/runtime/scripts/hfs/declaration-slots.mjs +14 -3
- package/runtime/scripts/hfs/manifest-shape.mjs +10 -6
- package/runtime/scripts/hfs/rule-params-shape.mjs +18 -6
- package/runtime/scripts/hfs/rules/a15-findings.mjs +4 -1
- package/runtime/scripts/hfs/rules/bypass-markers.mjs +22 -7
- package/runtime/scripts/hfs/rules/database-migrations.mjs +69 -14
- package/runtime/scripts/hfs/rules/database-queue-sources.mjs +9 -7
- package/runtime/scripts/hfs/rules/database-queues.mjs +68 -18
- package/runtime/scripts/hfs/rules/edition.mjs +56 -12
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +1 -10
- package/runtime/scripts/hfs/rules/fe-stack.mjs +12 -7
- package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +1 -1
- package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +75 -9
- package/runtime/scripts/hfs/rules/stacks-runtime.mjs +1 -1
- package/runtime/scripts/hfs/rules/stacks-swarm.mjs +15 -4
- package/runtime/scripts/hfs/rules/text-integrity.mjs +73 -0
- package/runtime/scripts/hfs/runtime-rules/hook-shape.mjs +74 -0
- package/runtime/scripts/hfs/runtime-rules/release-notes.mjs +44 -0
- package/runtime/scripts/hfs/sealed-members.mjs +3 -3
- package/runtime/scripts/hfs/slot-side-problems.mjs +3 -3
- package/runtime/scripts/hfs/slots.mjs +2 -1
- package/runtime/scripts/hfs/stack-deploy-config.mjs +22 -5
- package/runtime/scripts/hfs/stacks-gate.mjs +28 -7
- package/runtime/scripts/hfs/view.mjs +2 -1
- package/runtime/scripts/lib/app-data.mjs +11 -0
- package/runtime/scripts/lib/bypass-markers.mjs +272 -75
- package/runtime/scripts/lib/check-format.mjs +7 -0
- package/runtime/scripts/lib/cli-arg.mjs +120 -0
- package/runtime/scripts/lib/css-rules.mjs +220 -0
- package/runtime/scripts/lib/dom-tree.mjs +4 -0
- package/runtime/scripts/lib/escape.mjs +16 -0
- package/runtime/scripts/lib/event-items.mjs +22 -0
- package/runtime/scripts/lib/failure-code-template-errors.mjs +161 -0
- package/runtime/scripts/lib/git-dir.mjs +120 -0
- package/runtime/scripts/lib/grammar-vendor-parity.mjs +377 -0
- package/runtime/scripts/lib/guard-command.mjs +24 -0
- package/runtime/scripts/lib/hook-limits.mjs +5 -0
- package/runtime/scripts/lib/interrupt-cleanup.mjs +38 -0
- package/runtime/scripts/lib/js-lex.mjs +200 -0
- package/runtime/scripts/lib/json-formats.mjs +0 -1
- package/runtime/scripts/lib/language.mjs +94 -1
- package/runtime/scripts/lib/loosening-budgets.mjs +44 -0
- package/runtime/scripts/lib/loosening-config.mjs +361 -0
- package/runtime/scripts/lib/loosening-state.mjs +223 -0
- package/runtime/scripts/lib/menu-parts.mjs +11 -0
- package/runtime/scripts/lib/normalize.mjs +25 -0
- package/runtime/scripts/lib/op-gate.mjs +15 -0
- package/runtime/scripts/lib/redact.mjs +208 -0
- package/runtime/scripts/lib/runtime-location.mjs +21 -0
- package/runtime/scripts/lib/runtime-package-inventory.mjs +21 -0
- package/runtime/scripts/lib/runtime-package-manifests.mjs +84 -0
- package/runtime/scripts/lib/runtime-publication-tags.mjs +99 -0
- package/runtime/scripts/lib/seat-env.mjs +43 -0
- package/runtime/scripts/lib/secret-patterns.mjs +58 -9
- package/runtime/scripts/lib/shell-source.mjs +4 -0
- package/runtime/scripts/lib/source-phrases.mjs +34 -0
- package/runtime/scripts/lib/sqlite.mjs +21 -0
- package/runtime/scripts/lib/swarm-stack.mjs +2 -1
- package/runtime/scripts/lib/tar-files.mjs +39 -0
- package/runtime/scripts/lib/ts-analysis.mjs +10 -0
- package/runtime/scripts/lib/worktree-exclude.mjs +35 -0
- package/runtime/scripts/lib/worktree-record.mjs +14 -0
- package/runtime/scripts/machine/artifact-hold.mjs +52 -0
- package/runtime/scripts/machine/ask-receipts.mjs +113 -0
- package/runtime/scripts/machine/evidence-store.mjs +223 -0
- package/runtime/scripts/machine/home.mjs +216 -0
- package/runtime/scripts/machine/host-lock.mjs +184 -0
- package/runtime/scripts/machine/lock-recovery.mjs +55 -0
- package/runtime/scripts/machine/roles-contract.mjs +90 -0
- package/runtime/scripts/machine/runtime-change.mjs +20 -0
- package/runtime/scripts/machine/workflow-tree.mjs +122 -0
- package/runtime/scripts/reconciler/checklist-items.mjs +7 -0
- package/runtime/scripts/reconciler/guard-row.mjs +25 -0
- package/runtime/scripts/work/asset-slot.mjs +284 -0
- package/runtime/scripts/work/brand/brand-colour.mjs +125 -0
- package/runtime/scripts/work/brand/brand-tokens.mjs +167 -0
- package/runtime/scripts/work/brand/brand-value-source.mjs +106 -0
- package/runtime/scripts/work/brand/brand.mjs +892 -0
- package/runtime/scripts/work/draw/draw-dna-anatomy.mjs +60 -0
- package/runtime/scripts/work/draw/draw-dna-findings.mjs +256 -0
- package/runtime/scripts/work/draw/draw-dna-selector.mjs +38 -0
- package/runtime/scripts/work/draw/draw-dna.mjs +483 -0
- package/runtime/scripts/work/draw/draw-layer.mjs +305 -0
- package/runtime/scripts/work/draw/draw-rationale-page.mjs +237 -0
- package/runtime/scripts/work/draw/draw-rationale-ref.mjs +14 -0
- package/runtime/scripts/work/draw/draw-rationale.mjs +353 -0
- package/runtime/scripts/work/draw/draw-source.mjs +412 -0
- package/runtime/scripts/work/draw/draw-taste.mjs +278 -0
- package/runtime/scripts/work/draw/draw-typecheck.mjs +72 -0
- package/runtime/scripts/work/draw/rationale-findings.mjs +205 -0
- package/runtime/scripts/work/draw-grammar.mjs +175 -0
- package/runtime/scripts/work/draw-render-page.mjs +188 -0
- package/runtime/scripts/work/draw-render.mjs +626 -0
- package/runtime/scripts/work/grammar-proposal.mjs +279 -0
- package/runtime/scripts/work/png.mjs +237 -0
- package/runtime/scripts/work/render-tools.mjs +64 -0
- package/runtime/scripts/work/work-io.mjs +119 -0
- package/scaffold/add-inbox-lite.mjs +2 -2
- package/scaffold/add-plan.mjs +40 -13
- package/scaffold/add-queue-lite.mjs +372 -0
- package/scaffold/add.mjs +5 -2
- package/scaffold/app.mjs +1 -2
- package/scaffold/edition-gate.mjs +11 -7
- package/scaffold/lite-exports.mjs +10 -0
- package/scaffold/pattern-file-slots.mjs +33 -0
- package/scaffold/test-world.mjs +1 -1
- package/scaffold/yaml-block-edit.mjs +117 -0
- package/src/main.mjs +5 -4
- package/stack/deploy.mjs +7 -3
- package/stack/post-deploy.mjs +48 -0
- package/sync/index.mjs +10 -0
- package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +1 -1
- package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +1 -1
- package/templates/app/codegen-graphql.mjs.partial +59 -12
- package/templates/app/hooks/husky/pre-commit +2 -2
- package/templates/app/hooks/husky/pre-push +10 -3
- package/templates/app/package-scripts/redis-dev.json +3 -0
- package/templates/app/package-scripts-lite/package.json +1 -0
- package/templates/app/skeleton/.starcistacks/application-stacks.yaml +36 -8
- package/templates/app/skeleton/.starcistacks/dev/README.md +1 -1
- package/templates/app/skeleton/.starcistacks/production/README.md +10 -9
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/postgres-init.sh +21 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +1 -1
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +1 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +47 -5
- package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +5 -3
- package/templates/app/skeleton/README.md +2 -0
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +5 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +2 -2
- package/templates/app/skeleton-lite/.starcistacks/production/README.md +1 -1
- package/templates/app/skeleton-lite/README.md +1 -1
- package/templates/be/inbox/platform/client-operation-outcomes.sql.tpl +18 -0
- package/templates/be/inbox/platform/event-bus/errors/event-bus.error.ts.tpl +16 -0
- package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +10 -0
- package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +1 -1
- package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +15 -3
- package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +11 -3
- package/templates/be/inbox/platform/event-bus/index.ts.tpl +2 -0
- package/templates/be/inbox/platform/event-bus/messages/event-bus.messages.ts.tpl +7 -0
- package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +2 -2
- package/templates/be/inbox/platform/idempotency/errors/idempotency.error.ts.tpl +19 -0
- package/templates/be/inbox/platform/idempotency/index.ts.tpl +2 -0
- package/templates/be/inbox/platform/idempotency/messages/idempotency.messages.ts.tpl +13 -0
- package/templates/be/inbox/platform/inbox/client-operation.policy.spec.ts.tpl +32 -0
- package/templates/be/inbox/platform/inbox/client-operation.policy.ts.tpl +17 -0
- package/templates/be/inbox/platform/inbox/errors/inbox.error.ts.tpl +22 -0
- package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +21 -0
- package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +334 -11
- package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +55 -4
- package/templates/be/inbox/platform/inbox/index.ts.tpl +2 -0
- package/templates/be/inbox/platform/inbox/messages/inbox.messages.ts.tpl +15 -0
- package/templates/be/inbox/platform/inbox/persistence/inbox.rows.ts.tpl +33 -0
- package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +26 -0
- package/templates/be/inbox/platform/inbox/request-fingerprint.policy.spec.ts.tpl +76 -0
- package/templates/be/inbox/platform/inbox/request-fingerprint.policy.ts.tpl +36 -0
- package/templates/be/inbox/platform/migration.sql.tpl +1 -0
- package/templates/be/inbox/service.spec.ts.tpl +4 -4
- package/templates/be/inbox/service.ts.tpl +3 -1
- package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -2
- package/templates/be/inbox/webhook-door.ts.tpl +3 -2
- package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +3 -0
- package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +10 -0
- package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +2 -2
- package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +16 -3
- package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +11 -3
- package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +139 -50
- package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +25 -2
- package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +139 -78
- package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +10 -6
- package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +262 -159
- package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +32 -5
- package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +2 -0
- package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +5 -1
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +2 -0
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +4 -4
- package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +1 -0
- package/templates/be/patterns/idempotency/platform/idempotency/errors/idempotency.error.ts.tpl +19 -0
- package/templates/be/patterns/idempotency/platform/idempotency/index.ts.tpl +2 -0
- package/templates/be/patterns/idempotency/platform/idempotency/messages/idempotency.messages.ts.tpl +13 -0
- package/templates/be/patterns/idempotency/platform/inbox/client-operation.policy.spec.ts.tpl +32 -0
- package/templates/be/patterns/idempotency/platform/inbox/client-operation.policy.ts.tpl +17 -0
- package/templates/be/patterns/idempotency/platform/inbox/client-outcome-backfill.client.spec.ts.tpl +66 -0
- package/templates/be/patterns/idempotency/platform/inbox/client-outcome-backfill.client.ts.tpl +24 -0
- package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-backfill.client.spec.ts.tpl +60 -0
- package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-backfill.client.ts.tpl +12 -0
- package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-release.client.spec.ts.tpl +49 -0
- package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-release.client.ts.tpl +12 -0
- package/templates/be/patterns/idempotency/platform/inbox/effectful.decorators.ts.tpl +7 -0
- package/templates/be/patterns/idempotency/platform/inbox/errors/inbox.error.ts.tpl +22 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.decorators.ts.tpl +17 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +7 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.module.ts.tpl +28 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.options.ts.tpl +7 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.port.ts.tpl +44 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.service.spec.ts.tpl +388 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.service.ts.tpl +77 -0
- package/templates/be/patterns/idempotency/platform/inbox/index.ts.tpl +10 -0
- package/templates/be/patterns/idempotency/platform/inbox/messages/inbox.messages.ts.tpl +15 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/connection.ts.tpl +9 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/entities/inbox-claim.entity.ts.tpl +25 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.rows.ts.tpl +33 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.sql.ts.tpl +39 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/migrations/1789800005000-create-inbox-claims.ts.tpl +21 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/migrations/1792800000000-client-operation-outcomes.ts.tpl +21 -0
- package/templates/be/patterns/idempotency/platform/inbox/request-fingerprint.policy.spec.ts.tpl +76 -0
- package/templates/be/patterns/idempotency/platform/inbox/request-fingerprint.policy.ts.tpl +36 -0
- package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +1 -0
- package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +4 -3
- package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +2 -0
- package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +20 -2
- package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +1 -0
- package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +4 -0
- package/templates/be/patterns/jobs/platform-lite/jobs.index.ts.tpl +10 -0
- package/templates/be/patterns/jobs/platform-lite/jobs.sql.tpl +44 -0
- package/templates/be/patterns/jobs/platform-lite/persistence.jobs.sql.ts.tpl +33 -0
- package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +203 -18
- package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +78 -22
- package/templates/be/patterns/queues/platform/errors/queue.error.ts.tpl +19 -0
- package/templates/be/patterns/queues/platform/messages/queue.messages.ts.tpl +13 -0
- package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +1 -0
- package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +4 -0
- package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +2 -0
- package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +4 -4
- package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +14 -2
- package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +5 -2
- package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +72 -74
- package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +1 -1
- package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +23 -8
- package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +29 -8
- package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +7 -3
- package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +5 -0
- package/templates/be/patterns/queues/platform/queue.index.ts.tpl +2 -0
- package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +4 -0
- package/templates/be/patterns/queues/platform/queue.module.ts.tpl +2 -1
- package/templates/be/patterns/queues/platform/queue.port.ts.tpl +1 -1
- package/templates/be/patterns/queues/platform-lite/bullmq-queue-transport.client.spec.ts.tpl +417 -0
- package/templates/be/patterns/queues/platform-lite/bullmq-queue-transport.client.ts.tpl +172 -0
- package/templates/be/patterns/queues/platform-lite/persistence.queue.sql.ts.tpl +12 -0
- package/templates/be/patterns/queues/platform-lite/queue-outbox.sql.tpl +32 -0
- package/templates/be/patterns/queues/platform-lite/queue-relay.service.spec.ts.tpl +148 -0
- package/templates/be/patterns/queues/platform-lite/queue-relay.service.ts.tpl +51 -0
- package/templates/be/patterns/queues/platform-lite/queue-transport.port.ts.tpl +163 -0
- package/templates/be/patterns/queues/platform-lite/queue.config.ts.tpl +17 -0
- package/templates/be/patterns/queues/platform-lite/queue.error.ts.tpl +25 -0
- package/templates/be/patterns/queues/platform-lite/queue.index.ts.tpl +8 -0
- package/templates/be/patterns/queues/platform-lite/queue.messages.ts.tpl +21 -0
- package/templates/be/patterns/queues/platform-lite/queue.module.ts.tpl +74 -0
- package/templates/be/patterns/queues/platform-lite/queue.options.ts.tpl +38 -0
- package/templates/be/patterns/queues/queue.queue.ts.tpl +2 -2
- package/templates/be/patterns/reactors/application.contracts.ts.tpl +2 -0
- package/templates/be/patterns/reactors/consumer.ts.tpl +5 -1
- package/templates/be/platform/graphql/graphql.contracts.ts +2 -1
- package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +1 -1
- package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +6 -1
- package/templates/be/skeleton/apps/__app__/src/main.ts +7 -4
- package/templates/be/skeleton/apps/cli/src/compose/platform.ts +4 -0
- package/templates/be/skeleton/apps/cli/src/main.ts +7 -4
- package/templates/be/skeleton/src/modules/platform/database/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/http/errors/http.error.ts +22 -0
- package/templates/be/skeleton/src/modules/platform/http/fetch-http-client.client.spec.ts +235 -0
- package/templates/be/skeleton/src/modules/platform/http/fetch-http-client.client.ts +85 -0
- package/templates/be/skeleton/src/modules/platform/http/graphql-call.mapper.spec.ts +92 -0
- package/templates/be/skeleton/src/modules/platform/http/graphql-call.mapper.ts +52 -0
- package/templates/be/skeleton/src/modules/platform/http/http.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/http/http.module.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/http/http.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/http/http.port.ts +40 -0
- package/templates/be/skeleton/src/modules/platform/http/index.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/http/messages/http.messages.ts +15 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.module.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.port.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/ids/index.ts +4 -0
- package/templates/be/skeleton/src/modules/platform/ids/system-ids.service.spec.ts +15 -0
- package/templates/be/skeleton/src/modules/platform/ids/system-ids.service.ts +10 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation-request.policy.spec.ts +59 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation-request.policy.ts +21 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.contracts.ts +16 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.policy.spec.ts +150 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.policy.ts +42 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.port.ts +15 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.service.spec.ts +23 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.service.ts +28 -0
- package/templates/be/skeleton/src/modules/platform/logging/index.ts +14 -1
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +39 -6
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +15 -3
- package/templates/be/skeleton/src/modules/platform/logging/logging.decorators.ts +17 -2
- package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +13 -1
- package/templates/be/skeleton/src/modules/platform/logging/logging.module.ts +13 -3
- package/templates/be/skeleton/src/modules/platform/logging/nest-logger.service.spec.ts +69 -0
- package/templates/be/skeleton/src/modules/platform/logging/nest-logger.service.ts +48 -0
- package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/primitives/record.policy.spec.ts +12 -0
- package/templates/be/skeleton/src/modules/platform/primitives/record.policy.ts +3 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +6 -1
- package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +14 -1
- package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
- package/templates/be/test-world/world/test-apps.options.ts.tpl +2 -0
- package/templates/be/test-world/world/test-world.config.ts.tpl +1 -0
- package/upgrade/check.mjs +12 -135
- package/upgrade/file-state.mjs +1 -16
- package/upgrade/index.mjs +34 -361
- package/runtime/scripts/lib/check-code-loosening.mjs +0 -128
- package/runtime/scripts/lib/gate-loosening.mjs +0 -292
- package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +0 -89
- package/templates/app/upgrade-full/.starcistacks/dev/README.md +0 -16
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +0 -13
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +0 -3
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +0 -12
- package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +0 -7
- package/templates/be/skeleton-lite/apps/api/src/main.ts +0 -26
- package/templates/be/upgrade-full/apps/cli/src/app.module.ts +0 -12
- package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +0 -7
- package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +0 -5
- package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +0 -9
- package/templates/be/upgrade-full/apps/cli/src/main.ts +0 -29
- package/templates/be/upgrade-full/src/features/cli/cli.module.ts +0 -7
- package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +0 -23
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// grammar-dist.mjs - is packages/grammar/dist the build of the source beside it? The judge every grammar dist reader asks
|
|
2
|
+
// (gates, work, the reconciler and land); scripts/checks/check-grammar-dist.mjs is its self-check CLI.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { byCodeUnit } from '../lib/list.mjs';
|
|
6
|
+
import {skillRoot} from '../../engine/runtime-root.mjs';
|
|
7
|
+
import {gitDirOf} from '../lib/git-dir.mjs';
|
|
8
|
+
import {readJsonFile as readJson} from '../lib/json.mjs';
|
|
9
|
+
import {slash} from '../lib/path-key.mjs';
|
|
10
|
+
import {DIGEST_ALGORITHM,STAMP_FILE,STAMP_SCHEMA,distDigest,sourceDigest} from '../../packages/grammar/scripts/build-stamp.mjs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A built `packages/grammar/dist` is what products actually get: `file:` consumers link the package
|
|
14
|
+
* directory, and the brand check reads the exported CSS. `dist/` is not tracked, so nothing but a build
|
|
15
|
+
* refreshes it, and a merge that changes the source leaves the previous build in place. This module
|
|
16
|
+
* decides whether a dist is the build of the source beside it, and every reader of grammar dist asks it
|
|
17
|
+
* first and refuses a dist it does not call fresh.
|
|
18
|
+
*
|
|
19
|
+
* Fresh means: `dist/.build-stamp.json` exists, names the manifest's version, was made with this
|
|
20
|
+
* digest algorithm from the current source digest, the built files still hash to the stamp's dist
|
|
21
|
+
* digest, and every `--*` custom property the source CSS declares has the same value in the copied dist
|
|
22
|
+
* CSS. A package that does not carry its source (a registry install) cannot be rebuilt or compared, so
|
|
23
|
+
* it is `unverifiable` and allowed once its stamp binds: the version and dist digest must agree.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export const GRAMMAR_PACKAGE='@starci/grammar';
|
|
27
|
+
export const GRAMMAR_DIST_FIX='run npm run build in packages/grammar';
|
|
28
|
+
const GRAMMAR_DIST_CHECK='starci/grammar-dist-check@1';
|
|
29
|
+
const TOKEN_DIFF_CAP=20;
|
|
30
|
+
const CSS_TOKEN_NAME = '--[A-Za-z0-9_-]+';
|
|
31
|
+
const CSS_TOKEN_SPACE=String.raw`\s*`;
|
|
32
|
+
const CSS_TOKEN_VALUE = '[^;{}]*';
|
|
33
|
+
const CSS_TOKEN_DECLARATION=new RegExp(`(${CSS_TOKEN_NAME})${CSS_TOKEN_SPACE}:(${CSS_TOKEN_VALUE})`,'g');
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The primary worktree of the checkout `dir` belongs to, or null when `dir` is the primary itself or carries no
|
|
38
|
+
* linked-worktree metadata. A linked worktree's `.git` file names `<primary>/.git/worktrees/<name>`, and that
|
|
39
|
+
* directory's `commondir` file names `<primary>/.git` — read from disk, never a git spawn.
|
|
40
|
+
*/
|
|
41
|
+
const primaryWorktreeOf=(dir)=>{
|
|
42
|
+
const gitDir=gitDirOf(dir);
|
|
43
|
+
if(!gitDir||gitDir===path.join(path.resolve(dir),'.git'))return null;
|
|
44
|
+
try{
|
|
45
|
+
const common=path.resolve(gitDir,fs.readFileSync(path.join(gitDir,'commondir'),'utf8').trim());
|
|
46
|
+
return path.basename(common)==='.git'?path.dirname(common):null;
|
|
47
|
+
}catch{return null;}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The grammar package the runtime at `runtimeRoot` ships: its own packages/grammar while that carries a dist/, or the
|
|
52
|
+
* primary worktree's when this checkout is a linked worktree that never built one — dist/ is untracked, so a land
|
|
53
|
+
* scratch or a lane checkout shares the primary's build (draw-grammar.mjs resolves a lane's grammar the same way).
|
|
54
|
+
* The primary worktree itself, and any standalone checkout, always answer their own: a missing dist on live main
|
|
55
|
+
* stays `missing`.
|
|
56
|
+
*/
|
|
57
|
+
function shippedGrammarPackageRoot(runtimeRoot=skillRoot,deps={}){
|
|
58
|
+
const own=path.join(runtimeRoot,'packages','grammar');
|
|
59
|
+
if((deps.exists??fs.existsSync)(path.join(own,'dist')))return own;
|
|
60
|
+
const primary=(deps.primaryOf??primaryWorktreeOf)(runtimeRoot);
|
|
61
|
+
return primary?path.join(primary,'packages','grammar'):own;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const defaultGrammarPackageRoot=()=>shippedGrammarPackageRoot(skillRoot);
|
|
65
|
+
|
|
66
|
+
/** Every `--name: value` declaration outside a comment, in document order, whitespace collapsed. */
|
|
67
|
+
function cssTokenDeclarations(text){
|
|
68
|
+
const code=String(text).replace(/\/\*[\s\S]*?\*\//g,'');
|
|
69
|
+
return [...code.matchAll(CSS_TOKEN_DECLARATION)].map(m=>({name:m[1],value:m[2].trim().replace(/\s+/g,' ')}));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The families `packages/grammar/scripts/copy-css.mjs` copies: every `src/<family>` that has a `styles.css`. */
|
|
73
|
+
function cssFamilies(packageRoot){
|
|
74
|
+
const src=path.join(packageRoot,'src');
|
|
75
|
+
if(!fs.existsSync(src))return [];
|
|
76
|
+
return fs.readdirSync(src,{withFileTypes:true}).filter(entry=>entry.isDirectory()&&fs.existsSync(path.join(src,entry.name,'styles.css')))
|
|
77
|
+
.map(entry=>entry.name).sort(byCodeUnit);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Compares every family's `--*` token declarations in dist CSS with the source CSS they were copied from.
|
|
82
|
+
* Returns the differing tokens (capped) and how many declarations were compared.
|
|
83
|
+
*/
|
|
84
|
+
function compareCssTokens(packageRoot){
|
|
85
|
+
const differences=[];let compared=0;
|
|
86
|
+
for(const family of cssFamilies(packageRoot)){
|
|
87
|
+
const dir=path.join(packageRoot,'src',family);
|
|
88
|
+
for(const name of fs.readdirSync(dir).filter(file=>file.endsWith('.css')).sort()){
|
|
89
|
+
const file=`${family}/${name}`;
|
|
90
|
+
const built=path.join(packageRoot,'dist',family,name);
|
|
91
|
+
const source=cssTokenDeclarations(fs.readFileSync(path.join(dir,name),'utf8'));
|
|
92
|
+
if(!fs.existsSync(built)){differences.push({file,token:null,src:`${source.length} declarations`,dist:'file missing'});continue;}
|
|
93
|
+
const dist=cssTokenDeclarations(fs.readFileSync(built,'utf8'));
|
|
94
|
+
compared+=source.length;
|
|
95
|
+
const group=list=>list.reduce((map,{name:token,value})=>map.set(token,[...(map.get(token)??[]),value]),new Map());
|
|
96
|
+
const [want,have]=[group(source),group(dist)];
|
|
97
|
+
for(const token of [...new Set([...want.keys(),...have.keys()])].sort(byCodeUnit)){
|
|
98
|
+
const [a,b]=[want.get(token)??[],have.get(token)??[]];
|
|
99
|
+
if(a.join('\0')!==b.join('\0'))differences.push({file,token,src:a.join(' | ')||null,dist:b.join(' | ')||null});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {compared,differences:differences.slice(0,TOKEN_DIFF_CAP),differenceCount:differences.length};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The nearest package that owns `file` when it is `@starci/grammar`, with whether the file is under its
|
|
108
|
+
* `dist/`. Symlinks are resolved first, so `node_modules/@starci/grammar` of a `file:` consumer lands on
|
|
109
|
+
* the linked package directory.
|
|
110
|
+
*/
|
|
111
|
+
function grammarPackageOf(file){
|
|
112
|
+
let real;
|
|
113
|
+
try{real=fs.realpathSync(path.resolve(file));}catch{return null;}
|
|
114
|
+
for(let cursor=path.dirname(real);path.dirname(cursor)!==cursor;cursor=path.dirname(cursor)){
|
|
115
|
+
const manifest=path.join(cursor,'package.json');
|
|
116
|
+
if(!fs.existsSync(manifest))continue;
|
|
117
|
+
if(readJson(manifest)?.name!==GRAMMAR_PACKAGE)return null;
|
|
118
|
+
const relative=slash(path.relative(cursor,real));
|
|
119
|
+
return {root:cursor,file:real,inDist:relative==='dist'||relative.startsWith('dist/')};
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The freshness of one grammar package's dist. `state` is one of fresh, unverifiable (no source carried,
|
|
126
|
+
* nothing to compare), missing, unstamped, stale, tampered; `ok` is true only for the first two.
|
|
127
|
+
*/
|
|
128
|
+
/** The `; <token summary>` tail of a detail string, empty when no token differs. */
|
|
129
|
+
const tokenTail=tokens=>tokens.differenceCount?'; '+tokenSummary(tokens):'';
|
|
130
|
+
|
|
131
|
+
/** The result for a dist with no valid stamp. */
|
|
132
|
+
const unstampedResult=(result,tokens,evidence,stampFile)=>
|
|
133
|
+
result('unstamped',`dist/${STAMP_FILE} is ${fs.existsSync(stampFile)?'not a valid build stamp':'missing'}, so this dist was not produced by the current build script${tokenTail(tokens)}`,evidence);
|
|
134
|
+
|
|
135
|
+
/** The result when the stamp's algorithm or source digest no longer binds the current source, or null. */
|
|
136
|
+
const sourceStaleness=(root,stamp,tokens,evidence,result)=>{
|
|
137
|
+
if(stamp.algorithm!==DIGEST_ALGORITHM)
|
|
138
|
+
return result('stale',`the stamp was made with digest ${stamp.algorithm ?? '(none)'}, this check computes ${DIGEST_ALGORITHM}`,evidence);
|
|
139
|
+
const current=sourceDigest(root);
|
|
140
|
+
if(stamp.sourceDigest!==current)
|
|
141
|
+
return result('stale',`dist was built from source ${short(stamp.sourceDigest)} (${stamp.version}, ${stamp.builtAt}) but the source is now ${short(current)}${tokenTail(tokens)}`,{...evidence,sourceDigest:current});
|
|
142
|
+
return null;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/** The result when the stamp's version/dist digest no longer binds the package, or null. */
|
|
146
|
+
const bindingStaleness=(root,manifest,stamp,tokens,evidence,result)=>{
|
|
147
|
+
if(stamp.version!==manifest.version)
|
|
148
|
+
return result('stale',`dist was built as version ${stamp.version} but package.json is ${manifest.version}`,evidence);
|
|
149
|
+
const built=distDigest(root);
|
|
150
|
+
if(stamp.distDigest!==built)
|
|
151
|
+
return result('tampered',`dist files changed after the build (stamp ${short(stamp.distDigest)}, now ${short(built)})${tokenTail(tokens)}`,{...evidence,distDigest:built});
|
|
152
|
+
if(tokens.differenceCount)return result('tampered',tokenSummary(tokens),evidence);
|
|
153
|
+
return null;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export function grammarDistStatus(packageRoot=defaultGrammarPackageRoot()){
|
|
157
|
+
const root=path.resolve(packageRoot);
|
|
158
|
+
const manifest=readJson(path.join(root,'package.json'));
|
|
159
|
+
const base={schema:GRAMMAR_DIST_CHECK,root:slash(root),package:manifest?.name??null,version:manifest?.version??null,fix:GRAMMAR_DIST_FIX};
|
|
160
|
+
const result=(state,detail,extra={})=>({...base,state,ok:state==='fresh'||state==='unverifiable',detail,...extra});
|
|
161
|
+
if(!manifest)return result('missing',`${slash(root)}/package.json is missing or unreadable`);
|
|
162
|
+
const hasSource=fs.existsSync(path.join(root,'src'));
|
|
163
|
+
const distDir=path.join(root,'dist');
|
|
164
|
+
if(!fs.existsSync(distDir)||!fs.statSync(distDir).isDirectory())
|
|
165
|
+
return hasSource?result('missing','dist/ does not exist; the package has never been built here'):result('unverifiable','no dist/ and no source: nothing to verify');
|
|
166
|
+
const stampFile=path.join(distDir,STAMP_FILE);
|
|
167
|
+
const stamp=readJson(stampFile);
|
|
168
|
+
const tokens=hasSource?compareCssTokens(root):{compared:0,differences:[],differenceCount:0};
|
|
169
|
+
const evidence={stamp,tokens};
|
|
170
|
+
if(!stamp||stamp.schema!==STAMP_SCHEMA)return unstampedResult(result,tokens,evidence,stampFile);
|
|
171
|
+
if(hasSource){
|
|
172
|
+
const stale=sourceStaleness(root,stamp,tokens,evidence,result);
|
|
173
|
+
if(stale)return stale;
|
|
174
|
+
}
|
|
175
|
+
const bound=bindingStaleness(root,manifest,stamp,tokens,evidence,result);
|
|
176
|
+
if(bound)return bound;
|
|
177
|
+
return hasSource
|
|
178
|
+
?result('fresh',`${manifest.name}@${manifest.version} built from source ${short(stamp.sourceDigest)} at ${stamp.builtAt}; ${tokens.compared} token declarations match src`,evidence)
|
|
179
|
+
:result('unverifiable',`${manifest.name}@${manifest.version} carries no source; its stamp and built files agree`,evidence);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const short=digest=>typeof digest==='string'?digest.replace(/^sha256:/,'').slice(0,12):'(none)';
|
|
183
|
+
const tokenDifferenceText=d=>{
|
|
184
|
+
if(!d.token)return `${d.file} ${d.dist}`;
|
|
185
|
+
return `${d.file} ${d.token} src=${d.src??'(absent)'} dist=${d.dist??'(absent)'}`;
|
|
186
|
+
};
|
|
187
|
+
function tokenSummary(tokens){
|
|
188
|
+
const shown=tokens.differences.slice(0,3).map(tokenDifferenceText);
|
|
189
|
+
return `${tokens.differenceCount} dist CSS token value(s) differ from src: ${shown.join('; ')}${tokens.differenceCount>3?'; ...':''}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** One line a reader throws or reports when it refuses a dist. Ends with the exact fix. */
|
|
193
|
+
export function grammarDistMessage(status){
|
|
194
|
+
return `${status.package??GRAMMAR_PACKAGE} dist at ${status.root}/dist is ${status.state}: ${status.detail}. Fix: ${GRAMMAR_DIST_FIX}.`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* The refusal for a file a reader is about to read: null when the file is not in a grammar dist or the
|
|
199
|
+
* dist is fresh (or unverifiable), otherwise the status with its message.
|
|
200
|
+
*/
|
|
201
|
+
export function grammarDistRefusal(file){
|
|
202
|
+
const owner=grammarPackageOf(file);
|
|
203
|
+
if(!owner?.inDist)return null;
|
|
204
|
+
const status=grammarDistStatus(owner.root);
|
|
205
|
+
return status.ok?null:{...status,message:grammarDistMessage(status)};
|
|
206
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// npm selects the root publish inventory from tracked names and verified generated names, without copying unrelated payload.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { containedPath } from '../lib/path-key.mjs';
|
|
5
|
+
import { isLinkLike } from '../api/fs/is-link-like.mjs';
|
|
6
|
+
import { packDryRun } from '../api/npm/pack-dry-run.mjs';
|
|
7
|
+
import { runNpm } from '../api/npm/run-npm.mjs';
|
|
8
|
+
|
|
9
|
+
/** npm's selected relative names over a fresh placeholder tree; only package/ignore metadata is copied from source. */
|
|
10
|
+
export function packTrackedSelection(root, projection, { tracked, generated = [], env = process.env, run = runNpm } = {}) {
|
|
11
|
+
fs.mkdirSync(projection);
|
|
12
|
+
const sourceRoot = path.resolve(root);
|
|
13
|
+
for (const relative of new Set([...tracked, ...generated])) {
|
|
14
|
+
const target = containedPath(projection, relative);
|
|
15
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
16
|
+
if (['package.json', 'package-lock.json', 'npm-shrinkwrap.json', '.npmignore', '.gitignore'].includes(path.basename(relative))) {
|
|
17
|
+
containedPath(sourceRoot, relative);
|
|
18
|
+
const source = path.resolve(sourceRoot, relative);
|
|
19
|
+
for (let cursor = source; cursor !== sourceRoot; cursor = path.dirname(cursor)) if (isLinkLike(cursor)) throw new Error(`tracked selection metadata contains a link: ${relative}`);
|
|
20
|
+
fs.copyFileSync(source, target);
|
|
21
|
+
} else fs.writeFileSync(target, '');
|
|
22
|
+
}
|
|
23
|
+
const listed = packDryRun(projection, { spawn: (args, options) => run(args, { ...options, env }) });
|
|
24
|
+
if (listed.error || listed.status !== 0) throw new Error('npm could not select the tracked-only root projection');
|
|
25
|
+
const packed = JSON.parse(listed.stdout);
|
|
26
|
+
return Array.isArray(packed) && packed.length === 1 && Array.isArray(packed[0].files) ? packed[0].files.map((file) => file.path) : null;
|
|
27
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// test-world-run.mjs - the test-world run summary (schema starci/test-world-run@1) e2e.verify and integration.verify attach
|
|
3
|
+
// (knowledge/op-gate.yaml proofs.test-world, contract change op-mechanism-proofs).
|
|
4
|
+
//
|
|
5
|
+
// starci gate test-world --root <app> --project e2e|integration|contract [--tests <path pattern>] [--out <file>]
|
|
6
|
+
//
|
|
7
|
+
// Over the app at --root it records, and `starci kernel settle` re-reads (scripts/kernel/gate-settle.mjs):
|
|
8
|
+
// harness the be jest config is @starci/jest-preset's starciJestConfig() (its world projects run on the preset's world
|
|
9
|
+
// runner: up to min(--maxWorkers, slots) files at once, each in a fresh process on its own data slot) and the world
|
|
10
|
+
// declaration be/src/tests/world/test-world.config.ts
|
|
11
|
+
// calls defineTestWorld;
|
|
12
|
+
// specs every spec of the project (be/src/tests/{e2e,integration,contract}/**/*.<project>-spec.ts, narrowed by --tests)
|
|
13
|
+
// takes its world from the library in the layer's form - useTestWorld({ apps }) for e2e, useTestWorld({ modules })
|
|
14
|
+
// for integration, useSandbox(...) for contract (the real client against the provider's sandbox) - and hand-rolls
|
|
15
|
+
// none of op-gate.yaml testWorld.forbidden (docker, testcontainers, DataSource, process.env, sleeps); the outage calls
|
|
16
|
+
// (cut, restore, during, latency) are counted;
|
|
17
|
+
// run the managed `npm run test:<project>` with jest's --json report: exit, totals, failed and skipped tests.
|
|
18
|
+
// Our own stack runs real and only third-party SaaS is faked at the network edge: the library owns that, a spec never does.
|
|
19
|
+
// Exit 0 green, 1 a finding or a red run, 2 the summary could not be built.
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import { runNpm } from '../api/npm/run-npm.mjs';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
import { posixPath } from '../lib/path-key.mjs';
|
|
25
|
+
import { valueFlags } from '../lib/cli-arg.mjs';
|
|
26
|
+
import { isMain } from '../lib/is-main.mjs'; import { walkFiles } from '../lib/walk.mjs';
|
|
27
|
+
import { opGateRules } from '../lib/op-gate.mjs';
|
|
28
|
+
import { makeTempDir } from '../api/fs/make-temp-dir.mjs';
|
|
29
|
+
|
|
30
|
+
export const TEST_WORLD_RUN_SCHEMA = 'starci/test-world-run@1';
|
|
31
|
+
const WORLD_PROJECTS = Object.freeze({
|
|
32
|
+
e2e: { dir: 'be/src/tests/e2e', suffix: '.e2e-spec.ts', mode: 'apps' },
|
|
33
|
+
integration: { dir: 'be/src/tests/integration', suffix: '.integration-spec.ts', mode: 'modules' },
|
|
34
|
+
contract: { dir: 'be/src/tests/contract', suffix: '.contract-spec.ts', mode: 'sandbox' },
|
|
35
|
+
});
|
|
36
|
+
const runtimeRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
37
|
+
const JEST_CONFIGS = ['be/jest.config.js', 'be/jest.config.cjs', 'be/jest.config.ts', 'jest.config.js', 'jest.config.cjs', 'jest.config.ts'];
|
|
38
|
+
const DECLARATION = 'be/src/tests/world/test-world.config.ts';
|
|
39
|
+
const USAGE = 'usage: starci gate test-world --root <app> --project e2e|integration|contract [--tests <path pattern>] [--out <file>]';
|
|
40
|
+
|
|
41
|
+
/** op-gate.yaml testWorld: {required[], forbidden[], outage[]}. */
|
|
42
|
+
export function testWorldRules(runtime = runtimeRoot) {
|
|
43
|
+
return opGateRules(runtime, 'testWorld');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The harness of the app: {jestConfig, preset, declaration, defineTestWorld}. */
|
|
47
|
+
function harnessOf(root) {
|
|
48
|
+
const jestConfig = JEST_CONFIGS.find((rel) => fs.existsSync(path.join(root, rel))) ?? null;
|
|
49
|
+
const text = jestConfig ? fs.readFileSync(path.join(root, jestConfig), 'utf8') : '';
|
|
50
|
+
const declared = fs.existsSync(path.join(root, DECLARATION)) ? fs.readFileSync(path.join(root, DECLARATION), 'utf8') : null;
|
|
51
|
+
return { jestConfig, preset: /@starci\/jest-preset/.test(text) && /starciJestConfig\s*\(/.test(text), declaration: declared === null ? null : DECLARATION, defineTestWorld: /defineTestWorld\s*\(/.test(declared ?? '') };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The `{ ... }` argument text of each useTestWorld( call, balanced over braces. */
|
|
55
|
+
function useTestWorldArgs(text) {
|
|
56
|
+
const out = [];
|
|
57
|
+
const re = /useTestWorld\s*\(/g;
|
|
58
|
+
let m;
|
|
59
|
+
while ((m = re.exec(text))) {
|
|
60
|
+
let depth = 0, i = m.index + m[0].length, start = i;
|
|
61
|
+
for (; i < text.length; i += 1) {
|
|
62
|
+
const c = text[i];
|
|
63
|
+
if (c === '(' || c === '{' || c === '[') depth += 1;
|
|
64
|
+
else if (c === ')' || c === '}' || c === ']') {
|
|
65
|
+
if (depth === 0) break;
|
|
66
|
+
depth -= 1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
out.push(text.slice(start, i));
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** One spec judged: {path, useTestWorld, modes[], outage, forbidden[]}; `sandbox` is a mode when the spec calls useSandbox. */
|
|
75
|
+
export function judgeSpec(rel, text, rules) {
|
|
76
|
+
const args = useTestWorldArgs(text);
|
|
77
|
+
const modes = [...new Set(args.flatMap((a) => ['apps', 'modules'].filter((k) => new RegExp(String.raw`(^|[{,\s])${k}\s*:`).test(a))))];
|
|
78
|
+
const sandbox = /useSandbox\s*\(/.test(text);
|
|
79
|
+
if (sandbox) modes.push('sandbox');
|
|
80
|
+
// A comment may name a forbidden word to explain its absence; only code counts.
|
|
81
|
+
const code = text.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/[^\n\r\u2028\u2029]*/gm, '$1');
|
|
82
|
+
return { path: rel, useTestWorld: args.length > 0 || sandbox, modes, outage: rules.outage.reduce((n, needle) => n + code.split(needle).length - 1, 0),
|
|
83
|
+
forbidden: rules.forbidden.filter((needle) => code.includes(needle)) };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The spec files of a world project under the app root (app-relative POSIX paths), narrowed by a --tests pattern. */
|
|
87
|
+
export function specsOf(root, project, tests = null) {
|
|
88
|
+
const def = WORLD_PROJECTS[project];
|
|
89
|
+
const dir = path.join(root, def.dir);
|
|
90
|
+
if (!fs.existsSync(dir)) return [];
|
|
91
|
+
const narrow = tests ? new RegExp(tests) : null;
|
|
92
|
+
return walkFiles(dir, { sorted: true, exclude: (name, full, entry) => entry.isDirectory() && name === 'node_modules' })
|
|
93
|
+
.map((file) => posixPath(path.relative(root, file)))
|
|
94
|
+
.filter((rel) => rel.endsWith(def.suffix) && (!narrow || narrow.test(rel)));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const RUN_COUNTS = Object.freeze(['total', 'passed', 'failed', 'skipped', 'files', 'failedFiles']);
|
|
98
|
+
const JEST_COUNTS = Object.freeze(['numTotalTests', 'numPassedTests', 'numFailedTests', 'numPendingTests', 'numTodoTests',
|
|
99
|
+
'numTotalTestSuites', 'numPassedTestSuites', 'numFailedTestSuites', 'numPendingTestSuites', 'numRuntimeErrorTestSuites']);
|
|
100
|
+
|
|
101
|
+
/** Reject an incomplete or inconsistent required test measurement without coercing reported counters. */
|
|
102
|
+
export function testRunCountsError(run) {
|
|
103
|
+
if (!run || RUN_COUNTS.some((key) => !Number.isSafeInteger(run[key]) || run[key] < 0)) return 'test counters must be non-negative safe integers';
|
|
104
|
+
if (run.total !== run.passed + run.failed + run.skipped || run.failedFiles > run.files) return 'test counters do not describe a complete run';
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The shape checks of the report and its process: an error string or null. */
|
|
109
|
+
const reportShapeError = (report, run) => {
|
|
110
|
+
if (run?.error || run?.signal || !Number.isSafeInteger(run?.status) || run.status < 0) {
|
|
111
|
+
const detail = run?.error?.message ?? run?.signal ?? `exit ${run?.status ?? 'unknown'}`;
|
|
112
|
+
return `jest did not complete: ${detail}`;
|
|
113
|
+
}
|
|
114
|
+
if (!report || typeof report !== 'object' || Array.isArray(report) || !Array.isArray(report.testResults)) return 'jest wrote no valid --json report';
|
|
115
|
+
if (JEST_COUNTS.some((key) => !Number.isSafeInteger(report[key]) || report[key] < 0)) return 'jest counters must be non-negative safe integers';
|
|
116
|
+
if (typeof report.success !== 'boolean' || typeof report.wasInterrupted !== 'boolean') return 'jest result is missing completion flags';
|
|
117
|
+
if (report.wasInterrupted || report.runExecError) return 'jest reports an interrupted or incomplete run';
|
|
118
|
+
if (report.numTotalTestSuites !== report.numPassedTestSuites + report.numFailedTestSuites + report.numPendingTestSuites
|
|
119
|
+
|| report.numRuntimeErrorTestSuites > report.numFailedTestSuites || report.testResults.length !== report.numTotalTestSuites)
|
|
120
|
+
return 'jest suite counters do not describe a complete run';
|
|
121
|
+
return null;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** The assertion and suite tallies of a valid report: {totals, suites}, or {error}. */
|
|
125
|
+
const suiteShapeError = (suite) => !suite || !['passed', 'failed', 'skipped', 'focused'].includes(suite.status) || !Array.isArray(suite.assertionResults)
|
|
126
|
+
? 'jest wrote an invalid suite result' : null;
|
|
127
|
+
|
|
128
|
+
const focusedSuiteError = (suite, report) => suite.status === 'focused'
|
|
129
|
+
&& (report.numPendingTests === 0 || !suite.assertionResults.some(a => ['pending', 'skipped', 'disabled'].includes(a?.status)))
|
|
130
|
+
? 'jest focused suite reports no pending assertion' : null;
|
|
131
|
+
|
|
132
|
+
const tallyAssertion = (assertion, totals) => {
|
|
133
|
+
if (assertion?.status === 'passed') totals.passed += 1;
|
|
134
|
+
else if (assertion?.status === 'failed') totals.failed += 1;
|
|
135
|
+
else if (['pending', 'todo', 'skipped', 'disabled'].includes(assertion?.status)) totals.skipped += 1;
|
|
136
|
+
else return 'jest wrote an invalid assertion result';
|
|
137
|
+
return null;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const tallySuites = (report) => {
|
|
141
|
+
const totals = { passed: 0, failed: 0, skipped: 0 }, suites = { passed: 0, failed: 0, skipped: 0 };
|
|
142
|
+
for (const suite of report.testResults) {
|
|
143
|
+
const shapeError = suiteShapeError(suite);
|
|
144
|
+
if (shapeError) return { error: shapeError };
|
|
145
|
+
const focusedError = focusedSuiteError(suite, report);
|
|
146
|
+
if (focusedError) return { error: focusedError };
|
|
147
|
+
suites[suite.status === 'focused' ? 'passed' : suite.status] += 1;
|
|
148
|
+
for (const assertion of suite.assertionResults) {
|
|
149
|
+
const assertionError = tallyAssertion(assertion, totals);
|
|
150
|
+
if (assertionError) return { error: assertionError };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return { totals, suites };
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/** Validate the Jest JSON and its real process result before any producer can call the measurement clean. */
|
|
157
|
+
export function jestRunError(report, run) {
|
|
158
|
+
const shapeError = reportShapeError(report, run);
|
|
159
|
+
if (shapeError) return shapeError;
|
|
160
|
+
const tally = tallySuites(report);
|
|
161
|
+
if (tally.error) return tally.error;
|
|
162
|
+
const { totals, suites } = tally;
|
|
163
|
+
const countsError = testRunCountsError(reduceJest(report));
|
|
164
|
+
if (countsError) return countsError;
|
|
165
|
+
if (suites.passed !== report.numPassedTestSuites || suites.failed !== report.numFailedTestSuites
|
|
166
|
+
|| suites.skipped !== report.numPendingTestSuites) return 'jest suite counters disagree with its results';
|
|
167
|
+
if (totals.passed !== report.numPassedTests || totals.failed !== report.numFailedTests
|
|
168
|
+
|| totals.skipped !== report.numPendingTests + report.numTodoTests) return 'jest assertion counters disagree with its results';
|
|
169
|
+
const measuredFailure = report.numFailedTests > 0 || report.numFailedTestSuites > 0;
|
|
170
|
+
// A normal failed assertion retains its typed finding; a nonzero process with a clean report never passes.
|
|
171
|
+
if (run.status !== 0 && !measuredFailure) return `jest exited ${run.status} without a reported test failure`;
|
|
172
|
+
if (!report.success && !measuredFailure) return 'jest reports an unsuccessful run without a reported test failure';
|
|
173
|
+
if (report.success && measuredFailure) return 'jest success contradicts its failed tests or suites';
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Jest's totals and executed suite files; testFiles are relative to root when supplied. */
|
|
178
|
+
export function reduceJest(report, root = null) {
|
|
179
|
+
const suites = Array.isArray(report?.testResults) ? report.testResults : [];
|
|
180
|
+
const failures = [];
|
|
181
|
+
for (const file of suites) for (const a of Array.isArray(file?.assertionResults) ? file.assertionResults : []) {
|
|
182
|
+
if (a.status === 'failed') failures.push({ file: posixPath(String(file.name ?? '')), test: a.fullName ?? a.title, message: String(a.failureMessages?.[0] ?? '').split('\n')[0].slice(0, 300) });
|
|
183
|
+
}
|
|
184
|
+
return { total: report?.numTotalTests ?? 0, passed: report?.numPassedTests ?? 0, failed: report?.numFailedTests ?? 0,
|
|
185
|
+
skipped: (report?.numPendingTests ?? 0) + (report?.numTodoTests ?? 0), files: report?.numTotalTestSuites ?? 0, failedFiles: report?.numFailedTestSuites ?? 0, failures: failures.slice(0, 100),
|
|
186
|
+
testFiles: suites.filter((suite) => (suite.assertionResults ?? []).some((assertion) => ['passed', 'failed'].includes(assertion.status)))
|
|
187
|
+
.map((suite) => suite.name ?? suite.testFilePath).filter((file) => typeof file === 'string')
|
|
188
|
+
.map((file) => posixPath(root ? path.relative(root, path.resolve(root, file)) : file)) };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Run the managed `npm run test:<project>` with jest's JSON report; {command, exit, ...reduceJest, error}. */
|
|
192
|
+
function runWorldProject(root, project, tests = null, { npm = runNpm } = {}) {
|
|
193
|
+
const outDir = makeTempDir('starci-test-world-run-');
|
|
194
|
+
const outFile = path.join(outDir, 'jest.json');
|
|
195
|
+
const args = ['run', `test:${project}`, '--', '--json', `--outputFile=${outFile}`, ...(tests ? ['--testPathPattern', tests] : [])];
|
|
196
|
+
const run = npm(args, { cwd: root, maxBuffer: 512 * 1024 * 1024 });
|
|
197
|
+
let report = null;
|
|
198
|
+
try { report = JSON.parse(fs.readFileSync(outFile, 'utf8')); } catch { report = null; }
|
|
199
|
+
try { fs.rmSync(outFile, { force: true }); fs.rmdirSync(outDir); } catch { /* a temp file of this run */ }
|
|
200
|
+
return { command: `npm ${args.filter((a) => !a.startsWith('--outputFile=')).join(' ')}`, exit: run.status ?? null,
|
|
201
|
+
...(report ? reduceJest(report, root) : { total: 0, passed: 0, failed: 0, skipped: 0, files: 0, failedFiles: 0, failures: [] }),
|
|
202
|
+
error: jestRunError(report, run) };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** The findings of a summary (harness, specs and run), each {rule, path, message}. */
|
|
206
|
+
export function testWorldFindings(summary) {
|
|
207
|
+
const out = [];
|
|
208
|
+
const h = summary.harness ?? {};
|
|
209
|
+
if (!h.preset) out.push({ rule: 'preset-missing', path: h.jestConfig, message: 'the be jest config is not @starci/jest-preset starciJestConfig(): its world projects do not run on the world runner' });
|
|
210
|
+
if (!h.declaration || !h.defineTestWorld) out.push({ rule: 'declaration-missing', path: DECLARATION, message: 'no world declaration calling defineTestWorld' });
|
|
211
|
+
if (!(summary.specs ?? []).length) out.push({ rule: 'no-specs', path: WORLD_PROJECTS[summary.project]?.dir ?? null, message: `no ${summary.project} spec was selected` });
|
|
212
|
+
const mode = WORLD_PROJECTS[summary.project]?.mode;
|
|
213
|
+
for (const spec of summary.specs ?? []) {
|
|
214
|
+
const form = mode === 'sandbox' ? 'useSandbox(...)' : `useTestWorld({ ${mode} })`;
|
|
215
|
+
if (!spec.useTestWorld) out.push({ rule: 'use-test-world-missing', path: spec.path, message: 'the spec never calls useTestWorld or useSandbox: its world is hand-rolled' });
|
|
216
|
+
else if (mode && !spec.modes.includes(mode)) out.push({ rule: 'layer-mode', path: spec.path, message: `a ${summary.project} spec calls ${form}; it uses ${spec.modes.join(', ') || 'neither apps nor modules'}` });
|
|
217
|
+
for (const needle of spec.forbidden) out.push({ rule: 'hand-rolled', path: spec.path, message: `the spec uses ${needle}: infrastructure belongs to @starci/test-world` });
|
|
218
|
+
}
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function buildTestWorldRun({ root, project, tests = null, rules = testWorldRules(), npm = runNpm, run = true }) {
|
|
223
|
+
if (!WORLD_PROJECTS[project]) throw new Error(`--project must be one of ${Object.keys(WORLD_PROJECTS).join(', ')}; ${USAGE}`);
|
|
224
|
+
const abs = path.resolve(root);
|
|
225
|
+
const specs = specsOf(abs, project, tests).map((rel) => judgeSpec(rel, fs.readFileSync(path.join(abs, rel), 'utf8'), rules));
|
|
226
|
+
const summary = { schema: TEST_WORLD_RUN_SCHEMA, at: new Date().toISOString(), root: posixPath(abs), project, tests, harness: harnessOf(abs), specs,
|
|
227
|
+
outageCalls: specs.reduce((n, s) => n + s.outage, 0), run: null, findings: [], exit: 2 };
|
|
228
|
+
summary.findings = testWorldFindings(summary);
|
|
229
|
+
summary.run = run ? runWorldProject(abs, project, tests, { npm }) : null;
|
|
230
|
+
const red = !summary.run || summary.run.error || summary.run.exit !== 0 || summary.run.failed > 0 || summary.run.skipped > 0 || summary.run.total === 0;
|
|
231
|
+
if (summary.run?.error) summary.exit = 2;
|
|
232
|
+
else summary.exit = summary.findings.length || red ? 1 : 0;
|
|
233
|
+
return summary;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function parseTestWorldArgs(argv) {
|
|
237
|
+
const opts = { root: null, project: null, tests: null, out: null, ...valueFlags(argv, ['--root', '--project', '--tests', '--out'], USAGE) };
|
|
238
|
+
if (!opts.project) throw new Error(`--project is required; ${USAGE}`);
|
|
239
|
+
return opts;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (isMain(import.meta.url)) {
|
|
243
|
+
try {
|
|
244
|
+
const opts = parseTestWorldArgs(process.argv.slice(2));
|
|
245
|
+
const summary = buildTestWorldRun({ root: opts.root ?? process.cwd(), project: opts.project, tests: opts.tests });
|
|
246
|
+
const text = `${JSON.stringify(summary, null, 2)}\n`;
|
|
247
|
+
if (opts.out) { fs.mkdirSync(path.dirname(path.resolve(opts.out)), { recursive: true }); fs.writeFileSync(path.resolve(opts.out), text); }
|
|
248
|
+
process.stdout.write(text);
|
|
249
|
+
process.exitCode = summary.exit;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
process.stderr.write(`test-world-run: ${error.message}\n`);
|
|
252
|
+
process.exitCode = 2;
|
|
253
|
+
}
|
|
254
|
+
}
|