@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,483 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// draw-dna.mjs — owner ruling 2026-09-27: interface.draw composes ONLY the Grammar's DNA components
|
|
3
|
+
// (knowledge/grammars/<family>/DNA.yaml renderers[]: 95 components, each with its anatomy classes and closed prop
|
|
4
|
+
// values). A drawing is a real HTML render, so the render source says what each visible element IS:
|
|
5
|
+
//
|
|
6
|
+
// data-grammar-component="<DNA name>" the component the element renders (Alert, Meter, SurfaceCard, Button ...)
|
|
7
|
+
// data-grammar-part="<part>" the anatomy part of the enclosing component (alert-actions, meter-fill ...;
|
|
8
|
+
// the DNA class minus `starci-core-`, or minus the component slug too)
|
|
9
|
+
// data-grammar-proposal="<name>" a gap DNA cannot express: the closest DNA composition stands in, and the
|
|
10
|
+
// proposal has an entry in grammar-proposal.md/.yaml (scripts/work/
|
|
11
|
+
// grammar-proposal.mjs) the owner is asked about - never invented inline
|
|
12
|
+
// data-<prop>="<value>" a closed prop value (data-tone, data-variant, data-size ...), judged against
|
|
13
|
+
// the DNA closedValues
|
|
14
|
+
//
|
|
15
|
+
// Refused per render source:
|
|
16
|
+
// DRAW_OFF_GRAMMAR_COMPONENT an element with none of the three attributes (pure phrasing - strong, em, br ... -
|
|
17
|
+
// inherits its parent's; an svg's inner shapes inherit the svg's); a component name DNA
|
|
18
|
+
// does not publish; a part none of the enclosing components publishes; a closed prop
|
|
19
|
+
// value outside its DNA values; a proposal with no entry in a grammar-proposal file;
|
|
20
|
+
// a status dot that is not the DNA Badge isDot dot (grammar 0.5.3: HeroUI's
|
|
21
|
+
// <CircleFill width={6} />, part badge-dot / class starci-core-badge-dot inside a Badge) -
|
|
22
|
+
// a hand-made dot span, a dot with a box-shadow/ring/outline halo, or a dot not 6px;
|
|
23
|
+
// DRAW_NOTICE_NOT_ALERT a notice that is not DNA `Alert` with a tone: an Alert without a tone in the
|
|
24
|
+
// PresentationState vocabulary, or a container (SurfaceCard, a bare div ...) posing as a
|
|
25
|
+
// notice - role alert/status or aria-live, notice/callout/banner vocabulary in its
|
|
26
|
+
// class, a non-neutral tone on the surface, or an outcome-toned IconTile beside an
|
|
27
|
+
// action (the r4 "white card + IconTile" notice);
|
|
28
|
+
// DRAW_RATIO_NOT_METER a ratio that is not one DNA `Meter`: role meter/progressbar or <meter>/<progress>
|
|
29
|
+
// outside Meter/Progress, a Progress or a hand-made bar/track/segment beside an "n/m"
|
|
30
|
+
// ratio, a hand-segmented Meter (segments without the DNA meter-segment anatomy), a Meter with no
|
|
31
|
+
// role=meter + aria-valuenow/aria-valuemax (DNA claims A11Y-3).
|
|
32
|
+
// DRAW_ALERT_ANATOMY (owner ruling 2026-09-27: the Alert is the REAL HeroUI Alert - @heroui/styles
|
|
33
|
+
// alert.css: `.alert` bg-surface + shadow-surface, a flex row; `.alert__indicator` a
|
|
34
|
+
// size-4 glyph with p-1 in the tone's soft-foreground; `.alert__title` text-sm/6 medium
|
|
35
|
+
// in the tone's soft-foreground; `.alert__description` muted; its action the grammar's
|
|
36
|
+
// Button in the variant the grammar Alert (0.5.3) gives its tone, as HeroUI's own Alert
|
|
37
|
+
// examples pair them: informative/accent -> primary, negative/danger -> danger, every
|
|
38
|
+
// other tone -> secondary) an Alert whose declared or computed background is a tone
|
|
39
|
+
// or any colour other than --surface; an IconTile, or an indicator 32px or larger,
|
|
40
|
+
// inside it; its indicator after its title rather than on the left; a hand-made (non-DNA)
|
|
41
|
+
// action inside it, a Button variant outside the DNA closedValues, or a DNA variant other
|
|
42
|
+
// than its tone's (a primary action on a warning Alert, a danger one on a non-danger Alert).
|
|
43
|
+
// DRAW_METER_TRACK (owner ruling 2026-09-27) a Meter track that is not the HeroUI h-2 (8px) track - h-1
|
|
44
|
+
// (4px) when segmented (DNA `Meter segments`, grammar 0.5.2) - spanning the full width
|
|
45
|
+
// of its band: a declared height other than that, a fixed pixel width
|
|
46
|
+
// (a stub), and - measured on the capture (draw-render `anatomy`) - a track narrower
|
|
47
|
+
// than its band's content box or segments of unequal width / wide gaps.
|
|
48
|
+
// DRAW_ASSET_SLOT_UNDECLARED (owner ruling 2026-09-27: artwork comes from interface.asset, never reused ad hoc)
|
|
49
|
+
// raster artwork (Image, MediaFrame, RankArtwork, a bare img) with no
|
|
50
|
+
// data-asset-slot="<id>", or a slot the drawing's asset-request.md does not request
|
|
51
|
+
// (scripts/work/asset-slot.mjs).
|
|
52
|
+
//
|
|
53
|
+
// starci work draw-dna <render.html> [--family starci] [--proposals <file>] [--json]
|
|
54
|
+
import fs from 'node:fs';
|
|
55
|
+
import path from 'node:path';
|
|
56
|
+
import { fileURLToPath } from 'node:url';
|
|
57
|
+
import { parseYaml } from '../../../engine/yaml.mjs';
|
|
58
|
+
import { PROPOSAL_FILE_NAMES, readProposals } from '../grammar-proposal.mjs'; import { isMain } from '../../lib/is-main.mjs';
|
|
59
|
+
import { ancestorsOf } from '../../lib/dom-tree.mjs';
|
|
60
|
+
import { list } from '../../lib/list.mjs';
|
|
61
|
+
import { appendDnaFindings } from './draw-dna-findings.mjs';
|
|
62
|
+
import { attributeSelectorsOf, withoutAttributeBlocks } from './draw-dna-selector.mjs';
|
|
63
|
+
import { ALERT_INDICATOR_MAX_PX, DRAW_ALERT_ANATOMY, DRAW_METER_TRACK, METER_FULL_WIDTH_SHARE, METER_SEGMENTED_TRACK_PX, METER_SEGMENT_GAP_MAX_PX, METER_TRACK_PX } from './draw-dna-anatomy.mjs';
|
|
64
|
+
export { DRAW_ALERT_ANATOMY, DRAW_METER_TRACK };
|
|
65
|
+
export { anatomyFindings } from './draw-dna-anatomy.mjs';
|
|
66
|
+
|
|
67
|
+
export const DRAW_OFF_GRAMMAR_COMPONENT = 'DRAW_OFF_GRAMMAR_COMPONENT';
|
|
68
|
+
export const DRAW_NOTICE_NOT_ALERT = 'DRAW_NOTICE_NOT_ALERT';
|
|
69
|
+
export const DRAW_RATIO_NOT_METER = 'DRAW_RATIO_NOT_METER';
|
|
70
|
+
/** Grammar 0.5.3 Badge isDot: the dot's class and its one size (HeroUI's <CircleFill width={6} />). */
|
|
71
|
+
const BADGE_DOT_CLASS = 'starci-core-badge-dot';
|
|
72
|
+
const BADGE_DOT_PX = 6;
|
|
73
|
+
const DOT_CLASS = /(?:^|[-_])(dot|status-dot|dot-indicator)(?:$|[-_])/i;
|
|
74
|
+
/**
|
|
75
|
+
* The grammar Alert's action Button variant for a PresentationState tone (@starci/grammar 0.5.3 Alert, after HeroUI v3's
|
|
76
|
+
* Alert examples: accent Alert -> primary "Refresh", danger Alert -> danger "Retry"; HeroUI has no success or warning
|
|
77
|
+
* Button variant, so every other tone keeps secondary).
|
|
78
|
+
*/
|
|
79
|
+
export const alertActionVariantFor = (tone) => tone === 'informative' && 'primary' || tone === 'negative' && 'danger' || 'secondary';
|
|
80
|
+
export const DRAW_ASSET_SLOT_UNDECLARED = 'DRAW_ASSET_SLOT_UNDECLARED';
|
|
81
|
+
/** The attribute a drawing marks an artwork slot with; interface.asset fills it (scripts/work/asset-slot.mjs). */
|
|
82
|
+
export const ASSET_SLOT_ATTR = 'data-asset-slot';
|
|
83
|
+
export const COMPONENT_ATTR = 'data-grammar-component';
|
|
84
|
+
export const PART_ATTR = 'data-grammar-part';
|
|
85
|
+
const PROPOSAL_ATTR = 'data-grammar-proposal';
|
|
86
|
+
/** The segmented-meter proposal the owner accepted; since grammar 0.5.2 it is DNA `Meter segments` (2..12). */
|
|
87
|
+
const METER_SEGMENTS_PROPOSAL = 'Meter.segments';
|
|
88
|
+
/** A segment of a Meter spelled with the DNA anatomy (part / class / grammar hook), not a hand-cut bar. */
|
|
89
|
+
const dnaSegment = (d) => /^(meter-)?segments?$/.test((d.attrs?.[PART_ATTR] ?? '').trim()) || classesOf(d).some((c) => /^starci-core-meter-segments?$/.test(c))
|
|
90
|
+
|| d.attrs?.['data-grammar-meter-segment'] != null || d.attrs?.['data-grammar-meter-segments'] != null;
|
|
91
|
+
/** Whether a Meter root draws segments (DNA `segments`, its hooks, or segment anatomy under it). */
|
|
92
|
+
const segmentedMeter = (el) => el.attrs?.['data-segments'] != null || el.attrs?.['data-grammar-meter-segments'] != null
|
|
93
|
+
|| walkElements(el).some((d) => dnaSegment(d) || /segment/i.test(`${d.attrs?.[PART_ATTR] ?? ''} ${classesOf(d).join(' ')}`));
|
|
94
|
+
|
|
95
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
|
|
96
|
+
const GRAMMARS = path.join(ROOT, 'knowledge', 'grammars');
|
|
97
|
+
const DEFAULT_FAMILY = 'starci';
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The Grammar's PresentationState vocabulary (knowledge/ui/composition/state.yaml: neutral, informative,
|
|
101
|
+
* affirmative, cautionary, negative, pending, unavailable) and the HeroUI tone names a drawing spells them with.
|
|
102
|
+
*/
|
|
103
|
+
const PRESENTATION_STATES = Object.freeze(['neutral', 'informative', 'affirmative', 'cautionary', 'negative', 'pending', 'unavailable']);
|
|
104
|
+
const TONE_ALIASES = Object.freeze({ default: 'neutral', muted: 'neutral', info: 'informative', accent: 'informative', primary: 'informative',
|
|
105
|
+
success: 'affirmative', warning: 'cautionary', danger: 'negative', error: 'negative' });
|
|
106
|
+
/** The tone of `value` in the PresentationState vocabulary, or null. */
|
|
107
|
+
export const presentationStateOf = (value) => {
|
|
108
|
+
const v = String(value ?? '').trim().toLowerCase();
|
|
109
|
+
return PRESENTATION_STATES.includes(v) ? v : (TONE_ALIASES[v] ?? null);
|
|
110
|
+
};
|
|
111
|
+
const OUTCOME_STATES = new Set(['affirmative', 'cautionary', 'negative']);
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
114
|
+
// A tolerant HTML reader: enough of the tree to know every element, its attributes, ancestry and text.
|
|
115
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
const VOID = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
|
|
118
|
+
const RAW = new Set(['script', 'style', 'template', 'textarea', 'title', 'noscript', 'xmp']);
|
|
119
|
+
const MARKUP_TAG_PATTERN = String.raw`<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|<![^>]*>|<\?[^>]*>`;
|
|
120
|
+
const END_TAG_PATTERN = String.raw`<\/([a-zA-Z][\w:-]*)\s*>`;
|
|
121
|
+
const START_TAG_NAME_PATTERN = String.raw`<([a-zA-Z][\w:-]*)`;
|
|
122
|
+
const START_TAG_ATTRIBUTES_PATTERN = "((?:\\s+[^\\s\"'>/=]+(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s\"'=<>`]+))?)*)\\s*(\\/?)>";
|
|
123
|
+
const TAG_RX = new RegExp(`${MARKUP_TAG_PATTERN}|${END_TAG_PATTERN}|${START_TAG_NAME_PATTERN}${START_TAG_ATTRIBUTES_PATTERN}`, 'g');
|
|
124
|
+
const ATTR_NAME_PATTERN = String.raw`([^\s"'>/=]+)`;
|
|
125
|
+
const ATTR_VALUE_PATTERN = "(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'=<>`]+)))?";
|
|
126
|
+
const ATTR_RX = new RegExp(`${ATTR_NAME_PATTERN}${ATTR_VALUE_PATTERN}`, 'g');
|
|
127
|
+
const decode = (s) => String(s).replaceAll(' ', ' ').replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll(/'|'/g, "'");
|
|
128
|
+
|
|
129
|
+
function parseAttrs(text) {
|
|
130
|
+
const out = {};
|
|
131
|
+
for (const m of String(text ?? '').matchAll(ATTR_RX)) {
|
|
132
|
+
const name = m[1].toLowerCase();
|
|
133
|
+
if (!Object.hasOwn(out, name)) out[name] = decode(m[2] ?? m[3] ?? m[4] ?? '');
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const appendParsedText = (node, text) => { if (text.trim()) node.children.push({ text: decode(text) }); };
|
|
139
|
+
|
|
140
|
+
function closeHtmlTag(current, tag) {
|
|
141
|
+
let at = current;
|
|
142
|
+
while (at) {
|
|
143
|
+
if (at.tag === tag) break;
|
|
144
|
+
at = at.parent;
|
|
145
|
+
}
|
|
146
|
+
return at?.parent ?? current;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function appendHtmlTag(s, match, current, index) {
|
|
150
|
+
if (match[1]) return { current: closeHtmlTag(current, match[1].toLowerCase()), index: null };
|
|
151
|
+
if (!match[2]) return { current, index: null };
|
|
152
|
+
const tag = match[2].toLowerCase();
|
|
153
|
+
const el = { tag, attrs: parseAttrs(match[3]), children: [], parent: current };
|
|
154
|
+
current.children.push(el);
|
|
155
|
+
if (RAW.has(tag)) {
|
|
156
|
+
const close = s.toLowerCase().indexOf(`</${tag}`, index);
|
|
157
|
+
const end = close < 0 ? s.length : close;
|
|
158
|
+
el.raw = s.slice(index, end);
|
|
159
|
+
const gt = close < 0 ? s.length : s.indexOf('>', close);
|
|
160
|
+
const nextIndex = gt < 0 ? s.length : gt + 1;
|
|
161
|
+
TAG_RX.lastIndex = nextIndex;
|
|
162
|
+
return { current, index: nextIndex };
|
|
163
|
+
}
|
|
164
|
+
return { current: !VOID.has(tag) && !match[4] ? el : current, index: null };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Parse html into {tag:'#root', children:[...]}; an element is {tag, attrs, children, parent, raw?}, a text {text}. */
|
|
168
|
+
export function parseHtml(html) {
|
|
169
|
+
const s = String(html ?? '');
|
|
170
|
+
const root = { tag: '#root', attrs: {}, children: [], parent: null };
|
|
171
|
+
let cur = root, i = 0;
|
|
172
|
+
TAG_RX.lastIndex = 0;
|
|
173
|
+
for (let m = TAG_RX.exec(s); m; m = TAG_RX.exec(s)) {
|
|
174
|
+
appendParsedText(cur, s.slice(i, m.index));
|
|
175
|
+
i = TAG_RX.lastIndex;
|
|
176
|
+
const result = appendHtmlTag(s, m, cur, i);
|
|
177
|
+
cur = result.current;
|
|
178
|
+
if (result.index !== null) i = result.index;
|
|
179
|
+
}
|
|
180
|
+
appendParsedText(cur, s.slice(i));
|
|
181
|
+
return root;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export const elementsOf = (node) => (node?.children ?? []).filter((c) => c.tag);
|
|
185
|
+
/** Every element under `node` in document order. */
|
|
186
|
+
export function walkElements(node, out = []) {
|
|
187
|
+
for (const c of elementsOf(node)) { out.push(c); walkElements(c, out); }
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
/** The visible text under an element (raw-text elements excluded). */
|
|
191
|
+
export function textOf(node) {
|
|
192
|
+
if (!node) return '';
|
|
193
|
+
if (node.text != null) return node.text;
|
|
194
|
+
if (RAW.has(node.tag) || node.tag === 'svg') return '';
|
|
195
|
+
return node.children.map(textOf).join(' ').replace(/\s+/g, ' ').trim();
|
|
196
|
+
}
|
|
197
|
+
export const classesOf = (el) => String(el?.attrs?.class ?? '').split(/\s+/).filter(Boolean);
|
|
198
|
+
const describe = (el) => {
|
|
199
|
+
const cls = classesOf(el).slice(0, 3).join('.');
|
|
200
|
+
const text = textOf(el).slice(0, 40);
|
|
201
|
+
return '<' + el.tag + (el.attrs.id ? '#' + el.attrs.id : '') + (cls ? '.' + cls : '') + '>' + (text ? ' "' + text + '"' : '');
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
205
|
+
// DNA
|
|
206
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
const dnaCache = new Map();
|
|
209
|
+
/** The DNA file of a Grammar family: knowledge/grammars/<family>/DNA.yaml, else the starci family's. */
|
|
210
|
+
function dnaFileOf(family = DEFAULT_FAMILY, root = GRAMMARS) {
|
|
211
|
+
for (const f of [family, DEFAULT_FAMILY]) {
|
|
212
|
+
const file = path.join(root, String(f ?? DEFAULT_FAMILY), 'DNA.yaml');
|
|
213
|
+
if (fs.existsSync(file)) return file;
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const kebab = (s) => String(s).replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/([A-Z])([A-Z][a-z])/g, '$1-$2').toLowerCase();
|
|
219
|
+
|
|
220
|
+
function partsOfRenderer(slug, classes) {
|
|
221
|
+
const parts = new Set();
|
|
222
|
+
for (const cls of classes) {
|
|
223
|
+
const bare = String(cls).replace(/^starci-core-/, '');
|
|
224
|
+
const plain = bare.replace(/^generic-/, '');
|
|
225
|
+
for (const p of [bare, plain, plain.startsWith(`${slug}-`) ? plain.slice(slug.length + 1) : null]) if (p) parts.add(p);
|
|
226
|
+
}
|
|
227
|
+
// The root spelled as a part (<slug>, <slug>-root, root) is the component itself.
|
|
228
|
+
if (parts.size) for (const p of [slug, `${slug}-root`, 'root']) parts.add(p);
|
|
229
|
+
return parts;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function closedValuesOfRenderer(closedValues) {
|
|
233
|
+
const closed = new Map();
|
|
234
|
+
for (const c of list(closedValues)) {
|
|
235
|
+
if (!c?.prop) continue;
|
|
236
|
+
closed.set(String(c.prop), { values: Array.isArray(c.values) ? c.values.map(String) : null, type: c.type ?? null });
|
|
237
|
+
}
|
|
238
|
+
return closed;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function componentFromRenderer(r) {
|
|
242
|
+
const slug = kebab(r.component);
|
|
243
|
+
const classes = list(r.classes);
|
|
244
|
+
return { name: r.component, kind: r.kind ?? null, slug, parts: partsOfRenderer(slug, classes), closed: closedValuesOfRenderer(r.closedValues), classes };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* The DNA as the gate reads it: Map(component -> {name, kind, parts:Set, closed:Map(prop -> {values|null, type})}).
|
|
249
|
+
* Parts are the component's anatomy classes minus `starci-core-` (alert-actions), also minus a leading `generic-`
|
|
250
|
+
* and minus the component's own slug (actions), so either spelling names the part.
|
|
251
|
+
*/
|
|
252
|
+
export function loadDna({ family = DEFAULT_FAMILY, file = null } = {}) {
|
|
253
|
+
const at = file ?? dnaFileOf(family);
|
|
254
|
+
if (!at) return null;
|
|
255
|
+
if (dnaCache.has(at)) return dnaCache.get(at);
|
|
256
|
+
let doc = null;
|
|
257
|
+
try { doc = parseYaml(fs.readFileSync(at, 'utf8')); } catch { doc = null; }
|
|
258
|
+
if (!doc || !Array.isArray(doc.renderers)) { dnaCache.set(at, null); return null; }
|
|
259
|
+
const components = new Map();
|
|
260
|
+
for (const r of doc.renderers) {
|
|
261
|
+
if (!r?.component) continue;
|
|
262
|
+
components.set(r.component, componentFromRenderer(r));
|
|
263
|
+
}
|
|
264
|
+
const dna = { file: at, family: doc.family ?? family, components };
|
|
265
|
+
dnaCache.set(at, dna);
|
|
266
|
+
return dna;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
270
|
+
// What is visible and what maps it
|
|
271
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
272
|
+
|
|
273
|
+
const NOT_RENDERED = new Set(['#root', 'html', 'head', 'body', 'script', 'style', 'template', 'noscript', 'meta', 'link', 'title', 'base', 'xmp']);
|
|
274
|
+
/** Phrasing that inherits the mapping of its parent when it carries no class, style or id of its own. */
|
|
275
|
+
const PHRASING = new Set(['strong', 'em', 'b', 'i', 'u', 's', 'small', 'sub', 'sup', 'abbr', 'mark', 'time', 'code', 'bdi', 'bdo', 'q', 'cite', 'dfn', 'var', 'data', 'kbd', 'br', 'wbr', 'span']);
|
|
276
|
+
const hiddenEl = (el) => el.attrs.hidden != null || /display\s*:\s*none/i.test(el.attrs.style ?? '') || el.attrs.type === 'hidden';
|
|
277
|
+
const insideSvg = (el) => ancestorsOf(el).some((a) => a.tag === 'svg');
|
|
278
|
+
const inHead = (el) => ancestorsOf(el).some((a) => a.tag === 'head' || NOT_RENDERED.has(a.tag) && a.tag !== 'html' && a.tag !== 'body');
|
|
279
|
+
const mappedSelf = (el) => el.attrs[COMPONENT_ATTR] != null || el.attrs[PART_ATTR] != null || el.attrs[PROPOSAL_ATTR] != null;
|
|
280
|
+
const plainPhrasing = (el) => PHRASING.has(el.tag) && (el.tag === 'br' || el.tag === 'wbr' || (!el.attrs.class && !el.attrs.style && !el.attrs.id));
|
|
281
|
+
|
|
282
|
+
/** Whether an element renders and so must say what it is. */
|
|
283
|
+
export function visibleElement(el) {
|
|
284
|
+
if (NOT_RENDERED.has(el.tag) || inHead(el)) return false;
|
|
285
|
+
if (hiddenEl(el) || ancestorsOf(el).some(hiddenEl)) return false;
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const componentNameOf = (el) => (el?.attrs?.[COMPONENT_ATTR] ?? '').trim();
|
|
290
|
+
/** The component name when `el` is that component's root (no nearest enclosing element names the same component). */
|
|
291
|
+
export const componentRootOf = (el) => {
|
|
292
|
+
const name = componentNameOf(el);
|
|
293
|
+
if (!name) return '';
|
|
294
|
+
const up = ancestorsOf(el).find((a) => a.attrs?.[COMPONENT_ATTR]);
|
|
295
|
+
return up && componentNameOf(up) === name && el.attrs[PART_ATTR] ? '' : name;
|
|
296
|
+
};
|
|
297
|
+
const proposalOf = (el) => [el, ...ancestorsOf(el)].map((a) => a.attrs?.[PROPOSAL_ATTR]).find((p) => p?.trim()) ?? null;
|
|
298
|
+
const inComponent = (el, names) => [el, ...ancestorsOf(el)].some((a) => names.includes(componentNameOf(a)));
|
|
299
|
+
|
|
300
|
+
/** The tone an element declares: data-tone / data-grammar-tone / tone / data-state / data-status, else a tone class. */
|
|
301
|
+
export function toneOf(el) {
|
|
302
|
+
for (const k of ['data-tone', 'data-grammar-tone', 'tone', 'data-state', 'data-color', 'color']) {
|
|
303
|
+
const t = presentationStateOf(el.attrs?.[k]);
|
|
304
|
+
if (t) return { tone: t, raw: el.attrs[k], via: k };
|
|
305
|
+
}
|
|
306
|
+
for (const cls of classesOf(el)) {
|
|
307
|
+
const m = /(?:^|--|-|_|\b)(neutral|informative|affirmative|cautionary|negative|pending|unavailable|default|info|accent|primary|success|warning|danger|error)$/.exec(cls);
|
|
308
|
+
if (m && (cls === m[1] || /(--|-|_)/.test(cls))) { const t = presentationStateOf(m[1]); if (t) return { tone: t, raw: m[1], via: `class ${cls}` }; }
|
|
309
|
+
}
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* The proposal names the given grammar-proposal files declare COMPLETE (scripts/work/grammar-proposal.mjs: name, gap,
|
|
315
|
+
* anatomy, tokens, claims, isolated render): Set of names. An incomplete entry is no entry.
|
|
316
|
+
*/
|
|
317
|
+
export function proposalNamesIn(files) {
|
|
318
|
+
const names = new Set();
|
|
319
|
+
for (const p of readProposals(files)) if (p.complete) for (const n of p.names) names.add(n);
|
|
320
|
+
return names;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const RATIO_RX = /(?<![\d./])(\d{1,4})\s*\/\s*(\d{1,4})(?![\d./])/;
|
|
324
|
+
const BAR_CLASS = /(?:^|[-_])(bar|bars|track|fill|segment|segments|progress|meter|gauge)(?:$|[-_])/i;
|
|
325
|
+
const NOTICE_CLASS = /(?:^|[-_])(notice|callout|banner|attention|alert|toast|announcement)(?:$|[-_])/i;
|
|
326
|
+
const CONTAINERS = new Set(['SurfaceCard', 'SurfaceListCard', 'SurfaceAccordionCard', 'SurfaceCopyGroup', 'PageContainer', 'EmptyNotice', 'Fieldset']);
|
|
327
|
+
const ACTION_COMPONENTS = new Set(['Button', 'IconButton', 'TextAction', 'Link', 'ButtonGroup']);
|
|
328
|
+
const isAction = (el) => ACTION_COMPONENTS.has(componentNameOf(el)) || el.tag === 'button' || (el.tag === 'a' && el.attrs.href != null);
|
|
329
|
+
|
|
330
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
331
|
+
// Declared style: inline style, utility classes and the render's own <style> rules
|
|
332
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
333
|
+
|
|
334
|
+
const declsOf = (text) => {
|
|
335
|
+
const out = {};
|
|
336
|
+
for (const part of String(text ?? '').split(';')) {
|
|
337
|
+
const i = part.indexOf(':');
|
|
338
|
+
if (i > 0) out[part.slice(0, i).trim().toLowerCase()] = part.slice(i + 1).replace(/!important/i, '').trim();
|
|
339
|
+
}
|
|
340
|
+
return out;
|
|
341
|
+
};
|
|
342
|
+
// A rule starts after a brace (or at the text start), so a run of selector text is scanned once.
|
|
343
|
+
const STYLE_RULE_RX = /(?<![^{}])([^{}]+)\{([^{}]*)\}/g;
|
|
344
|
+
const SELECTOR_COMBINATOR_RX = /\s+(?:[>+~]\s*)?|[>+~]\s*/;
|
|
345
|
+
|
|
346
|
+
/** The flat rules of the render's <style> blocks: [{selector, decls}] (innermost blocks of an @media included). */
|
|
347
|
+
function styleRulesOf(tree) {
|
|
348
|
+
const out = [];
|
|
349
|
+
for (const el of walkElements(tree).filter((e) => e.tag === 'style')) {
|
|
350
|
+
const css = String(el.raw ?? '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
351
|
+
for (const m of css.matchAll(STYLE_RULE_RX)) {
|
|
352
|
+
const decls = declsOf(m[2]);
|
|
353
|
+
for (const selector of m[1].split(',').map((s) => s.trim()).filter((s) => s && !s.startsWith('@'))) out.push({ selector, decls });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return out;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Whether a selector's last compound (no pseudo-class) matches `el` by tag, id, classes and attributes. */
|
|
360
|
+
function selectorMatches(selector, el) {
|
|
361
|
+
const last = selector.split(SELECTOR_COMBINATOR_RX).findLast(Boolean) ?? '';
|
|
362
|
+
if (!last || /:/.test(withoutAttributeBlocks(last))) return false;
|
|
363
|
+
const tag = /^[a-zA-Z][\w-]*/.exec(last)?.[0];
|
|
364
|
+
if (tag && tag.toLowerCase() !== el.tag) return false;
|
|
365
|
+
const classes = [...last.matchAll(/\.([\w-]+)/g)].map((m) => m[1]);
|
|
366
|
+
if (!tag && !classes.length && !/[#[]/.test(last)) return false;
|
|
367
|
+
if (classes.some((c) => !classesOf(el).includes(c))) return false;
|
|
368
|
+
const id = /#([\w-]+)/.exec(last)?.[1];
|
|
369
|
+
if (id && el.attrs.id !== id) return false;
|
|
370
|
+
for (const { name, value } of attributeSelectorsOf(last)) {
|
|
371
|
+
const v = el.attrs[name.toLowerCase()];
|
|
372
|
+
if (v == null || (value != null && v !== value)) return false;
|
|
373
|
+
}
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Every background an element declares: [{value, via}] from its style, a bg-* utility class, and matching <style> rules. */
|
|
378
|
+
function declaredBackgroundsOf(el, rules = []) {
|
|
379
|
+
const out = [];
|
|
380
|
+
const inline = declsOf(el.attrs.style);
|
|
381
|
+
for (const k of ['background', 'background-color']) if (inline[k]) out.push({ value: inline[k], via: `style ${k}` });
|
|
382
|
+
for (const c of classesOf(el)) if (c.startsWith('bg-')) out.push({ value: c.slice(3), via: `class ${c}` });
|
|
383
|
+
for (const r of rules) {
|
|
384
|
+
if (!selectorMatches(r.selector, el)) continue;
|
|
385
|
+
for (const k of ['background', 'background-color']) if (r.decls[k]) out.push({ value: r.decls[k], via: `${r.selector} {${k}}` });
|
|
386
|
+
}
|
|
387
|
+
return out;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Whether a declared background keeps the HeroUI surface: --surface, white, transparent or no colour. */
|
|
391
|
+
export function surfaceBackground(value) {
|
|
392
|
+
const v = String(value ?? '').trim().toLowerCase();
|
|
393
|
+
if (!v) return true;
|
|
394
|
+
if (/var\(\s*--(surface|color-surface|heroui-surface)\b/.test(v) || /^(surface|white|transparent|none|inherit|initial|unset)\b/.test(v)) return true;
|
|
395
|
+
if (/^#(fff|ffff|ffffff|ffffffff)\b/.test(v) || /^rgba?\(\s*255\s*,\s*255\s*,\s*255\b/.test(v) || /^rgba?\([^)]*,\s*0\s*\)$/.test(v)) return true;
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** The pixel size an element declares for `prop` (inline style, then matching <style> rules), px or rem; else null. */
|
|
400
|
+
function declaredPx(el, prop, rules = []) {
|
|
401
|
+
const read = (v) => {
|
|
402
|
+
const m = /^(-?\d+(?:\.\d+)?)(px|rem)$/.exec(String(v ?? '').trim());
|
|
403
|
+
if (!m) { return null; }
|
|
404
|
+
return Number(m[1]) * (m[2] === 'rem' ? 16 : 1);
|
|
405
|
+
};
|
|
406
|
+
const inline = read(declsOf(el.attrs.style)[prop]);
|
|
407
|
+
if (inline != null) return inline;
|
|
408
|
+
for (const r of rules) if (selectorMatches(r.selector, el) && read(r.decls[prop]) != null) return read(r.decls[prop]);
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const ARTWORK_COMPONENTS = new Set(['Image', 'MediaFrame', 'RankArtwork']);
|
|
413
|
+
const RASTER_SRC = /\.(png|jpe?g|webp|gif|avif)(?:[?#]|$)|^data:image\/(png|jpe?g|webp|gif|avif)/i;
|
|
414
|
+
/** Raster artwork: an Image/MediaFrame/RankArtwork root, or an img of a raster file outside Avatar/Icon/IconTile. */
|
|
415
|
+
function isArtwork(el) {
|
|
416
|
+
const name = componentRootOf(el);
|
|
417
|
+
if (ARTWORK_COMPONENTS.has(name) && !ancestorsOf(el).some((a) => ARTWORK_COMPONENTS.has(componentNameOf(a)))) return true;
|
|
418
|
+
if (el.tag !== 'img' || inComponent(el, ['Avatar', 'AvatarGroup', 'Icon', 'IconTile', ...ARTWORK_COMPONENTS])) return false;
|
|
419
|
+
return RASTER_SRC.test(String(el.attrs.src ?? '').trim());
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* The DNA findings of one render source: [{code, detail, count, examples}]. `proposals` is the Set of proposal names
|
|
424
|
+
* the drawing's grammar-proposal file(s) declare; `dna` the loaded DNA (loadDna); `assetRequests` the Set of slot ids
|
|
425
|
+
* its asset-request.md requests (null: not judged).
|
|
426
|
+
*/
|
|
427
|
+
const DNA_FINDING_HELPERS = {
|
|
428
|
+
ACTION_COMPONENTS, ALERT_INDICATOR_MAX_PX, ASSET_SLOT_ATTR, BADGE_DOT_CLASS, BADGE_DOT_PX, BAR_CLASS, CONTAINERS,
|
|
429
|
+
DOT_CLASS, DRAW_ALERT_ANATOMY, DRAW_ASSET_SLOT_UNDECLARED, DRAW_METER_TRACK, DRAW_NOTICE_NOT_ALERT, DRAW_OFF_GRAMMAR_COMPONENT, DRAW_RATIO_NOT_METER,
|
|
430
|
+
METER_FULL_WIDTH_SHARE, METER_SEGMENTED_TRACK_PX, METER_SEGMENTS_PROPOSAL, METER_TRACK_PX, METER_SEGMENT_GAP_MAX_PX, NOTICE_CLASS, OUTCOME_STATES,
|
|
431
|
+
PART_ATTR, PROPOSAL_ATTR, RATIO_RX, alertActionVariantFor, ancestorsOf, classesOf, componentNameOf, componentRootOf,
|
|
432
|
+
declaredBackgroundsOf, declaredPx, declsOf, describe, dnaSegment, inComponent, insideSvg, isAction, isArtwork, kebab,
|
|
433
|
+
mappedSelf, plainPhrasing, presentationStateOf, proposalOf, selectorMatches, segmentedMeter, styleRulesOf,
|
|
434
|
+
surfaceBackground, textOf, toneOf, walkElements
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* The DNA findings of one render source: [{code, detail, count, examples}]. `proposals` is the Set of proposal names
|
|
439
|
+
* the drawing's grammar-proposal file(s) declare; `dna` the loaded DNA (loadDna); `assetRequests` the Set of slot ids
|
|
440
|
+
* its asset-request.md requests (null: not judged).
|
|
441
|
+
*/
|
|
442
|
+
export function dnaFindings(html, { dna = loadDna(), proposals = new Set(), label = 'the render', assetRequests = null } = {}) {
|
|
443
|
+
const out = [];
|
|
444
|
+
if (!dna) return [{ code: DRAW_OFF_GRAMMAR_COMPONENT, detail: `${label}: no Grammar DNA (knowledge/grammars/<family>/DNA.yaml) to judge it against`, count: 1, examples: [] }];
|
|
445
|
+
const tree = parseHtml(html);
|
|
446
|
+
const all = walkElements(tree).filter(visibleElement);
|
|
447
|
+
out.push(...appendDnaFindings({ all, dna, proposals, label, assetRequests, tree, helpers: DNA_FINDING_HELPERS }));
|
|
448
|
+
return out;
|
|
449
|
+
}
|
|
450
|
+
/** The grammar-proposal files that belong to a render source: grammar-proposal.{md,yaml,yml} beside it and in `dirs`. */
|
|
451
|
+
export function proposalFilesFor(htmlFile, dirs = []) {
|
|
452
|
+
const out = [];
|
|
453
|
+
for (const dir of [path.dirname(htmlFile), ...dirs]) {
|
|
454
|
+
for (const name of PROPOSAL_FILE_NAMES) {
|
|
455
|
+
const f = path.join(dir, name);
|
|
456
|
+
if (fs.existsSync(f) && !out.includes(f)) out.push(f);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return out;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** Judge one render source file. */
|
|
463
|
+
function dnaFindingsOfFile(file, { family = DEFAULT_FAMILY, proposalFiles = null, dirs = [], assetRequests = null } = {}) {
|
|
464
|
+
const html = fs.readFileSync(file, 'utf8');
|
|
465
|
+
const files = proposalFiles ?? proposalFilesFor(file, dirs);
|
|
466
|
+
return dnaFindings(html, { dna: loadDna({ family }), proposals: proposalNamesIn(files), label: path.basename(file), assetRequests });
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
async function main(argv) {
|
|
470
|
+
const file = argv.find((a) => !a.startsWith('--') && /\.html?$/i.test(a));
|
|
471
|
+
if (!file) { process.stderr.write('use: starci work draw-dna <render.html> [--family starci] [--proposals <file>] [--json]\n'); return 2; }
|
|
472
|
+
const at = (n) => { const i = argv.indexOf(n); return i >= 0 ? argv[i + 1] : null; };
|
|
473
|
+
const { assetRequestIdsFor } = await import('../asset-slot.mjs');
|
|
474
|
+
const findings = dnaFindingsOfFile(path.resolve(file), { family: at('--family') ?? DEFAULT_FAMILY, proposalFiles: at('--proposals') ? [path.resolve(at('--proposals'))] : null,
|
|
475
|
+
assetRequests: assetRequestIdsFor(path.resolve(file)) });
|
|
476
|
+
if (argv.includes('--json')) process.stdout.write(`${JSON.stringify({ ok: !findings.length, findings }, null, 2)}\n`);
|
|
477
|
+
else process.stdout.write(`${findings.length ? 'REFUSED' : 'ok'}: ${findings.length} finding group(s)\n${findings.map((f) => ' [' + f.code + '] ' + f.detail).join('\n')}\n`);
|
|
478
|
+
return findings.length ? 1 : 0;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (isMain(import.meta.url)) {
|
|
482
|
+
process.exitCode = await main(process.argv.slice(2));
|
|
483
|
+
}
|