@starci/hfs 4.2.17 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +7 -0
- package/db/migrate.mjs +361 -0
- package/db/post-cutover.mjs +136 -0
- package/emit/db-types.mjs +10 -1
- package/lint/run.mjs +1 -1
- package/package.json +4 -1
- package/release/gate.mjs +298 -0
- package/release/index.mjs +103 -0
- package/release/push.mjs +110 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +11 -11
- package/runtime/knowledge/hfs/fe-stack.yaml +22 -1
- package/runtime/knowledge/hfs/rules.yaml +3437 -160
- package/runtime/knowledge/hfs/slots.yaml +430 -92
- package/runtime/knowledge/patterns/be/api.yaml +92 -2
- package/runtime/knowledge/patterns/be/cli.yaml +37 -9
- package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
- package/runtime/knowledge/patterns/be/jobs.yaml +20 -2
- package/runtime/knowledge/patterns/be/projections.yaml +4 -2
- package/runtime/knowledge/patterns/be/queues.yaml +11 -3
- package/runtime/knowledge/patterns/be/reactors.yaml +20 -1
- package/runtime/knowledge/patterns/be/realtime.yaml +25 -3
- package/runtime/knowledge/patterns/be/saga.yaml +20 -3
- package/runtime/knowledge/patterns/be/webhooks.yaml +210 -36
- package/runtime/knowledge/patterns/fe/folder.yaml +4 -0
- package/runtime/knowledge/sonar-gate.yaml +1 -1
- package/runtime/modules/kernel/failure-codes.yaml +3128 -47
- package/runtime/modules/models/runtimes.yaml +1 -1
- package/runtime/modules/schemas/application-stacks.schema.yaml +663 -0
- package/runtime/packages/cli/src/catalog.generated.mjs +14104 -0
- package/runtime/packages/cli/src/validate-args.mjs +213 -0
- package/runtime/scripts/api/git/add.mjs +5 -0
- package/runtime/scripts/api/git/cat-file.mjs +5 -0
- package/runtime/scripts/api/git/init.mjs +7 -0
- package/runtime/scripts/api/git/lib.mjs +3 -0
- package/runtime/scripts/api/git/tag.mjs +5 -0
- package/runtime/scripts/api/node/lib.mjs +20 -0
- package/runtime/scripts/api/node/run-node.mjs +6 -0
- package/runtime/scripts/api/sops/lib.mjs +41 -0
- package/runtime/scripts/api/sops/resolve-sops.mjs +7 -0
- package/runtime/scripts/api/sops/seal.mjs +1 -1
- package/runtime/scripts/api/sops/selected-age-recipient.mjs +7 -0
- package/runtime/scripts/api/supabase/database-dump.mjs +5 -0
- package/runtime/scripts/api/supabase/database-query.mjs +5 -0
- package/runtime/scripts/api/supabase/lib.mjs +44 -0
- package/runtime/scripts/api/supabase/migration-repair.mjs +5 -0
- package/runtime/scripts/hfs/app-wiring.mjs +201 -0
- package/runtime/scripts/hfs/architecture/backend-app-files.mjs +103 -0
- package/runtime/scripts/hfs/architecture/backend.mjs +22 -10
- package/runtime/scripts/hfs/architecture/background-unowned.mjs +157 -41
- package/runtime/scripts/hfs/architecture/canon-clones.mjs +240 -0
- package/runtime/scripts/hfs/architecture/canon-jsx-clones.mjs +143 -0
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +6 -3
- package/runtime/scripts/hfs/architecture/clones.mjs +17 -8
- package/runtime/scripts/hfs/architecture/connection-map.mjs +14 -12
- package/runtime/scripts/hfs/architecture/context-map.mjs +25 -14
- package/runtime/scripts/hfs/architecture/context-owner.mjs +1 -2
- package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +149 -0
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +23 -50
- package/runtime/scripts/hfs/architecture/default-deny.mjs +8 -6
- package/runtime/scripts/hfs/architecture/frontend-block-render.mjs +104 -0
- package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +30 -11
- package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
- package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +153 -0
- package/runtime/scripts/hfs/architecture/hfs.mjs +3 -3
- package/runtime/scripts/hfs/architecture/i18n-keys.mjs +16 -3
- package/runtime/scripts/hfs/architecture/machine-ast.mjs +17 -0
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +49 -25
- package/runtime/scripts/hfs/architecture/next-data-inference.mjs +176 -0
- package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +26 -0
- package/runtime/scripts/hfs/architecture/next-data.mjs +31 -23
- package/runtime/scripts/hfs/architecture/reachability.mjs +14 -0
- package/runtime/scripts/hfs/architecture/register-once.mjs +15 -7
- package/runtime/scripts/hfs/architecture/required-files.mjs +2 -1
- package/runtime/scripts/hfs/architecture/source-names.mjs +48 -3
- package/runtime/scripts/hfs/architecture/staged-consumers.mjs +37 -0
- package/runtime/scripts/hfs/architecture/supabase-ast.mjs +214 -17
- package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -6
- package/runtime/scripts/hfs/architecture/supabase.mjs +59 -21
- package/runtime/scripts/hfs/architecture/surface.mjs +2 -1
- package/runtime/scripts/hfs/architecture/tiers.mjs +11 -4
- package/runtime/scripts/hfs/architecture/typescript.mjs +1 -8
- package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +9 -13
- package/runtime/scripts/hfs/check-results.mjs +28 -0
- package/runtime/scripts/hfs/check.mjs +63 -72
- package/runtime/scripts/hfs/coverage-from.mjs +70 -0
- package/runtime/scripts/hfs/db-baseline-verb.mjs +276 -0
- package/runtime/scripts/hfs/db-plan-verb.mjs +254 -0
- package/runtime/scripts/hfs/db-restamp-verb.mjs +60 -0
- package/runtime/scripts/hfs/declaration-shape.mjs +39 -3
- package/runtime/scripts/hfs/doc-commands.mjs +367 -0
- package/runtime/scripts/hfs/edition-slots.mjs +2 -2
- package/runtime/scripts/hfs/example-secrets.mjs +158 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +91 -6
- package/runtime/scripts/hfs/path-findings.mjs +32 -2
- package/runtime/scripts/hfs/refusal-codes.mjs +6 -0
- package/runtime/scripts/hfs/rule-catalog.mjs +156 -11
- package/runtime/scripts/hfs/rule-params-shape.mjs +22 -10
- package/runtime/scripts/hfs/rules/a15-findings.mjs +88 -0
- package/runtime/scripts/hfs/rules/app-env-required.mjs +20 -0
- package/runtime/scripts/hfs/rules/app-root.mjs +2 -2
- package/runtime/scripts/hfs/rules/assets.mjs +79 -0
- package/runtime/scripts/hfs/rules/boot-proof.mjs +16 -0
- package/runtime/scripts/hfs/rules/canon-copy-drift.mjs +59 -0
- package/runtime/scripts/hfs/rules/contract.mjs +66 -5
- package/runtime/scripts/hfs/rules/coverage-below.mjs +230 -0
- package/runtime/scripts/hfs/rules/database-access.mjs +343 -0
- package/runtime/scripts/hfs/rules/database-baseline.mjs +142 -0
- package/runtime/scripts/hfs/rules/database-config.mjs +250 -2
- package/runtime/scripts/hfs/rules/database-constants.mjs +9 -0
- package/runtime/scripts/hfs/rules/database-migrations.mjs +317 -37
- package/runtime/scripts/hfs/rules/database-plpgsql.mjs +119 -9
- package/runtime/scripts/hfs/rules/database-queue-sources.mjs +176 -0
- package/runtime/scripts/hfs/rules/database-queues.mjs +361 -0
- package/runtime/scripts/hfs/rules/database-sql.mjs +91 -9
- package/runtime/scripts/hfs/rules/database.mjs +11 -2
- package/runtime/scripts/hfs/rules/deploy-scan.mjs +192 -0
- package/runtime/scripts/hfs/rules/deploy-workflow.mjs +219 -0
- package/runtime/scripts/hfs/rules/doc-fresh.mjs +286 -0
- package/runtime/scripts/hfs/rules/docker-health.mjs +249 -0
- package/runtime/scripts/hfs/rules/docker.mjs +24 -7
- package/runtime/scripts/hfs/rules/edition.mjs +11 -10
- package/runtime/scripts/hfs/rules/env-parity.mjs +373 -0
- package/runtime/scripts/hfs/rules/env-value-shape.mjs +113 -0
- package/runtime/scripts/hfs/rules/event-bus.mjs +3 -2
- package/runtime/scripts/hfs/rules/event-tables.mjs +100 -0
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +115 -15
- package/runtime/scripts/hfs/rules/fe-stack.mjs +57 -0
- package/runtime/scripts/hfs/rules/feature-files.mjs +28 -0
- package/runtime/scripts/hfs/rules/frontend-tree.mjs +54 -35
- package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -2
- package/runtime/scripts/hfs/rules/lexicon.mjs +62 -0
- package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +89 -0
- package/runtime/scripts/hfs/rules/monorepo.mjs +14 -1
- package/runtime/scripts/hfs/rules/pipeline.mjs +8 -1
- package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -0
- package/runtime/scripts/hfs/rules/release-provenance.mjs +211 -0
- package/runtime/scripts/hfs/rules/saga-delivery.mjs +66 -0
- package/runtime/scripts/hfs/rules/saga.mjs +8 -16
- package/runtime/scripts/hfs/rules/script-hygiene.mjs +151 -0
- package/runtime/scripts/hfs/rules/secret-custody-only.mjs +65 -0
- package/runtime/scripts/hfs/rules/secrets.mjs +26 -0
- package/runtime/scripts/hfs/rules/services.mjs +4 -4
- package/runtime/scripts/hfs/rules/stack-tooling.mjs +93 -0
- package/runtime/scripts/hfs/rules/stacks-deploy.mjs +76 -0
- package/runtime/scripts/hfs/rules/stacks-ingress.mjs +87 -0
- package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +111 -0
- package/runtime/scripts/hfs/rules/stacks-release.mjs +366 -0
- package/runtime/scripts/hfs/rules/stacks-runtime.mjs +168 -0
- package/runtime/scripts/hfs/rules/stacks-swarm.mjs +417 -0
- package/runtime/scripts/hfs/rules/stacks.mjs +13 -8
- package/runtime/scripts/hfs/rules/supabase-secrets.mjs +1 -1
- package/runtime/scripts/hfs/rules/supply-chain.mjs +167 -0
- package/runtime/scripts/hfs/rules/unused-symbol-flow.mjs +101 -0
- package/runtime/scripts/hfs/rules/unused-symbol-ledger.mjs +77 -0
- package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +165 -0
- package/runtime/scripts/hfs/rules/unused-symbol-values.mjs +103 -0
- package/runtime/scripts/hfs/rules/unused-symbol.mjs +320 -0
- package/runtime/scripts/hfs/runtime-rules/source-ast.mjs +185 -0
- package/runtime/scripts/hfs/runtime-rules/workflow-source.mjs +19 -0
- package/runtime/scripts/hfs/sealed-members.mjs +101 -0
- package/runtime/scripts/hfs/secret-bootstrap-crypto.mjs +200 -0
- package/runtime/scripts/hfs/secret-document.mjs +97 -0
- package/runtime/scripts/hfs/secret-edit.mjs +170 -0
- package/runtime/scripts/hfs/secret-errors.mjs +20 -0
- package/runtime/scripts/hfs/secret-files.mjs +124 -0
- package/runtime/scripts/hfs/secret-init.mjs +300 -0
- package/runtime/scripts/hfs/secret-output.mjs +100 -0
- package/runtime/scripts/hfs/secret-sops.mjs +130 -0
- package/runtime/scripts/hfs/secret-store.mjs +163 -0
- package/runtime/scripts/hfs/secret.mjs +162 -193
- package/runtime/scripts/hfs/slot-imports.mjs +18 -3
- package/runtime/scripts/hfs/slot-required.mjs +13 -0
- package/runtime/scripts/hfs/slot-semantic-problems.mjs +2 -1
- package/runtime/scripts/hfs/slots.mjs +1 -0
- package/runtime/scripts/hfs/stack-config-kit.mjs +34 -0
- package/runtime/scripts/hfs/stack-deploy-config.mjs +313 -0
- package/runtime/scripts/hfs/stack-ingress-config.mjs +237 -0
- package/runtime/scripts/hfs/stacks-gate.mjs +414 -0
- package/runtime/scripts/hfs/starcistacks-runtime.mjs +61 -0
- package/runtime/scripts/hfs/tree-findings.mjs +22 -0
- package/runtime/scripts/lib/command-tokens.mjs +126 -0
- package/runtime/scripts/lib/compose-values.mjs +11 -0
- package/runtime/scripts/lib/deployment-findings.mjs +7 -0
- package/runtime/scripts/lib/json-clone.mjs +61 -0
- package/runtime/scripts/lib/list.mjs +4 -0
- package/runtime/scripts/lib/roots.mjs +59 -0
- package/runtime/scripts/lib/runtime-example.mjs +13 -0
- package/runtime/scripts/lib/secret-patterns.mjs +60 -5
- package/runtime/scripts/lib/sql-ast.mjs +32 -0
- package/runtime/scripts/lib/stack-services.mjs +1 -1
- package/runtime/scripts/lib/swarm-stack.mjs +126 -0
- package/runtime/scripts/lib/trim-end.mjs +8 -0
- package/runtime/scripts/lib/ts-analysis.mjs +23 -0
- package/runtime/scripts/lib/ts-ast.mjs +27 -2
- package/runtime/scripts/lib/workflow-events.mjs +8 -0
- package/scaffold/add-app.mjs +77 -59
- package/scaffold/add-cli-lite.mjs +24 -17
- package/scaffold/add-compose.mjs +79 -0
- package/scaffold/add-database-lite.mjs +14 -0
- package/scaffold/add-fe-overlay.mjs +122 -0
- package/scaffold/add-fe-page.mjs +269 -0
- package/scaffold/add-inbox-lite.mjs +87 -124
- package/scaffold/add-plan.mjs +183 -0
- package/scaffold/add-table.mjs +132 -70
- package/scaffold/add.mjs +186 -204
- package/scaffold/app.mjs +128 -43
- package/scaffold/edition-gate.mjs +3 -3
- package/scaffold/image.mjs +2 -2
- package/scaffold/lite-exports.mjs +2 -1
- package/scaffold/service.mjs +2 -2
- package/src/main.mjs +238 -119
- package/stack/age-install.mjs +37 -0
- package/stack/backup.mjs +100 -0
- package/stack/build-args.mjs +42 -0
- package/stack/build.mjs +173 -0
- package/stack/cli.mjs +130 -0
- package/stack/config.mjs +57 -0
- package/stack/context.mjs +57 -0
- package/stack/converge.mjs +107 -0
- package/stack/deploy.mjs +301 -0
- package/stack/digest.mjs +16 -0
- package/stack/docker-transport.mjs +53 -0
- package/stack/edge-alias.mjs +118 -0
- package/stack/errors.mjs +21 -0
- package/stack/image-scan.mjs +152 -0
- package/stack/ingress-create.mjs +114 -0
- package/stack/ingress-publish.mjs +273 -0
- package/stack/ingress.mjs +706 -0
- package/stack/jobs.mjs +51 -0
- package/stack/live-restore.mjs +177 -0
- package/stack/prepare.mjs +266 -0
- package/stack/private-file.mjs +22 -0
- package/stack/provision.mjs +90 -0
- package/stack/prune.mjs +46 -0
- package/stack/release-store.mjs +87 -0
- package/stack/remote.mjs +205 -0
- package/stack/render.mjs +149 -0
- package/stack/retire.mjs +47 -0
- package/stack/sealed.mjs +149 -0
- package/stack/task.mjs +42 -0
- package/stack/verify.mjs +99 -0
- package/sync/ci-commands.mjs +13 -0
- package/sync/index.mjs +127 -64
- package/sync/stack-images.mjs +127 -0
- package/templates/app/ci-workflows/github/workflows/ci.yml +5 -1
- package/templates/app/ci-workflows/github/workflows/images.yml +15 -3
- package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +148 -0
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +7 -3
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +38 -6
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +14 -2
- package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +148 -0
- package/templates/app/codegen-graphql.mjs.partial +13 -2
- package/templates/app/codegen-lite/scripts/codegen.mjs +6 -1
- package/templates/app/codegen-prelude.mjs.partial +8 -0
- package/templates/app/gitignore +4 -0
- package/templates/app/gitignore-lite +4 -0
- package/templates/app/hooks/husky/pre-commit +7 -12
- package/templates/app/package-scripts/package.json +1 -0
- package/templates/app/package-scripts/supabase.json +7 -0
- package/templates/app/package-scripts-lite/package.json +1 -7
- package/templates/app/skeleton/.editorconfig +1 -0
- package/templates/app/skeleton/.gitattributes +7 -1
- package/templates/app/skeleton/.github/dependabot.yml +17 -0
- package/templates/app/skeleton/.nvmrc +1 -1
- package/templates/app/skeleton/.sops.yaml +1 -1
- package/templates/app/skeleton/.starcistacks/application-stacks.yaml +134 -1
- package/templates/app/skeleton/.starcistacks/dev/README.md +16 -0
- package/templates/app/skeleton/.starcistacks/dev/environment.json +5 -0
- package/templates/app/skeleton/.starcistacks/dev/infra/compose/stack.yaml +71 -0
- package/templates/app/skeleton/.starcistacks/dev/runtime/env/KEYS.md +14 -0
- package/templates/app/skeleton/.starcistacks/production/README.md +37 -0
- package/templates/app/skeleton/.starcistacks/production/environment.json +5 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/Caddyfile +15 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +9 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +142 -0
- package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +14 -0
- package/templates/app/skeleton/README.md +6 -6
- package/templates/app/skeleton/scripts/codegen.mjs +1 -2
- package/templates/app/skeleton-lite/.editorconfig +16 -0
- package/templates/app/skeleton-lite/.gitattributes +8 -0
- package/templates/app/skeleton-lite/.github/dependabot.yml +17 -0
- package/templates/app/skeleton-lite/.nvmrc +1 -0
- package/templates/app/skeleton-lite/.sops.yaml +3 -2
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +193 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +20 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +2 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +16 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +11 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +7 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +8 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +120 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -0
- package/templates/app/skeleton-lite/.starcistacks/production/README.md +38 -0
- package/templates/app/skeleton-lite/.starcistacks/production/environment.json +6 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/Caddyfile +11 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/images.lock.yaml +8 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +121 -0
- package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +21 -0
- package/templates/app/skeleton-lite/.starciwork/brand/index.yaml +12 -0
- package/templates/app/skeleton-lite/.starciwork/workspace.yaml +9 -0
- package/templates/app/skeleton-lite/README.md +8 -4
- package/templates/app/skeleton-lite/supabase/config.toml +8 -1
- package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +15 -6
- package/templates/app/starciwork.gitignore-lite +6 -0
- package/templates/app/upgrade-full/.starcistacks/dev/README.md +13 -7
- package/templates/be/image/worker/Dockerfile +1 -1
- package/templates/be/inbox/database-client.spec.ts.tpl +22 -28
- package/templates/be/inbox/database-client.ts.tpl +14 -28
- package/templates/be/inbox/delivery-recorded.event.ts.tpl +36 -0
- package/templates/be/inbox/event-index.ts.tpl +1 -0
- package/templates/be/inbox/events.json.tpl +10 -0
- package/templates/be/inbox/migration.sql.tpl +1 -0
- package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +42 -0
- package/templates/be/inbox/platform/event-bus/event-bus.decorators.ts.tpl +9 -0
- package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +6 -0
- package/templates/be/inbox/platform/event-bus/event-bus.module.ts.tpl +19 -0
- package/templates/be/inbox/platform/event-bus/event-bus.options.ts.tpl +2 -0
- package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +16 -0
- package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +45 -0
- package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +15 -0
- package/templates/be/inbox/platform/event-bus/index.ts.tpl +5 -0
- package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +5 -0
- package/templates/be/inbox/platform/inbox/inbox.decorators.ts.tpl +9 -0
- package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +6 -0
- package/templates/be/inbox/platform/inbox/inbox.module.ts.tpl +19 -0
- package/templates/be/inbox/platform/inbox/inbox.options.ts.tpl +2 -0
- package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +10 -0
- package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +41 -0
- package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +19 -0
- package/templates/be/inbox/platform/inbox/index.ts.tpl +3 -0
- package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +7 -0
- package/templates/be/inbox/platform/migration.sql.tpl +48 -0
- package/templates/be/inbox/service.spec.ts.tpl +49 -20
- package/templates/be/inbox/service.ts.tpl +27 -16
- package/templates/be/inbox/sql.ts.tpl +2 -2
- package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -3
- package/templates/be/inbox/webhook-door.ts.tpl +4 -3
- package/templates/be/patterns/api/feature.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/api/feature.module.ts.tpl +2 -1
- package/templates/be/patterns/api/feature.options.ts.tpl +2 -0
- package/templates/be/patterns/api/graphql.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/api/graphql.module.ts.tpl +3 -2
- package/templates/be/patterns/api/graphql.options.ts.tpl +2 -0
- package/templates/be/patterns/cli/group.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/cli/group.module.ts.tpl +2 -1
- package/templates/be/patterns/cli/group.options.ts.tpl +2 -0
- package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +0 -3
- package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +0 -2
- package/templates/be/patterns/jobs/job.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/jobs/job.module.ts.tpl +2 -1
- package/templates/be/patterns/jobs/job.options.ts.tpl +2 -0
- package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/jobs/job.queue.module.ts.tpl +6 -3
- package/templates/be/patterns/jobs/job.queue.options.ts.tpl +2 -0
- package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +3 -1
- package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +25 -9
- package/templates/be/patterns/queues/platform/queue.config.ts.tpl +1 -1
- package/templates/be/patterns/queues/platform/queue.index.ts.tpl +1 -1
- package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +2 -2
- package/templates/be/patterns/queues/platform/queue.module.ts.tpl +11 -0
- package/templates/be/patterns/queues/platform/queue.options.ts.tpl +10 -0
- package/templates/be/patterns/queues/queue.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/queues/queue.module.ts.tpl +2 -1
- package/templates/be/patterns/queues/queue.options.ts.tpl +2 -0
- package/templates/be/patterns/reactors/message.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/reactors/message.module.ts.tpl +6 -3
- package/templates/be/patterns/reactors/message.options.ts.tpl +2 -0
- package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/reactors/reactor.module.ts.tpl +2 -1
- package/templates/be/patterns/reactors/reactor.options.ts.tpl +2 -0
- package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +188 -0
- package/templates/be/patterns/realtime/channel.gateway.ts.tpl +22 -0
- package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +2 -1
- package/templates/be/patterns/realtime/channel.graphql.options.ts.tpl +2 -0
- package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +110 -0
- package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +7 -0
- package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/saga/saga.message.module.ts.tpl +6 -3
- package/templates/be/patterns/saga/saga.message.options.ts.tpl +2 -0
- package/templates/be/patterns/saga/saga.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/saga/saga.module.ts.tpl +2 -1
- package/templates/be/patterns/saga/saga.options.ts.tpl +2 -0
- package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +3 -3
- package/templates/be/patterns/webhooks/webhook.door.ts.tpl +3 -3
- package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +2 -1
- package/templates/be/patterns/webhooks/webhook.http.options.ts.tpl +2 -0
- package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +80 -26
- package/templates/be/platform/graphql/localize-errors.mapper.ts +2 -5
- package/templates/be/skeleton/apps/__app__/src/__app__.env.ts +21 -0
- package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +1 -1
- package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -45
- package/templates/be/skeleton/apps/__app__/src/compose/api.ts +4 -0
- package/templates/be/skeleton/apps/__app__/src/compose/domain.ts +11 -0
- package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +44 -0
- package/templates/be/skeleton/apps/__app__/src/compose/system.ts +5 -0
- package/templates/be/skeleton/apps/__app__/src/compose/webhooks.ts +4 -0
- package/templates/be/skeleton/apps/__app__/src/main.ts +18 -10
- package/templates/be/skeleton/apps/cli/src/app.module.ts +3 -13
- package/templates/be/skeleton/apps/cli/src/cli.env.ts +13 -0
- package/templates/be/skeleton/apps/cli/src/cli.options.ts +7 -17
- package/templates/be/skeleton/apps/cli/src/compose/cli.ts +5 -0
- package/templates/be/skeleton/apps/cli/src/compose/platform.ts +24 -0
- package/templates/be/skeleton/apps/cli/src/main.ts +3 -2
- package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.contracts.ts +2 -0
- package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.handler.ts +22 -0
- package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.query.ts +11 -0
- package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/api/system-health/system-health.module.ts +5 -3
- package/templates/be/skeleton/src/features/api/system-health/system-health.options.ts +2 -0
- package/templates/be/skeleton/src/features/api/system-health/transport/http/ready.controller.ts +19 -0
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module.ts +8 -3
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.options.ts +2 -0
- package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/cli/cli.module.ts +3 -2
- package/templates/be/skeleton/src/features/cli/cli.options.ts +2 -0
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +2 -1
- package/templates/be/skeleton/src/features/cli/migrate/migrate.options.ts +2 -0
- package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +2 -1
- package/templates/be/skeleton/src/features/cli/seed/seed.options.ts +2 -0
- package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +5 -6
- package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +56 -4
- package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +36 -5
- package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +39 -8
- package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +14 -8
- package/templates/be/skeleton/src/modules/domain/identity/credential.contracts.ts +26 -0
- package/templates/be/skeleton/src/modules/domain/identity/credential.guard.spec.ts +119 -0
- package/templates/be/skeleton/src/modules/domain/identity/credential.guard.ts +43 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +16 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -1
- package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +22 -4
- package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +8 -2
- package/templates/be/skeleton/src/modules/domain/identity/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +6 -6
- package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +11 -4
- package/templates/be/skeleton/src/modules/platform/database/database-probe.service.spec.ts +32 -0
- package/templates/be/skeleton/src/modules/platform/database/database-probe.service.ts +14 -0
- package/templates/be/skeleton/src/modules/platform/database/database.module.ts +7 -1
- package/templates/be/skeleton/src/modules/platform/database/index.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +8 -0
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +25 -6
- package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +16 -6
- package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.spec.ts +130 -0
- package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.ts +58 -0
- package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +3 -0
- package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -4
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -10
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +3 -1
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/http-security/index.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +12 -5
- package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +13 -8
- package/templates/be/skeleton/src/modules/platform/http-security/readiness.contracts.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/http-security/readiness.decorators.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.spec.ts +86 -0
- package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.ts +39 -0
- package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +139 -131
- package/templates/be/skeleton/src/modules/platform/i18n/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +91 -67
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +5 -4
- package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +0 -2
- package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +20 -6
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +16 -4
- package/templates/be/skeleton/src/modules/platform/retry/index.ts +3 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.module.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.port.ts +11 -0
- package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.spec.ts +36 -0
- package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.ts +10 -0
- package/templates/be/skeleton/src/tests/contract/http-security/http-security.contract-spec.ts +54 -0
- package/templates/be/skeleton-lite/apps/api/src/api.env.ts +25 -0
- package/templates/be/skeleton-lite/apps/api/src/api.options.ts +1 -1
- package/templates/be/skeleton-lite/apps/api/src/app.module.ts +18 -58
- package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +4 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +10 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +16 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +47 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/system.ts +5 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/webhooks.ts +4 -0
- package/templates/be/skeleton-lite/apps/api/src/main.ts +5 -13
- package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +17 -0
- package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.spec.ts +71 -5
- package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +29 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +68 -15
- package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +14 -9
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +9 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +16 -1
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +19 -4
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +5 -1
- package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -1
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -3
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +17 -5
- package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +24 -13
- package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +5 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/errors/database.error.ts +16 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +3 -0
- package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +5 -2
- package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +4 -1
- package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +7 -0
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +29 -0
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +16 -0
- package/templates/be/table/api/feature.module-definition.ts.tpl +6 -0
- package/templates/be/table/api/feature.module.ts.tpl +2 -1
- package/templates/be/table/api/feature.options.ts.tpl +2 -0
- package/templates/be/table/api/graphql.module-definition.ts.tpl +6 -0
- package/templates/be/table/api/graphql.module.ts.tpl +3 -2
- package/templates/be/table/api/graphql.options.ts.tpl +2 -0
- package/templates/be/table/migration.sql.tpl +1 -0
- package/templates/be/upgrade-full/apps/cli/src/app.module.ts +4 -6
- package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +7 -0
- package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +5 -0
- package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +9 -0
- package/templates/be/upgrade-full/apps/cli/src/main.ts +3 -0
- package/templates/be/upgrade-full/src/features/cli/cli.module.ts +3 -2
- package/templates/fe/auth/supabase-client.ts.tpl +34 -0
- package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +11 -0
- package/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/index.tsx +6 -1
- package/templates/fe/skeleton/apps/__app__/src/components/leaves/Icon/index.tsx +4 -0
- package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/component.tsx +31 -0
- package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/index.tsx +24 -0
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +6 -1
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +3 -4
- package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/index.ts +1 -0
- package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +15 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +18 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +19 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +7 -1
- package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/en.json +57 -0
- package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +6 -0
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/en.json +31 -0
- package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +1 -1
- package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +1 -0
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +1 -34
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +7 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +57 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +30 -24
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +1 -1
- package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/locale.ts +2 -2
- package/upgrade/check.mjs +3 -1
- package/upgrade/file-state.mjs +68 -0
- package/upgrade/index.mjs +58 -94
- package/runtime/scripts/api/git/log.mjs +0 -5
- package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +0 -55
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { loadSlotManifest, readRepoDeclaration } from "../runtime/scripts/hfs/slots.mjs";
|
|
4
|
+
import { ScaffoldError, pascalOf } from "./service.mjs";
|
|
5
|
+
|
|
6
|
+
const NAME = /^[a-z][a-z0-9-]*$/;
|
|
7
|
+
const RESERVED = /^(?:con|prn|aux|nul|com[0-9]|lpt[0-9])$/i;
|
|
8
|
+
const jsonText = (value) => `${JSON.stringify(value, null, 2)}\n`;
|
|
9
|
+
const objectCatalog = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
10
|
+
|
|
11
|
+
/** Validate a CLI name before it is used in a path, symbol or catalog key. */
|
|
12
|
+
export function feName(value, label = "name") {
|
|
13
|
+
if (typeof value !== "string" || !NAME.test(value) || value.split("-").includes("") || RESERVED.test(value)) {
|
|
14
|
+
throw new ScaffoldError("HFS_ADD_NAME_INVALID", `${label} must be kebab-case, without a path or a reserved filename`);
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** An app-relative target whose existing ancestors are real directories, never symlinks. */
|
|
20
|
+
function feTarget(root, relative) {
|
|
21
|
+
const target = path.resolve(root, relative);
|
|
22
|
+
const inside = path.relative(root, target);
|
|
23
|
+
if (!inside || inside.startsWith("..") || path.isAbsolute(inside)) {
|
|
24
|
+
throw new ScaffoldError("HFS_ADD_PATH_UNSAFE", `${relative} must stay inside the app root`);
|
|
25
|
+
}
|
|
26
|
+
let at = root;
|
|
27
|
+
const pieces = inside.split(path.sep);
|
|
28
|
+
for (let index = 0; index < pieces.length; index += 1) {
|
|
29
|
+
at = path.join(at, pieces[index]);
|
|
30
|
+
const stat = fs.lstatSync(at, { throwIfNoEntry: false });
|
|
31
|
+
if (!stat) continue;
|
|
32
|
+
if (stat.isSymbolicLink() || (index < pieces.length - 1 && !stat.isDirectory())) {
|
|
33
|
+
throw new ScaffoldError("HFS_ADD_PATH_UNSAFE", `${relative} has a symlink or a non-directory ancestor`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return target;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Resolve the declared Next app; a multi-app repository must select one explicitly. */
|
|
40
|
+
export function readFeContext({ root, app }) {
|
|
41
|
+
const repoRoot = path.resolve(root);
|
|
42
|
+
const stat = fs.lstatSync(repoRoot, { throwIfNoEntry: false });
|
|
43
|
+
if (!stat?.isDirectory() || stat.isSymbolicLink()) {
|
|
44
|
+
throw new ScaffoldError("HFS_ADD_NOT_AN_APP", "add page and overlay run at a real app root directory");
|
|
45
|
+
}
|
|
46
|
+
const declaration = feTarget(repoRoot, "hfs.json");
|
|
47
|
+
const packageFile = feTarget(repoRoot, "package.json");
|
|
48
|
+
if (!fs.existsSync(declaration) || !fs.existsSync(packageFile)) {
|
|
49
|
+
throw new ScaffoldError("HFS_ADD_NOT_AN_APP", "add page and overlay need hfs.json and package.json at the app root");
|
|
50
|
+
}
|
|
51
|
+
const repo = readRepoDeclaration(loadSlotManifest(), repoRoot);
|
|
52
|
+
const apps = repo.sides?.fe?.apps.filter((candidate) => candidate.kind === "next") ?? [];
|
|
53
|
+
if (!apps.length) throw new ScaffoldError("HFS_ADD_NO_FRONT_END", "this app has no declared Next front end");
|
|
54
|
+
if (app !== undefined) feName(app, "app");
|
|
55
|
+
if (app === undefined && apps.length !== 1) {
|
|
56
|
+
throw new ScaffoldError("HFS_ADD_OPTION_MISSING", "add page and overlay need --app when several Next apps are declared");
|
|
57
|
+
}
|
|
58
|
+
const selected = apps.find((candidate) => candidate.name === (app ?? apps[0].name));
|
|
59
|
+
if (!selected) throw new ScaffoldError("HFS_ADD_APP_UNKNOWN", `front-end app ${app} is not declared`);
|
|
60
|
+
feName(selected.name, "declared app");
|
|
61
|
+
return { root: repoRoot, app: selected.name, repo, source: `fe/apps/${selected.name}/src` };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Read a required owner file only after checking every path ancestor. */
|
|
65
|
+
export function readFeFile(context, relative) {
|
|
66
|
+
const target = feTarget(context.root, relative);
|
|
67
|
+
if (!fs.existsSync(target) || !fs.statSync(target).isFile()) {
|
|
68
|
+
throw new ScaffoldError("HFS_ADD_WIRE_MISSING", `${relative} is required before this feature can be added`);
|
|
69
|
+
}
|
|
70
|
+
return fs.readFileSync(target, "utf8");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Refuse an existing owner, including a partially generated one. */
|
|
74
|
+
export function requireNewFeOwner(context, relative) {
|
|
75
|
+
if (fs.existsSync(feTarget(context.root, relative))) {
|
|
76
|
+
throw new ScaffoldError("HFS_ADD_EXISTS", `${relative} already exists; add never replaces an existing owner`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function readCatalogs(context) {
|
|
81
|
+
const directory = `${context.source}/modules/i18n/messages`;
|
|
82
|
+
const target = feTarget(context.root, directory);
|
|
83
|
+
if (!fs.existsSync(target)) throw new ScaffoldError("HFS_ADD_WIRE_MISSING", `${directory} is required`);
|
|
84
|
+
if (!fs.statSync(target).isDirectory()) throw new ScaffoldError("HFS_ADD_I18N_INVALID", `${directory} must be a directory`);
|
|
85
|
+
const files = fs.readdirSync(target).filter((file) => file.endsWith(".json")).sort();
|
|
86
|
+
if (!["vi.json", "en.json"].every((file) => files.includes(file))) {
|
|
87
|
+
throw new ScaffoldError("HFS_ADD_I18N_MISSING", `${directory} needs both vi.json and en.json`);
|
|
88
|
+
}
|
|
89
|
+
return files.map((file) => {
|
|
90
|
+
const relative = `${directory}/${file}`;
|
|
91
|
+
const before = readFeFile(context, relative);
|
|
92
|
+
return { relative, before, locale: file.slice(0, -5), catalog: parseCatalog(before, relative) };
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function parseCatalog(before, relative) {
|
|
97
|
+
let catalog;
|
|
98
|
+
try { catalog = JSON.parse(before); } catch {
|
|
99
|
+
throw new ScaffoldError("HFS_ADD_I18N_INVALID", `${relative} must contain valid JSON`);
|
|
100
|
+
}
|
|
101
|
+
if (!objectCatalog(catalog)) throw new ScaffoldError("HFS_ADD_I18N_INVALID", `${relative} must contain a catalog object`);
|
|
102
|
+
return catalog;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function catalogNamespace(catalogs, app) {
|
|
106
|
+
const first = catalogs[0].catalog;
|
|
107
|
+
const keys = Object.keys(first);
|
|
108
|
+
let namespace = keys.length === 1 ? keys[0] : app;
|
|
109
|
+
if (!Object.hasOwn(first, namespace) && Object.hasOwn(first, "app")) namespace = "app";
|
|
110
|
+
if (!catalogs.every(({ catalog }) => Object.hasOwn(catalog, namespace) && objectCatalog(catalog[namespace]))) {
|
|
111
|
+
throw new ScaffoldError("HFS_ADD_I18N_INVALID", "the app catalogs need a shared object namespace");
|
|
112
|
+
}
|
|
113
|
+
return namespace;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function catalogCopy(locale, defaults, messages) {
|
|
117
|
+
const copy = messages?.[locale] ?? defaults[locale];
|
|
118
|
+
const required = Object.keys(defaults.en);
|
|
119
|
+
if (!copy || !required.every((key) => typeof copy[key] === "string" && copy[key].trim())) {
|
|
120
|
+
throw new ScaffoldError("HFS_ADD_I18N_INVALID", `provide the ${required.join(", ")} messages for locale ${locale}`);
|
|
121
|
+
}
|
|
122
|
+
return Object.fromEntries(required.map((key) => [key, copy[key]]));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Plan matching localized keys in every catalog without changing existing copy. */
|
|
126
|
+
export function planFeCatalogs(context, { group, name, defaults, messages }) {
|
|
127
|
+
const catalogs = readCatalogs(context);
|
|
128
|
+
const namespace = catalogNamespace(catalogs, context.app);
|
|
129
|
+
const updates = catalogs.map(({ catalog, locale, relative, before }) => {
|
|
130
|
+
const owner = catalog[namespace];
|
|
131
|
+
if (owner[group] !== undefined && !objectCatalog(owner[group])) {
|
|
132
|
+
throw new ScaffoldError("HFS_ADD_I18N_INVALID", `${relative}: ${namespace}.${group} must be an object`);
|
|
133
|
+
}
|
|
134
|
+
owner[group] ??= {};
|
|
135
|
+
if (Object.hasOwn(owner[group], name)) {
|
|
136
|
+
throw new ScaffoldError("HFS_ADD_EXISTS", `${relative} already declares ${namespace}.${group}.${name}`);
|
|
137
|
+
}
|
|
138
|
+
owner[group][name] = catalogCopy(locale, defaults, messages);
|
|
139
|
+
return { relative, before, body: jsonText(catalog) };
|
|
140
|
+
});
|
|
141
|
+
return { namespace: `${namespace}.${group}.${name}`, updates };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function createParent(root, target, made) {
|
|
145
|
+
const missing = [];
|
|
146
|
+
for (let at = path.dirname(target); at !== root && !fs.existsSync(at); at = path.dirname(at)) missing.push(at);
|
|
147
|
+
for (const directory of missing.reverse()) {
|
|
148
|
+
fs.mkdirSync(directory);
|
|
149
|
+
made.push(directory);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function writeCreated(target, body, written) {
|
|
154
|
+
const descriptor = fs.openSync(target, "wx");
|
|
155
|
+
written.push(target);
|
|
156
|
+
try { fs.writeFileSync(descriptor, body); } finally { fs.closeSync(descriptor); }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Execute a fully checked plan and restore updated owners if a write fails. */
|
|
160
|
+
export function writeFePlan(context, created, updates) {
|
|
161
|
+
for (const file of created) requireNewFeOwner(context, file.relative);
|
|
162
|
+
for (const file of updates) {
|
|
163
|
+
if (readFeFile(context, file.relative) !== file.before) {
|
|
164
|
+
throw new ScaffoldError("HFS_ADD_WIRE_INVALID", `${file.relative} changed while planning the feature`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const written = [];
|
|
168
|
+
const updated = [];
|
|
169
|
+
const directories = [];
|
|
170
|
+
try {
|
|
171
|
+
for (const file of created) {
|
|
172
|
+
const target = feTarget(context.root, file.relative);
|
|
173
|
+
createParent(context.root, target, directories);
|
|
174
|
+
writeCreated(target, file.body, written);
|
|
175
|
+
}
|
|
176
|
+
for (const file of updates) {
|
|
177
|
+
updated.push(file);
|
|
178
|
+
fs.writeFileSync(feTarget(context.root, file.relative), file.body);
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
for (const file of updated.reverse()) fs.writeFileSync(feTarget(context.root, file.relative), file.before);
|
|
182
|
+
for (const file of written.reverse()) fs.rmSync(file);
|
|
183
|
+
for (const directory of directories.reverse()) fs.rmdirSync(directory);
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
return { created: created.map((file) => file.relative), updated: updates.map((file) => file.relative) };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function pageSources({ name, namespace, symbol, routeSymbol }) {
|
|
190
|
+
const metadata = `${name.split("-").map((word, index) => index ? pascalOf(word) : word).join("")}Metadata`;
|
|
191
|
+
return {
|
|
192
|
+
metadata,
|
|
193
|
+
entry: `import type { Metadata } from "next"
|
|
194
|
+
import { getTranslations } from "next-intl/server"
|
|
195
|
+
import { ${routeSymbol} } from "@/modules/routes"
|
|
196
|
+
import { ${symbol}Base } from "./component"
|
|
197
|
+
|
|
198
|
+
/** The localized document title and canonical destination of this page. */
|
|
199
|
+
export const ${metadata} = async (): Promise<Metadata> => {
|
|
200
|
+
const t = await getTranslations(${JSON.stringify(namespace)})
|
|
201
|
+
return { title: t("title"), alternates: { canonical: ${routeSymbol} } }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Resolve the requested locale's copy before drawing this server page. */
|
|
205
|
+
export const ${symbol} = async () => {
|
|
206
|
+
const t = await getTranslations(${JSON.stringify(namespace)})
|
|
207
|
+
return <${symbol}Base props={{ title: t("title") }} />
|
|
208
|
+
}
|
|
209
|
+
`,
|
|
210
|
+
component: `import { Heading } from "@starci/grammar/common"
|
|
211
|
+
|
|
212
|
+
/** The localized words displayed by this page. */
|
|
213
|
+
export interface ${symbol}BaseProps {
|
|
214
|
+
readonly props: { readonly title: string }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Draw the page using the settled copy supplied by its server owner. */
|
|
218
|
+
export const ${symbol}Base = (props: ${symbol}BaseProps) => (
|
|
219
|
+
<Heading level={1}>{props.props.title}</Heading>
|
|
220
|
+
)
|
|
221
|
+
`,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function pageRoute(symbol, metadata) {
|
|
226
|
+
return `import { ${symbol} } from "@/features/pages/${symbol}"
|
|
227
|
+
|
|
228
|
+
/** The locale-aware metadata of this page. */
|
|
229
|
+
export { ${metadata} as generateMetadata } from "@/features/pages/${symbol}"
|
|
230
|
+
|
|
231
|
+
/** Mount the page's public server owner. */
|
|
232
|
+
const Page = () => <${symbol} />
|
|
233
|
+
|
|
234
|
+
export default Page
|
|
235
|
+
`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Add a server page, its thin localized route and matching vi/en catalog entries. */
|
|
239
|
+
export function addFePage({ root, app, name, route = name, messages }) {
|
|
240
|
+
feName(name, "page name");
|
|
241
|
+
if (typeof route !== "string" || !route) throw new ScaffoldError("HFS_ADD_OPTION_INVALID", "--route needs one or more kebab-case path segments");
|
|
242
|
+
for (const segment of route.split("/")) feName(segment, "route segment");
|
|
243
|
+
const context = readFeContext({ root, app });
|
|
244
|
+
const symbol = `${pascalOf(name)}Page`;
|
|
245
|
+
const owner = `${context.source}/features/pages/${symbol}`;
|
|
246
|
+
requireNewFeOwner(context, owner);
|
|
247
|
+
const routeFile = `${context.source}/app/[locale]/${route}/page.tsx`;
|
|
248
|
+
requireNewFeOwner(context, routeFile);
|
|
249
|
+
const catalogs = planFeCatalogs(context, {
|
|
250
|
+
group: "pages", name, messages,
|
|
251
|
+
defaults: { vi: { title: "Trang m\u1edbi" }, en: { title: "New page" } },
|
|
252
|
+
});
|
|
253
|
+
const routeSymbol = `${name.replaceAll("-", "_").toUpperCase()}_PAGE_ROUTE`;
|
|
254
|
+
const routesFile = `${context.source}/modules/routes/index.ts`;
|
|
255
|
+
const before = readFeFile(context, routesFile);
|
|
256
|
+
if (before.split(/[^A-Za-z0-9_]/).includes(routeSymbol)) {
|
|
257
|
+
throw new ScaffoldError("HFS_ADD_EXISTS", `${routesFile} already declares ${routeSymbol}`);
|
|
258
|
+
}
|
|
259
|
+
const sources = pageSources({ name, namespace: catalogs.namespace, symbol, routeSymbol });
|
|
260
|
+
const created = [
|
|
261
|
+
{ relative: `${owner}/index.tsx`, body: sources.entry },
|
|
262
|
+
{ relative: `${owner}/component.tsx`, body: sources.component },
|
|
263
|
+
{ relative: routeFile, body: pageRoute(symbol, sources.metadata) },
|
|
264
|
+
];
|
|
265
|
+
const destination = `/${route}`;
|
|
266
|
+
const routeBody = `${before.trimEnd()}\n\n/** The canonical destination of the ${symbol} owner. */\nexport const ${routeSymbol} = ${JSON.stringify(destination)} as const\n`;
|
|
267
|
+
const updates = [...catalogs.updates, { relative: routesFile, before, body: routeBody }];
|
|
268
|
+
return { ...writeFePlan(context, created, updates), registered: { app: context.app, page: symbol, route: destination } };
|
|
269
|
+
}
|
|
@@ -3,6 +3,9 @@ import path from "node:path";
|
|
|
3
3
|
import { render, TEMPLATES_DIR } from "../sync/index.mjs";
|
|
4
4
|
import { ScaffoldError, pascalOf } from "./service.mjs";
|
|
5
5
|
import { filesBelow } from "./lite-exports.mjs";
|
|
6
|
+
import { jsonText } from "./app.mjs";
|
|
7
|
+
import { byCodeUnit } from "../report/order.mjs";
|
|
8
|
+
import { withSqlIdentifierError } from "./add-database-lite.mjs";
|
|
6
9
|
|
|
7
10
|
const MIGRATION = /^(\d{14})_[a-z][a-z0-9]*(?:-[a-z0-9]+)*\.sql$/;
|
|
8
11
|
const PLACEHOLDER = /@@([A-Za-z][A-Za-z0-9]*)@@/g;
|
|
@@ -25,13 +28,13 @@ const template = (relative, values) => {
|
|
|
25
28
|
});
|
|
26
29
|
};
|
|
27
30
|
|
|
28
|
-
/** The lite templates of the webhook door and its unit spec: the door
|
|
31
|
+
/** The lite templates of the webhook door and its unit spec: the door records the delivery in the provider inbox. */
|
|
29
32
|
const LITE_WEBHOOK_TEMPLATES = Object.freeze({
|
|
30
33
|
"webhooks/webhook.door.ts.tpl": "../inbox/webhook-door.ts.tpl",
|
|
31
34
|
"webhooks/webhook.door.spec.ts.tpl": "../inbox/webhook-door.spec.ts.tpl",
|
|
32
35
|
});
|
|
33
36
|
|
|
34
|
-
/** Lite replaces only the webhook door and its spec
|
|
37
|
+
/** Lite replaces only the webhook door and its spec with the durable provider inbox intake. */
|
|
35
38
|
export const selectLiteWebhookEntries = (entries) =>
|
|
36
39
|
entries.map((entry) =>
|
|
37
40
|
LITE_WEBHOOK_TEMPLATES[entry.template] === undefined
|
|
@@ -39,86 +42,24 @@ export const selectLiteWebhookEntries = (entries) =>
|
|
|
39
42
|
: { ...entry, template: LITE_WEBHOOK_TEMPLATES[entry.template] },
|
|
40
43
|
);
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
for (let index = open; index < text.length; index += 1) {
|
|
45
|
-
if (text[index] === "{") depth += 1;
|
|
46
|
-
else if (text[index] === "}") {
|
|
47
|
-
depth -= 1;
|
|
48
|
-
if (depth === 0) return index;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return -1;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const addEntityManagerImport = (text) => {
|
|
55
|
-
if (/import type \{[^}]*\bEntityManager\b[^}]*\} from "typeorm"/.test(text))
|
|
56
|
-
return text;
|
|
57
|
-
return `import type { EntityManager } from "typeorm"\n${text}`;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
/** Gives the selected domain intake the transaction manager while preserving its existing processing body. */
|
|
61
|
-
function adaptedDomainService({ root, service, serviceModule, provider, Provider }) {
|
|
45
|
+
/** Validates the selected processing owner for a later reactor/job without invoking or changing that service in the door. */
|
|
46
|
+
function validateProcessingOwner({ root, service, serviceModule, provider }) {
|
|
62
47
|
const prefix = "@modules/domain/";
|
|
63
|
-
|
|
48
|
+
const capability = serviceModule.slice(prefix.length);
|
|
49
|
+
if (!serviceModule.startsWith(prefix) || !/^[a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)*$/.test(capability))
|
|
64
50
|
throw new ScaffoldError(
|
|
65
51
|
"HFS_ADD_INBOX_SERVICE",
|
|
66
|
-
`lite add webhook needs a
|
|
52
|
+
`lite add webhook needs a processing service module below ${prefix}`,
|
|
67
53
|
);
|
|
68
|
-
const owner = path.join(
|
|
69
|
-
|
|
70
|
-
"
|
|
71
|
-
"src",
|
|
72
|
-
"modules",
|
|
73
|
-
"domain",
|
|
74
|
-
...serviceModule.slice(prefix.length).split("/"),
|
|
75
|
-
);
|
|
76
|
-
const candidates = filesBelow(owner).filter(
|
|
77
|
-
(file) =>
|
|
78
|
-
file.endsWith(".service.ts") &&
|
|
79
|
-
new RegExp(`\\bexport\\s+class\\s+${service}\\b`).test(
|
|
80
|
-
fs.readFileSync(file, "utf8"),
|
|
81
|
-
),
|
|
54
|
+
const owner = path.join(root, "be", "src", "modules", "domain", ...capability.split("/"));
|
|
55
|
+
const candidates = filesBelow(owner).filter((file) =>
|
|
56
|
+
file.endsWith(".service.ts") && new RegExp(String.raw`\bexport\s+class\s+${service}\b`).test(fs.readFileSync(file, "utf8")),
|
|
82
57
|
);
|
|
83
58
|
if (candidates.length !== 1)
|
|
84
|
-
throw new ScaffoldError(
|
|
85
|
-
"HFS_ADD_INBOX_SERVICE",
|
|
86
|
-
`${serviceModule} must export exactly one ${service} class from a .service.ts file before adding the ${provider} webhook`,
|
|
87
|
-
);
|
|
88
|
-
const file = candidates[0];
|
|
89
|
-
let text = fs.readFileSync(file, "utf8").replace(/\r\n/g, "\n");
|
|
90
|
-
const method = `accept${Provider}Delivery`;
|
|
91
|
-
const methodAt = text.search(new RegExp(`\\b${method}\\s*\\(`));
|
|
92
|
-
if (methodAt < 0)
|
|
93
|
-
throw new ScaffoldError(
|
|
94
|
-
"HFS_ADD_INBOX_INTAKE",
|
|
95
|
-
`${service} must declare ${method}(delivery) before adding the ${provider} webhook`,
|
|
96
|
-
);
|
|
97
|
-
const paramsOpen = text.indexOf("(", methodAt);
|
|
98
|
-
const paramsClose = text.indexOf(")", paramsOpen);
|
|
99
|
-
const bodyOpen = text.indexOf("{", paramsClose);
|
|
100
|
-
const bodyClose = matchingBrace(text, bodyOpen);
|
|
101
|
-
if (paramsClose < 0 || bodyOpen < 0 || bodyClose < 0)
|
|
102
59
|
throw new ScaffoldError(
|
|
103
60
|
"HFS_ADD_INBOX_INTAKE",
|
|
104
|
-
`${
|
|
61
|
+
`${serviceModule} must export exactly one ${service} from a .service.ts file as the processing owner of the ${provider} inbox`,
|
|
105
62
|
);
|
|
106
|
-
const parameters = text.slice(paramsOpen + 1, paramsClose).trim();
|
|
107
|
-
if (/(?:^|,)\s*(?:manager|entityManager)\s*:/.test(parameters)) return { file, text };
|
|
108
|
-
const manager =
|
|
109
|
-
/private\s+readonly\s+([A-Za-z][A-Za-z0-9]*)\s*:\s*EntityManager/.exec(
|
|
110
|
-
text,
|
|
111
|
-
)?.[1];
|
|
112
|
-
if (!manager)
|
|
113
|
-
throw new ScaffoldError(
|
|
114
|
-
"HFS_ADD_INBOX_INTAKE",
|
|
115
|
-
`${service}.${method} needs an injected EntityManager so the inbox can keep its processing in one transaction`,
|
|
116
|
-
);
|
|
117
|
-
const body = text
|
|
118
|
-
.slice(bodyOpen + 1, bodyClose)
|
|
119
|
-
.replaceAll(`this.${manager}`, "entityManager");
|
|
120
|
-
text = `${text.slice(0, paramsOpen + 1)}${parameters}, entityManager: EntityManager = this.${manager}${text.slice(paramsClose, bodyOpen + 1)}${body}${text.slice(bodyClose)}`;
|
|
121
|
-
return { file, text: addEntityManagerImport(text) };
|
|
122
63
|
}
|
|
123
64
|
|
|
124
65
|
const databasePatches = (root) => {
|
|
@@ -131,9 +72,11 @@ const databasePatches = (root) => {
|
|
|
131
72
|
"lite add webhook needs the scaffolded platform/database capability",
|
|
132
73
|
);
|
|
133
74
|
let index = fs.readFileSync(indexFile, "utf8").replace(/\r\n/g, "\n");
|
|
134
|
-
if (!index.includes('export {
|
|
135
|
-
index = `${index.
|
|
136
|
-
|
|
75
|
+
if (!index.includes('export { recordWebhookDelivery } from "./webhook-inbox.client"'))
|
|
76
|
+
index = `${index.trimEnd()}\nexport { recordWebhookDelivery } from "./webhook-inbox.client"\n`;
|
|
77
|
+
if (!index.split("\n").some((line) => line.startsWith("export { ") && line.includes("DatabaseErrorCode") && line.endsWith(' } from "./errors/database.error"')))
|
|
78
|
+
index = `${index.trimEnd()}\nexport { DatabaseError, DatabaseErrorCode } from "./errors/database.error"\n`;
|
|
79
|
+
let error = withSqlIdentifierError(fs.readFileSync(errorFile, "utf8").replace(/\r\n/g, "\n"));
|
|
137
80
|
const enumLine =
|
|
138
81
|
' WebhookDeliveryIdRequired = "DATABASE_WEBHOOK_DELIVERY_ID_REQUIRED",';
|
|
139
82
|
if (!error.includes(enumLine)) {
|
|
@@ -159,47 +102,61 @@ const databasePatches = (root) => {
|
|
|
159
102
|
];
|
|
160
103
|
};
|
|
161
104
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
105
|
+
/** The first webhook installs only the lite inbox claim and transactional event publisher, shared by all providers. */
|
|
106
|
+
const handoffPlatformFiles = (root, values) => {
|
|
107
|
+
const planned = [];
|
|
108
|
+
for (const capability of ["inbox", "event-bus"]) {
|
|
109
|
+
const base = `be/src/modules/platform/${capability}`;
|
|
110
|
+
if (fs.existsSync(path.join(root, base, "index.ts"))) continue;
|
|
111
|
+
const folder = path.join(TEMPLATES_DIR, "be", "inbox", "platform", capability);
|
|
112
|
+
for (const file of filesBelow(folder)) {
|
|
113
|
+
const relative = path.relative(folder, file).replaceAll("\\", "/");
|
|
114
|
+
planned.push({
|
|
115
|
+
relative: `${base}/${relative.replace(/\.tpl$/, "")}`,
|
|
116
|
+
text: template(`platform/${capability}/${relative}`, values),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return planned;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/** A provider may already publish other events; retain its barrel and contract when adding the receipt event. */
|
|
124
|
+
const eventFiles = (root, provider, values) => {
|
|
125
|
+
const entries = [
|
|
126
|
+
{ relative: `be/src/modules/events/${provider}/index.ts`, text: template("event-index.ts.tpl", values) },
|
|
127
|
+
{ relative: `be/contracts/${provider}/events.json`, text: template("events.json.tpl", values) },
|
|
128
|
+
];
|
|
129
|
+
const planned = [];
|
|
130
|
+
const patches = [];
|
|
131
|
+
for (const entry of entries) {
|
|
132
|
+
const file = path.join(root, ...entry.relative.split("/"));
|
|
133
|
+
if (!fs.existsSync(file)) {
|
|
134
|
+
planned.push(entry);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const previous = fs.readFileSync(file, "utf8").replace(/\r\n/g, "\n");
|
|
138
|
+
if (entry.relative.endsWith("index.ts")) {
|
|
139
|
+
patches.push({ file, text: previous.includes(entry.text.trim()) ? previous : `${previous.trimEnd()}\n${entry.text}` });
|
|
140
|
+
} else {
|
|
141
|
+
const contract = JSON.parse(previous);
|
|
142
|
+
if (contract.schema !== "starci/event-contract@1" || contract.service !== provider || !contract.events || Array.isArray(contract.events) || typeof contract.events !== "object")
|
|
143
|
+
throw new ScaffoldError("HFS_ADD_EVENT_CONTRACT", `${entry.relative} is not the event contract of ${provider}`);
|
|
144
|
+
const additions = JSON.parse(entry.text).events;
|
|
145
|
+
contract.events = { ...contract.events, ...additions };
|
|
146
|
+
patches.push({ file, text: jsonText(contract) });
|
|
147
|
+
}
|
|
198
148
|
}
|
|
199
|
-
return {
|
|
149
|
+
return { planned, patches };
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const eventPatternPatch = (root) => {
|
|
153
|
+
const file = path.join(root, "hfs.json");
|
|
154
|
+
const declaration = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
155
|
+
declaration.sides.be.patterns = [...new Set([...(declaration.sides.be.patterns ?? []), "event-bus"])].sort(byCodeUnit);
|
|
156
|
+
return { file, text: jsonText(declaration) };
|
|
200
157
|
};
|
|
201
158
|
|
|
202
|
-
/** Writes the lite provider inbox
|
|
159
|
+
/** Writes the lite provider inbox. Processing belongs to a separately added reactor or job, after the record commits. */
|
|
203
160
|
export function addLiteWebhookInbox({
|
|
204
161
|
root,
|
|
205
162
|
repo,
|
|
@@ -209,11 +166,12 @@ export function addLiteWebhookInbox({
|
|
|
209
166
|
now = Date.now,
|
|
210
167
|
}) {
|
|
211
168
|
if (repo.edition !== "lite" || noun !== "webhook") return [];
|
|
169
|
+
validateProcessingOwner({ root, ...values, provider });
|
|
212
170
|
const Provider = pascalOf(provider);
|
|
213
171
|
const table = `${snakeOf(provider)}_inbox`;
|
|
214
172
|
const migrationsDir = path.join(root, "supabase", "migrations");
|
|
215
173
|
const migrations = fs.existsSync(migrationsDir)
|
|
216
|
-
? fs.readdirSync(migrationsDir).filter((file) => MIGRATION.test(file)).sort()
|
|
174
|
+
? fs.readdirSync(migrationsDir).filter((file) => MIGRATION.test(file)).sort(byCodeUnit)
|
|
217
175
|
: [];
|
|
218
176
|
const suffix = `${provider}-inbox.sql`;
|
|
219
177
|
if (migrations.some((file) => file.endsWith(`_${suffix}`)))
|
|
@@ -236,6 +194,7 @@ export function addLiteWebhookInbox({
|
|
|
236
194
|
table,
|
|
237
195
|
};
|
|
238
196
|
const base = `be/src/modules/domain/${provider}-inbox`;
|
|
197
|
+
const events = eventFiles(root, provider, renderValues);
|
|
239
198
|
const planned = [
|
|
240
199
|
{
|
|
241
200
|
relative: `supabase/migrations/${stamp}_${suffix}`,
|
|
@@ -266,7 +225,18 @@ export function addLiteWebhookInbox({
|
|
|
266
225
|
relative: `${base}/persistence/${provider}-inbox.sql.ts`,
|
|
267
226
|
text: template("sql.ts.tpl", renderValues),
|
|
268
227
|
},
|
|
228
|
+
{
|
|
229
|
+
relative: `be/src/modules/events/${provider}/${provider}-delivery-recorded.event.ts`,
|
|
230
|
+
text: template("delivery-recorded.event.ts.tpl", renderValues),
|
|
231
|
+
},
|
|
232
|
+
...events.planned,
|
|
233
|
+
...handoffPlatformFiles(root, renderValues),
|
|
269
234
|
];
|
|
235
|
+
const hasHandoffStorage = migrations.some((file) =>
|
|
236
|
+
/create\s+table\s+public\.event_bus\b/i.test(fs.readFileSync(path.join(migrationsDir, file), "utf8")),
|
|
237
|
+
);
|
|
238
|
+
if (!hasHandoffStorage)
|
|
239
|
+
planned[0].text = `${template("platform/migration.sql.tpl", renderValues)}\n${planned[0].text}`;
|
|
270
240
|
const databaseSql = "be/src/modules/platform/database/database.sql.ts";
|
|
271
241
|
if (!fs.existsSync(path.join(root, ...databaseSql.split("/")))) {
|
|
272
242
|
planned.push({
|
|
@@ -308,17 +278,10 @@ export function addLiteWebhookInbox({
|
|
|
308
278
|
"HFS_ADD_EXISTS",
|
|
309
279
|
`webhook ${provider} inbox already exists: ${clashes.join(", ")}`,
|
|
310
280
|
);
|
|
311
|
-
const servicePatch = adaptedDomainService({
|
|
312
|
-
root,
|
|
313
|
-
service: values.service,
|
|
314
|
-
serviceModule: values.serviceModule,
|
|
315
|
-
provider,
|
|
316
|
-
Provider,
|
|
317
|
-
});
|
|
318
281
|
const patches = [
|
|
319
|
-
servicePatch,
|
|
320
282
|
...databasePatches(root),
|
|
321
|
-
|
|
283
|
+
...events.patches,
|
|
284
|
+
eventPatternPatch(root),
|
|
322
285
|
];
|
|
323
286
|
for (const entry of planned) {
|
|
324
287
|
const target = path.join(root, ...entry.relative.split("/"));
|