@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
|
@@ -19,7 +19,7 @@ const ARGUMENT = /^[A-Za-z0-9_@%+=:,./-]+$/u;
|
|
|
19
19
|
const UPPER_ENV = KEY;
|
|
20
20
|
|
|
21
21
|
const FIELDS = {
|
|
22
|
-
deploy: ['stack', 'remote', 'images', 'volumes', 'secrets', 'environment', 'configs', 'passEnv', 'verify', 'jobs', 'tasks', 'backup', 'ingress', 'registry'],
|
|
22
|
+
deploy: ['stack', 'remote', 'images', 'volumes', 'secrets', 'environment', 'configs', 'passEnv', 'postDeploy', 'verify', 'jobs', 'tasks', 'backup', 'ingress', 'registry'],
|
|
23
23
|
remote: ['host', 'releasesBase', 'sealed'],
|
|
24
24
|
sealed: ['ops', 'sshKey', 'knownHosts'],
|
|
25
25
|
image: ['app', 'buildArgs'],
|
|
@@ -35,7 +35,7 @@ const FIELDS = {
|
|
|
35
35
|
http: ['name', 'url', 'method', 'body', 'status', 'json', 'contains', 'retry'],
|
|
36
36
|
exec: ['name', 'service', 'command', 'stdout'],
|
|
37
37
|
jobs: ['service', 'command'],
|
|
38
|
-
task: ['service', 'command'],
|
|
38
|
+
task: ['service', 'command', 'timeoutSeconds'],
|
|
39
39
|
backup: ['image', 'keepDays', 'dir', 'volumes'],
|
|
40
40
|
backupVolume: ['name', 'volume'],
|
|
41
41
|
registry: ['host', 'visibility'],
|
|
@@ -228,9 +228,11 @@ function jobs(c, block, services) {
|
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
const TASK_NAME = /^[a-z][a-z0-9-]{0,40}$/u;
|
|
231
|
+
/** The seconds a post-deploy task may run before the deploy counts it failed and rolls back. */
|
|
232
|
+
const DEFAULT_TASK_TIMEOUT_SECONDS = 600;
|
|
231
233
|
|
|
232
234
|
/** The one-off commands (`starci app stack task <name>`): name -> { service, command }, each run inside a running task of the service. */
|
|
233
|
-
function
|
|
235
|
+
function tasksOf(c, block, services) {
|
|
234
236
|
if (block === undefined) return {};
|
|
235
237
|
if (!plain(block)) { c.problem('deploy.tasks', 'must be a mapping'); return {}; }
|
|
236
238
|
const out = {};
|
|
@@ -240,11 +242,24 @@ function tasks(c, block, services) {
|
|
|
240
242
|
if (!closedFields(c, at, entry, FIELDS.task)) continue;
|
|
241
243
|
nameField(c, `${at}.service`, entry.service);
|
|
242
244
|
if (services.size > 0 && !services.has(entry.service)) c.problem(`${at}.service`, `${entry.service} is not a service of the stack source`);
|
|
243
|
-
|
|
245
|
+
if (entry.timeoutSeconds !== undefined && !(Number.isInteger(entry.timeoutSeconds) && entry.timeoutSeconds >= 1 && entry.timeoutSeconds <= 3600)) c.problem(`${at}.timeoutSeconds`, 'must be a whole number of seconds from 1 to 3600');
|
|
246
|
+
out[name] = { service: entry.service, command: command(c, `${at}.command`, entry.command), timeoutSeconds: entry.timeoutSeconds ?? DEFAULT_TASK_TIMEOUT_SECONDS };
|
|
244
247
|
}
|
|
245
248
|
return out;
|
|
246
249
|
}
|
|
247
250
|
|
|
251
|
+
/** The release tasks that run after convergence and before verification: a unique list of declared task names, in run order. */
|
|
252
|
+
function postDeploy(c, block, declared) {
|
|
253
|
+
if (block === undefined) return [];
|
|
254
|
+
if (!Array.isArray(block)) { c.problem('deploy.postDeploy', 'must be a list of declared task names'); return []; }
|
|
255
|
+
block.forEach((name, index) => {
|
|
256
|
+
const at = `deploy.postDeploy[${index}]`;
|
|
257
|
+
if (typeof name !== 'string' || !Object.hasOwn(declared, name)) c.problem(at, 'must name a task declared under deploy.tasks');
|
|
258
|
+
else if (block.indexOf(name) !== index) c.problem(at, `${name} is listed twice`);
|
|
259
|
+
});
|
|
260
|
+
return block;
|
|
261
|
+
}
|
|
262
|
+
|
|
248
263
|
const REGISTRY_HOST = /^[a-z0-9][a-z0-9.-]*(?::\d{1,5})?$/u;
|
|
249
264
|
const VISIBILITY = ['public', 'private'];
|
|
250
265
|
|
|
@@ -295,6 +310,7 @@ export function normalizeDeploy(block, { project, probe }) {
|
|
|
295
310
|
const services = probe.services ?? new Set();
|
|
296
311
|
const members = probe.members ?? new Set();
|
|
297
312
|
if (block.stack !== undefined) nameField(c, 'deploy.stack', block.stack);
|
|
313
|
+
const tasks = tasksOf(c, block.tasks, services);
|
|
298
314
|
const config = {
|
|
299
315
|
stack: block.stack ?? project,
|
|
300
316
|
remote: remote(c, block.remote),
|
|
@@ -306,7 +322,8 @@ export function normalizeDeploy(block, { project, probe }) {
|
|
|
306
322
|
passEnv: block.passEnv === undefined ? [] : stringList(c, 'deploy.passEnv', block.passEnv, KEY),
|
|
307
323
|
verify: verify(c, block.verify, services),
|
|
308
324
|
jobs: jobs(c, block.jobs, services),
|
|
309
|
-
tasks
|
|
325
|
+
tasks,
|
|
326
|
+
postDeploy: postDeploy(c, block.postDeploy, tasks),
|
|
310
327
|
backup: backup(c, block.backup),
|
|
311
328
|
ingress: ingress(c, block.ingress, probe),
|
|
312
329
|
registry: registry(c, block.registry),
|
|
@@ -13,7 +13,8 @@ import { ingressIssues } from './rules/stacks-ingress.mjs';
|
|
|
13
13
|
import { liteBackingIssues } from './rules/stacks-lite-backing.mjs';
|
|
14
14
|
import { releaseBackupIssues, scheduleIssues } from './rules/stacks-release.mjs';
|
|
15
15
|
import { stacksDeploymentContexts } from './rules/stacks-runtime.mjs';
|
|
16
|
-
import {
|
|
16
|
+
import { readJson } from './rules/read.mjs';
|
|
17
|
+
import { applicationStackFindings, liteCustodyDirectory } from './starcistacks-runtime.mjs';
|
|
17
18
|
|
|
18
19
|
const RESULT='starci/application-stacks-check@1';
|
|
19
20
|
const STACKS_DIR='.starcistacks';
|
|
@@ -32,7 +33,7 @@ const HARDENING_CODES=new Set([
|
|
|
32
33
|
'STACKS_PLACEMENT_MISSING','STACKS_HEALTHCHECK_MISSING','STACKS_UPDATE_POLICY_MISSING','STACKS_REALTIME_SCALED',
|
|
33
34
|
'STACKS_RELEASE_UNDECLARED','BACKUP_UNDECLARED','BACKUP_PROOF_MISSING','STACKS_MODEL_UNAVAILABLE',
|
|
34
35
|
'HFS_SCHEDULE_UNDECLARED','STACKS_INGRESS_UNDECLARED','STACKS_INGRESS_UNKNOWN',
|
|
35
|
-
'STACKS_LITE_SELFHOSTED_DB','STACKS_SIDECAR_VOLUME_UNDECLARED','STACKS_SIDECAR_DB_UNSUPPORTED',
|
|
36
|
+
'STACKS_LITE_SELFHOSTED_DB','STACKS_SIDECAR_VOLUME_UNDECLARED','STACKS_SIDECAR_DB_UNSUPPORTED','STACKS_LITE_REDIS_PERSISTENCE',
|
|
36
37
|
]);
|
|
37
38
|
|
|
38
39
|
function regular(file,{root=null}={}){
|
|
@@ -296,11 +297,29 @@ const checkServiceEnvironment=(services,add)=>{
|
|
|
296
297
|
for(const [service,definition] of Object.entries(services))for(const [key] of composeEntries(definition?.environment, { isMapping: object }))
|
|
297
298
|
if(sensitive.test(key)&&!/_FILE$/i.test(key))add('plaintext-sensitive-environment',`services.${service}.environment.${key}`,'sensitive configuration is present as a direct environment value; value redacted');
|
|
298
299
|
};
|
|
299
|
-
const
|
|
300
|
+
const dependencyComponent=(id,component,apps)=>component?.stateful===true||component?.database!==undefined
|
|
301
|
+
||!apps.has(component?.app)&&!apps.has(id)&&!['service','job'].includes(component?.role);
|
|
302
|
+
const checkDependencyContainers=({services,spec,manifest,repo,add})=>{
|
|
303
|
+
const apps=new Set([...(Array.isArray(repo?.apps)?repo.apps:[]),
|
|
304
|
+
...Object.values(object(repo?.sides)?repo.sides:{}).flatMap(side=>Array.isArray(side?.apps)?side.apps:[])].map(app=>app.name));
|
|
305
|
+
for(const id of Object.keys(object(spec?.components)?spec.components:{})){
|
|
306
|
+
const component=manifest.components?.[id];
|
|
307
|
+
if(nonempty(component?.app)&&!apps.has(component.app))
|
|
308
|
+
add('HFS_STACKS_SHAPE',`components.${id}.app`,'an application component must name an app in the HFS declaration');
|
|
309
|
+
if(!dependencyComponent(id,component,apps))continue;
|
|
310
|
+
const service=services[id];
|
|
311
|
+
if(!object(service)||!nonempty(service.image)&&!nonempty(service.build)&&!nonempty(service.build?.context))
|
|
312
|
+
add('HFS_STACKS_SHAPE',`services.${id}`,'a selected dependency must run in a Docker Linux container with a rendered image or build context');
|
|
313
|
+
else if(service.platform!==undefined&&(typeof service.platform!=='string'||!/^linux(?:\/[^/]+){0,2}$/.test(service.platform)))
|
|
314
|
+
add('HFS_STACKS_SHAPE',`services.${id}.platform`,'a selected dependency must use a Docker Linux container platform');
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
const checkModel=({model,ids,spec,manifest,lite,repo,add})=>{
|
|
300
318
|
const services=object(model.services)?model.services:{};
|
|
301
319
|
checkServiceIds(services,ids,add);
|
|
302
320
|
if(unresolvedCompose(model))add('compose-placeholder-unresolved','composeModel','rendered Compose still contains an unresolved interpolation placeholder in a host-resolved field');
|
|
303
321
|
checkServiceEnvironment(services,add);
|
|
322
|
+
checkDependencyContainers({services,spec,manifest,repo,add});
|
|
304
323
|
if(object(spec)&&spec.runtime==='docker-swarm')for(const issue of swarmModelIssues({model,components:manifest.components,bindings:spec.components,lite}))
|
|
305
324
|
add('HFS_STACKS_SHAPE',issue.at,issue.message);
|
|
306
325
|
};
|
|
@@ -327,12 +346,12 @@ const checkKubernetesDeclaration=(manifest,add)=>{
|
|
|
327
346
|
if(!['deferred','supported'].includes(manifest.k8s?.status)||!String(manifest.k8s?.reason??'').trim())
|
|
328
347
|
add('k8s-status-invalid','k8s','Kubernetes must be explicitly deferred or supported with a reason');
|
|
329
348
|
};
|
|
330
|
-
const checkSelectedEnvironment=({environment,environmentNames,environments,stacksRoot,ids,root,model,manifest,lite,add})=>{
|
|
349
|
+
const checkSelectedEnvironment=({environment,environmentNames,environments,stacksRoot,ids,root,model,manifest,lite,repo,add})=>{
|
|
331
350
|
if(nonempty(environment)&&!environmentNames.has(String(environment)))
|
|
332
351
|
add('environment-unknown','environment',`environment must be one the declaration admits: ${[...environmentNames].join(', ')}`);
|
|
333
352
|
const spec=environments[String(environment)],envDir=path.join(stacksRoot,String(environment??''));
|
|
334
353
|
if(object(spec))checkEnvironmentSpec({spec,environment,envDir,ids,root,add});
|
|
335
|
-
checkModel({model,ids,spec,manifest,lite,add});
|
|
354
|
+
checkModel({model,ids,spec,manifest,lite,repo,add});
|
|
336
355
|
};
|
|
337
356
|
const checkStacksManifest=(manifest,model,{stacksRoot,environment,root,repo,add})=>{
|
|
338
357
|
for(const issue of validateSchema(manifest))add('schema-shape-invalid',issue.path,issue.message);
|
|
@@ -342,7 +361,7 @@ const checkStacksManifest=(manifest,model,{stacksRoot,environment,root,repo,add}
|
|
|
342
361
|
checkStacksTree({stacksRoot,manifest,environmentNames,add,lite});
|
|
343
362
|
checkKubernetesDeclaration(manifest,add);
|
|
344
363
|
for(const issue of swarmInventoryIssues({components:manifest.components,lite}))add('HFS_STACKS_SHAPE',issue.at,issue.message);
|
|
345
|
-
checkSelectedEnvironment({environment,environmentNames,environments,stacksRoot,ids,root,model,manifest,lite,add});
|
|
364
|
+
checkSelectedEnvironment({environment,environmentNames,environments,stacksRoot,ids,root,model,manifest,lite,repo,add});
|
|
346
365
|
};
|
|
347
366
|
|
|
348
367
|
function deploymentAvailable(model,errors){
|
|
@@ -370,8 +389,10 @@ function checkSwarmHardening({manifest,model,environment,root,files,repo,errors,
|
|
|
370
389
|
|
|
371
390
|
export function checkApplicationStacks({repoRoot,environment,deploymentModelFile,files,repo}={}){
|
|
372
391
|
const root=path.resolve(String(repoRoot??'')),stacksRoot=path.join(root,STACKS_DIR),manifestFile=path.join(stacksRoot,'application-stacks.yaml'),errors=[];
|
|
373
|
-
const declaration=
|
|
392
|
+
const diskDeclaration=readJson(root,'hfs.json'),declaration={edition:'full',...(object(diskDeclaration)?diskDeclaration:{}),...(object(repo)?repo:{})};
|
|
374
393
|
const add=(code,at,message)=>errors.push({code,path:at,message});
|
|
394
|
+
if(fs.existsSync(path.join(root,'hfs.json'))&&!object(diskDeclaration))
|
|
395
|
+
add('HFS_STACKS_SHAPE','hfs.json','the HFS declaration must be a readable JSON object before application identities can be checked');
|
|
375
396
|
checkInputPaths({environment,stacksRoot,manifestFile,deploymentModelFile,add});
|
|
376
397
|
const {manifest,model}=loadInputs({manifestFile,deploymentModelFile,errors,add});
|
|
377
398
|
checkStacksManifest(manifest,model,{stacksRoot,environment,root,repo:declaration,add});
|
|
@@ -14,7 +14,7 @@ const isAbsoluteAny = (text) => path.isAbsolute(text) || /^[A-Za-z]:\//.test(tex
|
|
|
14
14
|
*
|
|
15
15
|
* @param {object} opened - The result of `openHfs` (manifest, repo and resolver methods).
|
|
16
16
|
* @param {string} repoRoot - The absolute repository root.
|
|
17
|
-
* @returns {object} The frozen view: profile, apps, connections, ruleParams, relative, classify, slotOf, tierOf, ownerOf, slot.
|
|
17
|
+
* @returns {object} The frozen view: profile, apps, connections, patterns, ruleParams, relative, classify, slotOf, tierOf, ownerOf, slot.
|
|
18
18
|
*/
|
|
19
19
|
function hfsView(opened, repoRoot) {
|
|
20
20
|
const rel = (file) => {
|
|
@@ -35,6 +35,7 @@ function hfsView(opened, repoRoot) {
|
|
|
35
35
|
edition: opened.repo.edition ?? 'full',
|
|
36
36
|
apps: opened.repo.apps,
|
|
37
37
|
connections: opened.repo.connections,
|
|
38
|
+
patterns: Object.freeze([...(opened.repo.patterns ?? [])]),
|
|
38
39
|
ruleParams: opened.ruleParams(),
|
|
39
40
|
/** The repository-relative, forward-slash form of a linted filename. */
|
|
40
41
|
relative: rel,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// app-data.mjs — the per-OS application-data base directory (the parent of 'orca', 'codex-runtime-home'
|
|
2
|
+
// and friends): %APPDATA% on Windows, ~/Library/Application Support on macOS, $XDG_CONFIG_HOME|~/.config elsewhere.
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
/** The application-data base directory of this platform: {env, platform, home}: the caller passes the environment. */
|
|
7
|
+
export const appDataBase = ({ env, platform = process.platform, home = os.homedir() } = {}) => {
|
|
8
|
+
if (platform === 'win32') return env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
9
|
+
if (platform === 'darwin') return path.join(home, 'Library', 'Application Support');
|
|
10
|
+
return env.XDG_CONFIG_HOME || path.join(home, '.config');
|
|
11
|
+
};
|
|
@@ -1,116 +1,313 @@
|
|
|
1
1
|
// bypass-markers.mjs - what a BYPASS of a check looks like in text (owner ruling loosening-always-red: any bypass is red, always).
|
|
2
|
-
// One table serves both sides of the no-bypass law:
|
|
3
|
-
// product scripts/hfs/rules/bypass-markers.mjs (HFS_BYPASS_MARKER, `starci app check`) and `starci app hygiene` (the pre-commit
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// its
|
|
2
|
+
// One table serves both sides of the no-bypass law, and both read a whole file as it stands (no history):
|
|
3
|
+
// product scripts/hfs/rules/bypass-markers.mjs (HFS_BYPASS_MARKER, `starci app check`) and `starci app hygiene` (the pre-commit hook);
|
|
4
|
+
// runtime scripts/lib/loosening-state.mjs (RT_LOOSENING_STATE, `starci runtime check`).
|
|
5
|
+
// A marker is a way to make a check pass without satisfying it: an inline disable of any tool, a type-check escape, a Sonar or coverage
|
|
6
|
+
// exclusion comment, a skipped or focused test, a failure swallowed in a hook, a script or a workflow step, a hook skipped.
|
|
7
|
+
// A directive is read where it acts: a suppression word at the start of a comment, a test marker in code, a swallowed failure in a shell line or
|
|
8
|
+
// in the string a script hands a shell. The same words quoted as data (a message, a ban list, a fixture) are no directive.
|
|
9
|
+
// No file is skipped for its place or its name: a generated file or a folder named build is read like any other (a generator that emits a marker
|
|
10
|
+
// is a loosening too); build output is untracked and never reaches a tree check.
|
|
11
|
+
// The patterns are assembled from fragments (joined) so that this file never spells a marker itself.
|
|
12
|
+
import { langOf, lexSource, splitLines } from './js-lex.mjs';
|
|
13
|
+
|
|
10
14
|
const join = (...parts) => parts.join('');
|
|
11
|
-
const COMMENT_OPEN = /\/\/|\/\*/g;
|
|
12
15
|
|
|
13
16
|
const SUPPRESSION_WORDS = [
|
|
14
17
|
join('eslint', '-disable'), join('stylelint', '-disable'), join('tslint', ':disable'), join('prettier', '-ignore'), join('biome', '-ignore'),
|
|
15
|
-
join('@ts', '-ignore'), join('@ts', '-expect-error'), join('@ts', '-nocheck'),
|
|
16
|
-
join('istanbul', ' ignore'), join('c8', ' ignore'), join('v8', ' ignore'), join('jscpd', ':ignore'),
|
|
18
|
+
join('@ts', '-ignore'), join('@ts', '-expect-error'), join('@ts', '-nocheck'),
|
|
19
|
+
join('istanbul', ' ignore'), join('c8', ' ignore'), join('v8', ' ignore'), join('jscpd', ':ignore'), join('eslint', '-env'),
|
|
17
20
|
];
|
|
18
21
|
const escaped = (word) => word.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
19
|
-
|
|
22
|
+
/** A suppression directive opens its comment: after the opener only blanks and asterisks may precede it. */
|
|
23
|
+
const DIRECTIVE = new RegExp(String.raw`^[\s*/]*(${SUPPRESSION_WORDS.map(escaped).join('|')})`);
|
|
24
|
+
/** Inline configuration ESLint honours at the start of a comment: a rule level, declared globals or exports. */
|
|
25
|
+
const INLINE_CONFIG = new RegExp(String.raw`^[\s*/]*(${join('eslint')}\s+[@\w/.-]+\s*:|globals?\s|exported\b)`);
|
|
26
|
+
/** The type-check escapes: TypeScript reads them on the last line of a block comment too. */
|
|
27
|
+
const TYPE_ESCAPES = SUPPRESSION_WORDS.filter((word) => word.startsWith('@ts'));
|
|
28
|
+
/** Sonar reads its exclusion word anywhere in a comment. */
|
|
29
|
+
const SONAR_WORD = join('NO', 'SONAR');
|
|
20
30
|
|
|
21
|
-
const
|
|
22
|
-
|
|
31
|
+
const RUNNER = '(?:it|test|describe|suite)';
|
|
32
|
+
const ANYWHERE_TESTS = [
|
|
33
|
+
[new RegExp(String.raw`\b${RUNNER}(?:\.\w+)*\.(?:skip|only|todo|failing)\b`), 'a skipped or focused test'],
|
|
34
|
+
[new RegExp(String.raw`\b${RUNNER}(?:\.\w+)*\.(?:skipIf|runIf)\(`), 'a conditionally skipped test'],
|
|
35
|
+
[new RegExp(String.raw`\{[^}]*\b(?:skip|only|todo)\b[^}]*\}\s*=\s*${RUNNER}\b`), 'a skip taken from the test runner'],
|
|
36
|
+
];
|
|
37
|
+
/** Names that are ordinary identifiers outside a test file: read only in a spec or test file. */
|
|
38
|
+
const SPEC_TESTS = [
|
|
23
39
|
[new RegExp(String.raw`\b(?:xit|xtest|xdescribe|fit|fdescribe)\(`), 'a skipped or focused test'],
|
|
24
|
-
[new RegExp(String.raw
|
|
25
|
-
[new RegExp(String.raw
|
|
40
|
+
[new RegExp(String.raw`\b(?:skip|todo)\s*:\s*(?!false\b|\d|\[)\S|\bonly\s*:\s*true\b`), 'a skip option'],
|
|
41
|
+
[new RegExp(String.raw`\.\s*(?:skip|todo)\s*\(`), 'a skipped test through its context'],
|
|
26
42
|
[new RegExp(String.raw`\bpending\(\)`), 'a pending test'],
|
|
27
43
|
];
|
|
44
|
+
const TEST_KEYS = new Set(['skip', 'only', 'todo', 'failing']);
|
|
45
|
+
|
|
46
|
+
const CHILD = '(?:execSync|spawnSync|execFileSync)';
|
|
47
|
+
/** Exit codes that a script resets or a helper swallows (scripts, hooks and tools; never specs). */
|
|
48
|
+
const EXIT_PATTERNS = [
|
|
49
|
+
[new RegExp(String.raw`\btry\s*\{[^{}]*\b${CHILD}\b[^{}]*\}\s*catch\s*(?:\(\s*\w*\s*\))?\s*\{\s*\}`), 'a child failure swallowed by an empty catch'],
|
|
50
|
+
[new RegExp(String.raw`\b${CHILD}\([^;]*\);\s*process\.exit\(\s*0\s*\)`), 'an exit 0 right after a child whose status is never read'],
|
|
51
|
+
];
|
|
52
|
+
const EXIT_EVENTS = new Set(['exit', 'uncaughtException', 'unhandledRejection']);
|
|
53
|
+
const EXIT_ZERO = /exitCode\s*=\s*0|exit\(\s*0\s*\)/;
|
|
54
|
+
|
|
55
|
+
/** The text after the `=>` of a handler argument that follows the event name (`, () => ...`), or null when it is not one. */
|
|
56
|
+
function arrowBody(after) {
|
|
57
|
+
const text = after.trimStart();
|
|
58
|
+
const arrow = text.indexOf('=>');
|
|
59
|
+
return text.startsWith(',') && arrow > 0 && arrow < 60 ? text.slice(arrow + 2).trimStart() : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Whether the handler that starts at `after` (what follows the event name) is empty, resets the exit code or exits 0. */
|
|
63
|
+
function resetsExit(after) {
|
|
64
|
+
const body = arrowBody(after);
|
|
65
|
+
if (body === null) return false;
|
|
66
|
+
if (!body.startsWith('{')) return EXIT_ZERO.test(body.slice(0, 40));
|
|
67
|
+
const inner = body.slice(1, Math.max(1, body.indexOf('}')));
|
|
68
|
+
return inner.trim() === '' || EXIT_ZERO.test(inner);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Whether a line commits with a short flag group that holds n, which skips the hooks (`-n`, `-nm`, `-an`). */
|
|
72
|
+
function commitsWithShortSkip(line) {
|
|
73
|
+
const words = line.split(/\s+/);
|
|
74
|
+
const at = words.indexOf('commit');
|
|
75
|
+
return words.includes('git') && at >= 0 && words.slice(at + 1).some((word) => /^-[a-zA-Z]+$/.test(word) && word.includes('n'));
|
|
76
|
+
}
|
|
28
77
|
|
|
29
78
|
const SWALLOW = [
|
|
30
|
-
[/\|\|\s*(?:true
|
|
79
|
+
[/\|\|\s*(?:true\b|:|"true"|'true'|\/(?:usr\/)?bin\/true|exit\s+0\b|return\s+0\b|echo\b|printf\b)/, 'a failure swallowed with or-true'],
|
|
80
|
+
[/;\s*(?:true\b(?!\s*[,:(.=])|:\s*$)/, 'a failure swallowed with semicolon-true'],
|
|
31
81
|
[/\bcontinue-on-error\s*:\s*(?!false\b)\S/, join('continue', '-on-error')],
|
|
32
|
-
[/\bset\s+\+e
|
|
33
|
-
[/\
|
|
82
|
+
[/\bset\s+\+[a-z]*e/, join('set ', '+e')],
|
|
83
|
+
[/\bset\s+\+o\s+errexit\b/, join('set ', '+o errexit')],
|
|
84
|
+
[/\btrap\s+['"]?exit\s+0/, join('trap ', 'exit 0')],
|
|
85
|
+
[/\bHUSKY\s*=\s*0\b/, join('HUSKY', '=0')],
|
|
34
86
|
[/--no-verify\b/, '--no-verify'],
|
|
87
|
+
[{ test: commitsWithShortSkip }, join('git commit ', '-n')],
|
|
88
|
+
[/-c\s+core\.hooksPath=[/\w]/, join('core.hooks', 'Path redirected')],
|
|
35
89
|
[/\bfail_ci_if_error\s*:\s*false\b/, 'fail_ci_if_error: false'],
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
// A weakening of the run itself that today's rendered canon still spells in places (the CI template runs jest with it); the runtime
|
|
39
|
-
// detector refuses it as an ADDED line, the product rule waits until the canon is rid of it.
|
|
40
|
-
const PENDING = [
|
|
41
|
-
[/--passWithNoTests\b/, '--passWithNoTests'],
|
|
90
|
+
[/--(?:passWithNoTests|pass-with-no-tests)\b/, '--passWithNoTests'],
|
|
42
91
|
[/--coverageThreshold\b/, '--coverageThreshold'],
|
|
92
|
+
[/--no-error-on-unmatched-pattern\b/, '--no-error-on-unmatched-pattern'],
|
|
93
|
+
[/--max-warnings(?:\s+|=)(?!0\b)\S/, 'a warning budget'],
|
|
94
|
+
[/--continue-on-error\b|--keep-going\b/, join('--continue', '-on-error')],
|
|
95
|
+
[/\beslint\b[^\n|;&]*--quiet\b/, 'eslint --quiet'],
|
|
96
|
+
[/--(?:rule\s+['"]?[@\w/-]+:\s*(?:off|0)|ignore-pattern|testPathIgnorePatterns)\b/, 'a check narrowed on the command line'],
|
|
97
|
+
[/-Dsonar\.(?:[\w.]*(?:exclusions|inclusions|sources)\b|qualitygate\.wait=(?!true\b))/, 'a Sonar scope set on the command line'],
|
|
98
|
+
[/sonar\.qualitygate\.wait\s*=\s*false/, 'a Sonar gate not waited for'],
|
|
43
99
|
];
|
|
100
|
+
/** The failures a script hands a shell in a string: the commands only, never a flag a message names. */
|
|
101
|
+
const COMMAND_SWALLOW = SWALLOW.filter(([, label]) => /or-true|semicolon-true|set \+|trap exit|HUSKY|git commit -n|Path redirected/.test(label));
|
|
102
|
+
const GIT_VERBS = new Set(['commit', 'push', 'merge', 'rebase', 'cherry-pick', 'am', 'revert']);
|
|
103
|
+
/** A git command line carrying the flag: after the flag only more flags, operators or quoted and variable words follow, never prose. */
|
|
104
|
+
const GIT_COMMAND = /^\s*git\s(?:\S+\s)*?--no-verify(?:\s+(?:-\S*|[&|;>"'$]\S*))*\s*$/;
|
|
105
|
+
const GIT_LINE_WITH_MESSAGE = /^\s*git\s.*--no-verify\b.*\s-m\s/;
|
|
44
106
|
|
|
107
|
+
/** Workflow lines that make a job or a step not run, or a shell not stop on a failure. */
|
|
108
|
+
const WORKFLOW_PATTERNS = [
|
|
109
|
+
[/^\s*shell:\s*bash\s*\{0\}/, 'a shell that does not stop on a failure'],
|
|
110
|
+
];
|
|
45
111
|
const CODE_FILE = /\.(?:[cm]?[jt]sx?|vue|svelte|css|scss|less)$/;
|
|
46
|
-
const SHELL_FILE = /(?:^|\/)(?:\.husky\/(?!_\/)[^/]+|[^/]+\.sh|hooks\/husky\/[^/]+)$/;
|
|
47
|
-
const WORKFLOW_FILE = /(?:^|\/)(?:\.github|github)\/(?:workflows|actions)
|
|
112
|
+
const SHELL_FILE = /(?:^|\/)(?:\.(?:husky|githooks)\/(?!_\/)[^/]+|[^/]+\.sh|hooks\/husky\/[^/]+)$/;
|
|
113
|
+
const WORKFLOW_FILE = /(?:^|\/)(?:(?:\.github|github)\/(?:workflows|actions)\/.+|ci-workflows(?:-lite)?\/[^/]+|lefthook)\.ya?ml$/;
|
|
114
|
+
const DOCKERFILE = /(?:^|\/)Dockerfile(?:\.[\w-]+)?$/;
|
|
48
115
|
const MANIFEST_FILE = /(?:^|\/)package\.json$/;
|
|
49
|
-
const
|
|
50
|
-
const
|
|
116
|
+
const TEST_FILE = /(?:\.(?:spec|test|e2e-spec|integration-spec|contract-spec)\.[cm]?[jt]sx?$|(?:^|\/)(?:__tests__|tests?|e2e)\/)/;
|
|
117
|
+
const SHEBANG = /^#!\s*(?:\/usr\/bin\/env\s+)?\S*(?:sh|bash|zsh|dash)\b/;
|
|
118
|
+
const NAMED_SCRIPT = /\.\w+$/;
|
|
51
119
|
|
|
52
|
-
/** The kind of text `file` holds for the markers: 'code', 'shell' (hooks, scripts), 'workflow', 'manifest', or null
|
|
53
|
-
export function bypassFileKind(file) {
|
|
54
|
-
if (SKIPPED_TREE.test(file) || GENERATED_NAME.test(file)) return null;
|
|
120
|
+
/** The kind of text `file` holds for the markers: 'code', 'shell' (hooks, scripts), 'workflow', 'dockerfile', 'manifest', or null. `text` lets an extensionless file with a shell shebang count as a script. */
|
|
121
|
+
export function bypassFileKind(file, text = '') {
|
|
55
122
|
if (SHELL_FILE.test(file)) return 'shell';
|
|
56
123
|
if (WORKFLOW_FILE.test(file)) return 'workflow';
|
|
124
|
+
if (DOCKERFILE.test(file)) return 'dockerfile';
|
|
57
125
|
if (MANIFEST_FILE.test(file)) return 'manifest';
|
|
58
126
|
if (CODE_FILE.test(file)) return 'code';
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
127
|
+
return !NAMED_SCRIPT.test(file.slice(file.lastIndexOf('/') + 1)) && SHEBANG.test(text) ? 'shell' : null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The text of a shell line without the read-only git queries in its `$(...)` and backticks: `|| true` inside one is a query, not a bypass. */
|
|
131
|
+
const QUERY = String.raw`git\s+(?:tag|rev-parse|config|ls-files|describe|log|show|status|branch|remote|diff|symbolic-ref|rev-list)\b[^()\`]*`;
|
|
132
|
+
const withoutSubstitutions = (line) => line.replace(new RegExp(String.raw`\$\(\s*${QUERY}\)`, 'g'), '').replace(new RegExp(String.raw`\`\s*${QUERY}\``, 'g'), '');
|
|
133
|
+
|
|
134
|
+
const labelsOf = (pairs, text) => pairs.filter(([pattern]) => pattern.test(text)).map(([, label]) => label);
|
|
135
|
+
|
|
136
|
+
/** The labels of the bypass markers on one `line` of a shell, Dockerfile or workflow file ([] when none). */
|
|
137
|
+
function textLabelsOf(kind, line) {
|
|
138
|
+
const text = withoutSubstitutions(line);
|
|
139
|
+
if (/^\s*#/.test(text)) return [];
|
|
140
|
+
const own = kind === 'workflow' ? WORKFLOW_PATTERNS : [];
|
|
141
|
+
return [...labelsOf(SWALLOW, text), ...labelsOf(own, text)];
|
|
73
142
|
}
|
|
74
143
|
|
|
75
|
-
|
|
76
|
-
const
|
|
144
|
+
const CONSTANT_FALSE = /^\s*(?:-\s+)?if:\s*(?:\$\{\{\s*)?false\b/;
|
|
145
|
+
const CHECK_STEP = /(?:run|uses|name):[^\n]*(?:test|lint|check|audit|tsc|coverage|sonar|codecov|jest|eslint|stylelint|prettier|verify|scan)/i;
|
|
77
146
|
|
|
78
|
-
|
|
147
|
+
/** A job or a check step of a workflow that a constant false condition keeps from running (a setup step disabled for an app without build values is none). */
|
|
148
|
+
function disabledHits(text) {
|
|
149
|
+
const lines = text.split('\n');
|
|
150
|
+
const hits = [];
|
|
151
|
+
lines.forEach((content, index) => {
|
|
152
|
+
if (!CONSTANT_FALSE.test(content)) return;
|
|
153
|
+
const indent = content.search(/\S/);
|
|
154
|
+
const label = 'a constant false condition';
|
|
155
|
+
if (indent === 4) { hits.push({ line: index + 1, label, text: content.trim() }); return; }
|
|
156
|
+
let start = index;
|
|
157
|
+
while (start > 0 && !/^\s*-\s/.test(lines[start]) && lines[start].search(/\S/) >= indent) start -= 1;
|
|
158
|
+
let end = index + 1;
|
|
159
|
+
while (end < lines.length && (lines[end].trim() === '' || lines[end].search(/\S/) >= indent) && !/^\s*-\s/.test(lines[end])) end += 1;
|
|
160
|
+
if (CHECK_STEP.test(lines.slice(start, end).join('\n'))) hits.push({ line: index + 1, label, text: content.trim() });
|
|
161
|
+
});
|
|
162
|
+
return hits;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** A job-level `if: always()` of a workflow that never reads a result of the jobs it needs. */
|
|
166
|
+
function alwaysHits(text) {
|
|
167
|
+
const reads = /needs\.[\w-]+\.result|needs\.\*|toJSON\(needs\)/.test(text);
|
|
168
|
+
return text.split('\n').flatMap((content, index) => (/^ {4}if:\s*(?:\$\{\{\s*)?always\(\)/.test(content) && !reads
|
|
169
|
+
? [{ line: index + 1, label: 'always() without reading the results of the needed jobs', text: content.trim() }] : []));
|
|
170
|
+
}
|
|
79
171
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
172
|
+
/** The directive a comment line is, or null: a suppression word opens its comment; the Sonar word may stand anywhere in one. */
|
|
173
|
+
function commentDirective(comment, opening) {
|
|
174
|
+
const inline = comment.block ? INLINE_CONFIG.exec(comment.text)?.[1]?.trim() : undefined;
|
|
175
|
+
const open = opening ? (DIRECTIVE.exec(comment.text)?.[1] ?? inline) : null;
|
|
176
|
+
const closing = comment.last ? TYPE_ESCAPES.find((word) => comment.text.replace(/^[\s*]+/, '').startsWith(word)) : null;
|
|
177
|
+
return open ?? closing ?? (comment.text.includes(SONAR_WORD) ? SONAR_WORD : null);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** The comments with the line a directive may open: the first non-blank line of each comment. */
|
|
181
|
+
function commentHits(comments) {
|
|
182
|
+
let waiting = false;
|
|
183
|
+
return comments.flatMap((comment) => {
|
|
184
|
+
if (comment.first) waiting = true;
|
|
185
|
+
const opening = waiting && comment.text.trim() !== '';
|
|
186
|
+
if (opening) waiting = false;
|
|
187
|
+
const directive = commentDirective(comment, opening);
|
|
188
|
+
return directive ? [{ line: comment.line, label: directive, text: comment.text.trim() }] : [];
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const OPENERS = '([{';
|
|
193
|
+
const CLOSERS = ')]}';
|
|
194
|
+
|
|
195
|
+
/** The index of the bracket that encloses `at` in the blanked `code` (walking back, skipping balanced pairs), or -1. */
|
|
196
|
+
function enclosingOpen(code, at) {
|
|
197
|
+
let depth = 0;
|
|
198
|
+
for (let index = at; index >= 0; index -= 1) {
|
|
199
|
+
if (CLOSERS.includes(code[index])) depth += 1;
|
|
200
|
+
else if (OPENERS.includes(code[index]) && depth === 0) return index;
|
|
201
|
+
else if (OPENERS.includes(code[index])) depth -= 1;
|
|
85
202
|
}
|
|
86
|
-
return
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** The
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}).map(([, label]) => label);
|
|
97
|
-
return [...(comment ? [comment] : []), ...tests];
|
|
203
|
+
return -1;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** The index of the `]` that closes the `[` at `open`, or -1. */
|
|
207
|
+
function closingBracket(code, open) {
|
|
208
|
+
let depth = 0;
|
|
209
|
+
for (let index = open + 1; index < code.length; index += 1) {
|
|
210
|
+
if (code[index] === '[') depth += 1;
|
|
211
|
+
else if (code[index] === ']' && depth === 0) return index;
|
|
212
|
+
else if (code[index] === ']') depth -= 1;
|
|
98
213
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
214
|
+
return -1;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** The offsets of the brackets of the array that holds `at` in the blanked `code`, or null when no array does. */
|
|
218
|
+
function arrayAround(code, at) {
|
|
219
|
+
const open = enclosingOpen(code, at);
|
|
220
|
+
if (open < 0 || code[open] !== '[') return null;
|
|
221
|
+
const close = closingBracket(code, open);
|
|
222
|
+
return close < 0 ? null : [open, close];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Whether the string `piece` hands git the hook-skipping flag: a git command line, or an element of an array that also names a git verb. */
|
|
226
|
+
function skipsHooks(piece, code, strings) {
|
|
227
|
+
if (GIT_COMMAND.test(piece.text) || GIT_LINE_WITH_MESSAGE.test(piece.text)) return true;
|
|
228
|
+
if (piece.text.trim() !== '--no-verify') return false;
|
|
229
|
+
const around = arrayAround(code, piece.at);
|
|
230
|
+
return around !== null && strings.some((other) => other.at > around[0] && other.at < around[1] && GIT_VERBS.has(other.text));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** A test marker spelled as an index (`describe['skip']`): a string equal to a marker right after a `[`. */
|
|
234
|
+
const indexedMarker = (piece, code) => TEST_KEYS.has(piece.text) && code.slice(0, piece.at).trimEnd().endsWith('[');
|
|
235
|
+
|
|
236
|
+
/** An `exit` handler of the process that resets the exit code or swallows the failure. */
|
|
237
|
+
function exitHandler(piece, code) {
|
|
238
|
+
if (!EXIT_EVENTS.has(piece.text)) return false;
|
|
239
|
+
const before = code.slice(Math.max(0, piece.at - 24), piece.at);
|
|
240
|
+
return /process\s*\.\s*(?:on|once)\(\s*['"]?$/.test(before) && resetsExit(code.slice(piece.at + piece.text.length + 1, piece.at + piece.text.length + 200));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function stringHits(strings, code) {
|
|
244
|
+
const hits = [];
|
|
245
|
+
for (const piece of strings) {
|
|
246
|
+
piece.text.split('\n').forEach((content, offset) => {
|
|
247
|
+
for (const label of labelsOf(COMMAND_SWALLOW, withoutSubstitutions(content))) hits.push({ line: piece.line + offset, label, text: content.trim() });
|
|
248
|
+
});
|
|
249
|
+
if (skipsHooks(piece, code, strings)) hits.push({ line: piece.line, label: '--no-verify', text: piece.text.trim() });
|
|
250
|
+
if (/\bHUSKY\b/.test(code.slice(Math.max(0, piece.at - 12), piece.at)) && /^["']?0$/.test(piece.text.trim())) hits.push({ line: piece.line, label: join('HUSKY', '=0'), text: piece.text });
|
|
251
|
+
}
|
|
252
|
+
return hits;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** The exit patterns of a script, matched over the whole code (a try block and the call that follows it span lines). */
|
|
256
|
+
function exitHits(code, sourceLines) {
|
|
257
|
+
return EXIT_PATTERNS.flatMap(([pattern, label]) => [...code.matchAll(new RegExp(pattern.source, 'g'))].map((match) => {
|
|
258
|
+
const line = splitLines(code.slice(0, match.index)).length;
|
|
259
|
+
return { line, label, text: (sourceLines[line - 1] ?? '').trim() };
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function codeHits(file, text) {
|
|
264
|
+
const { code, comments, strings } = lexSource(text, langOf(file));
|
|
265
|
+
const sourceLines = splitLines(text);
|
|
266
|
+
const spec = TEST_FILE.test(file);
|
|
267
|
+
const hits = commentHits(comments);
|
|
268
|
+
const tests = spec ? [...ANYWHERE_TESTS, ...SPEC_TESTS] : ANYWHERE_TESTS.slice(0, 2);
|
|
269
|
+
splitLines(code).forEach((content, index) => {
|
|
270
|
+
for (const label of labelsOf(tests, content)) hits.push({ line: index + 1, label, text: (sourceLines[index] ?? '').trim() });
|
|
271
|
+
});
|
|
272
|
+
if (!spec) hits.push(...exitHits(code, sourceLines));
|
|
273
|
+
for (const piece of strings) {
|
|
274
|
+
if (spec && indexedMarker(piece, code)) hits.push({ line: piece.line, label: 'a skipped or focused test', text: piece.text });
|
|
275
|
+
if (!spec && exitHandler(piece, code)) hits.push({ line: piece.line, label: 'a process exit handler that resets the exit code', text: piece.text });
|
|
104
276
|
}
|
|
105
|
-
|
|
277
|
+
const seen = new Set();
|
|
278
|
+
return [...hits, ...stringHits(strings, code)].sort((x, y) => x.line - y.line).filter((hit) => !seen.has(hit.line) && seen.add(hit.line));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** package.json: every command a shell runs (scripts, lint-staged, husky, simple-git-hooks) and a tool config kept outside its own file. */
|
|
282
|
+
const COMMAND_KEYS = ['scripts', 'lint-staged', 'husky', 'simple-git-hooks'];
|
|
283
|
+
const TOOL_BLOCKS = ['jest', 'eslintConfig', 'stylelint'];
|
|
284
|
+
|
|
285
|
+
function commandStrings(value, name) {
|
|
286
|
+
if (typeof value === 'string') return [[name, value]];
|
|
287
|
+
if (value === null || typeof value !== 'object') return [];
|
|
288
|
+
return Object.entries(value).flatMap(([key, inner]) => commandStrings(inner, Array.isArray(value) ? name : key));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function manifestHits(text) {
|
|
292
|
+
let doc;
|
|
293
|
+
try { doc = JSON.parse(text); } catch { return []; }
|
|
294
|
+
const lines = text.split('\n');
|
|
295
|
+
const lineOf = (name) => lines.findIndex((line) => line.includes(JSON.stringify(name))) + 1;
|
|
296
|
+
const commands = COMMAND_KEYS.flatMap((key) => commandStrings(doc?.[key], key));
|
|
297
|
+
const hits = commands.flatMap(([name, command]) => labelsOf(SWALLOW, withoutSubstitutions(command)).map((label) => ({ line: lineOf(name), label, text: command.trim() })));
|
|
298
|
+
const blocks = TOOL_BLOCKS.filter((key) => doc?.[key] !== undefined).map((key) => ({ line: lineOf(key), label: `a ${key} config kept in package.json`, text: key }));
|
|
299
|
+
return [...hits, ...blocks];
|
|
106
300
|
}
|
|
107
301
|
|
|
108
302
|
/**
|
|
109
303
|
* Every bypass marker of the text of one file: [{line, label, text}] (line numbers from 1). The file's kind decides the patterns;
|
|
110
304
|
* a file of no kind has none.
|
|
111
305
|
*/
|
|
112
|
-
export function bypassesIn(file, text
|
|
113
|
-
const kind = bypassFileKind(file);
|
|
306
|
+
export function bypassesIn(file, text) {
|
|
307
|
+
const kind = bypassFileKind(file, text);
|
|
114
308
|
if (!kind || typeof text !== 'string') return [];
|
|
115
|
-
|
|
309
|
+
if (kind === 'code') return codeHits(file, text);
|
|
310
|
+
if (kind === 'manifest') return manifestHits(text);
|
|
311
|
+
const hits = text.split(/\r?\n/).flatMap((content, index) => textLabelsOf(kind, content).map((label) => ({ line: index + 1, label, text: content.trim() })));
|
|
312
|
+
return kind === 'workflow' ? [...hits, ...alwaysHits(text), ...disabledHits(text)] : hits;
|
|
116
313
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// check-format.mjs — the one-line-per-check report every canon check CLI shares.
|
|
2
|
+
|
|
3
|
+
const MARK = { pass: 'pass', fail: 'FAIL', skip: 'skip' };
|
|
4
|
+
|
|
5
|
+
/** `header` plus one ' [outcome] id: detail' line per check of `checks`, joined with '\n'. */
|
|
6
|
+
export const formatCheckLines = (header, checks) =>
|
|
7
|
+
[header, ...(checks ?? []).map((entry) => ` [${MARK[entry.outcome]}] ${entry.id}: ${entry.detail}`)].join('\n');
|