@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
package/scaffold/add.mjs
CHANGED
|
@@ -16,101 +16,18 @@
|
|
|
16
16
|
// `@@vCamel@@` its camelCase, `@@vUpper@@` its UPPER_SNAKE and `@@vSnake@@` its snake_case; the option `--service Class=module` gives `@@service@@` (the class) and `@@serviceModule@@`.
|
|
17
17
|
import fs from 'node:fs';
|
|
18
18
|
import path from 'node:path';
|
|
19
|
-
import { createSlotResolver, loadSlotManifest, readRepoDeclaration, ruleParams } from '../runtime/scripts/hfs/slots.mjs';
|
|
20
|
-
import { parseYaml } from '../runtime/engine/yaml.mjs';
|
|
21
|
-
import { TEMPLATES_DIR } from '../sync/index.mjs';
|
|
22
19
|
import { ScaffoldError, pascalOf } from './service.mjs';
|
|
23
|
-
import { refuseFullAdapterInLite, refuseInEdition } from './edition-gate.mjs';
|
|
24
20
|
import { addTable, graphqlPlatformFiles } from './add-table.mjs';
|
|
21
|
+
import { addModuleImport, compositionCode, readComposeGroup, writeComposeGroup } from './add-compose.mjs';
|
|
25
22
|
import { addApp } from './add-app.mjs';
|
|
26
23
|
import { ensureLiteCli } from './add-cli-lite.mjs';
|
|
27
|
-
import { addLiteWebhookInbox
|
|
24
|
+
import { addLiteWebhookInbox } from './add-inbox-lite.mjs';
|
|
25
|
+
import { addBuiltinLiteCli, fillKnownForms, formsOf, planKind, readAddContext } from './add-plan.mjs';
|
|
26
|
+
export { liteApiTemplates } from './add-plan.mjs';
|
|
28
27
|
import { bundledPins, jsonText, packageJsonText, peerDependencies } from './app.mjs';
|
|
29
28
|
import { byCodeUnit } from '../report/order.mjs';
|
|
30
29
|
import { registerLiteDomainService, registerLiteExports } from './lite-exports.mjs';
|
|
31
30
|
|
|
32
|
-
const KEBAB = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
33
|
-
const VARIABLE = /<([A-Za-z][A-Za-z0-9]*)>/g;
|
|
34
|
-
const PLACEHOLDER = /@@([A-Za-z][A-Za-z0-9]*)@@/g;
|
|
35
|
-
const PLATFORM_PREFIX = 'src/modules/platform/';
|
|
36
|
-
const TOPICS_DIR = path.join(import.meta.dirname, '..', 'runtime', 'knowledge', 'patterns', 'be');
|
|
37
|
-
const PATTERN_TEMPLATES = path.join(TEMPLATES_DIR, 'be', 'patterns');
|
|
38
|
-
|
|
39
|
-
const camelOf = (name) => { const pascal = pascalOf(name); return pascal[0].toLowerCase() + pascal.slice(1); };
|
|
40
|
-
|
|
41
|
-
/** The `files:` tree of the topic `topic`, read from the knowledge file the package carries. */
|
|
42
|
-
function readTree(topic) {
|
|
43
|
-
const file = path.join(TOPICS_DIR, `${topic}.yaml`);
|
|
44
|
-
if (!fs.existsSync(file)) throw new ScaffoldError('HFS_ADD_TOPIC_MISSING', `the pattern topic ${topic} (knowledge/patterns/be/${topic}.yaml) is not carried by this package`);
|
|
45
|
-
const doc = parseYaml(fs.readFileSync(file, 'utf8'));
|
|
46
|
-
if (!Array.isArray(doc?.files)) throw new ScaffoldError('HFS_ADD_TREE_MISSING', `knowledge/patterns/be/${topic}.yaml has no files tree`);
|
|
47
|
-
return doc.files;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** The parsed document of the topic `topic` (knowledge/patterns/be/<topic>.yaml) the package carries. */
|
|
51
|
-
const readTopic = (topic) => ({ id: topic, doc: parseYaml(fs.readFileSync(path.join(TOPICS_DIR, `${topic}.yaml`), 'utf8')) });
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* The lite api tree: the full tree, GraphQL door included (owner ruling 2026-10-13: lite and full have one code structure), with
|
|
55
|
-
* the command and handler that hand the verified Supabase principal to the domain service (lite's identity adapter).
|
|
56
|
-
*/
|
|
57
|
-
const selectLiteApiEntries = entries => entries.map(entry => {
|
|
58
|
-
if (entry.path === 'src/features/api/<feature>/application/<action>.command.ts') return { ...entry, template: 'api/application.command.principal.ts.tpl' };
|
|
59
|
-
if (entry.path === 'src/features/api/<feature>/application/<action>.handler.ts') return { ...entry, template: 'api/application.handler.principal.ts.tpl' };
|
|
60
|
-
return entry;
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
/** The templates the lite api tree names beyond the pattern topic's own files tree (the principal-aware command and handler): the template set stays one set with the topics' (tests/packages-hfs/hfs-add.spec.mjs). */
|
|
64
|
-
export const liteApiTemplates = () => selectLiteApiEntries(readTree('api')).map(entry => entry.template).filter(Boolean);
|
|
65
|
-
|
|
66
|
-
/** Fills the `<name>` variables of a path; returns null when a variable has no value. */
|
|
67
|
-
const fillPath = (text, values) => {
|
|
68
|
-
let missing = false;
|
|
69
|
-
const filled = text.replace(VARIABLE, (whole, name) => (values[name] === undefined ? ((missing = true), whole) : values[name]));
|
|
70
|
-
return missing ? null : filled;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/** The body of a template with its placeholders filled; an unknown placeholder is a refusal. */
|
|
74
|
-
const renderBody = (text, values, forms, template) => text.replace(PLACEHOLDER, (whole, name) => {
|
|
75
|
-
if (forms[name] === undefined) throw new ScaffoldError('HFS_ADD_PLACEHOLDER_UNKNOWN', `template ${template} uses ${whole}, which no variable of this noun provides`);
|
|
76
|
-
return forms[name];
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
/** The spellings of every variable: kebab (`job`), Pascal (`Job`), camel (`jobCamel`), UPPER_SNAKE (`jobUpper`) and snake_case (`jobSnake`). */
|
|
80
|
-
function formsOf(values) {
|
|
81
|
-
const forms = {};
|
|
82
|
-
for (const [name, value] of Object.entries(values)) {
|
|
83
|
-
const kebab = KEBAB.test(value);
|
|
84
|
-
forms[name] = value;
|
|
85
|
-
forms[name[0].toUpperCase() + name.slice(1)] = kebab ? pascalOf(value) : value[0].toUpperCase() + value.slice(1);
|
|
86
|
-
forms[`${name}Camel`] = kebab ? camelOf(value) : value[0].toLowerCase() + value.slice(1);
|
|
87
|
-
forms[`${name}Upper`] = kebab ? value.replaceAll('-', '_').toUpperCase() : value;
|
|
88
|
-
forms[`${name}Snake`] = kebab ? value.replaceAll('-', '_') : value;
|
|
89
|
-
}
|
|
90
|
-
return forms;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** The variables of the noun: the instance name, the options it needs and the defaults of the others. */
|
|
94
|
-
function variablesOf({ spec, noun, name, options }) {
|
|
95
|
-
if (!KEBAB.test(name)) throw new ScaffoldError('HFS_ADD_NAME_INVALID', `${noun} name ${name} must be kebab-case (letters, digits and single dashes)`);
|
|
96
|
-
const values = { [spec.variable]: name };
|
|
97
|
-
for (const need of spec.needs ?? []) {
|
|
98
|
-
const value = options[need];
|
|
99
|
-
if (typeof value !== 'string' || !value) throw new ScaffoldError('HFS_ADD_OPTION_MISSING', `starci app add ${noun} needs --${need}`);
|
|
100
|
-
if (need === 'service') {
|
|
101
|
-
const [className, module] = value.split('=');
|
|
102
|
-
if (!className || !module) throw new ScaffoldError('HFS_ADD_OPTION_INVALID', '--service takes <ServiceClass>=<module specifier>');
|
|
103
|
-
values.service = className;
|
|
104
|
-
values.serviceModule = module;
|
|
105
|
-
} else {
|
|
106
|
-
if (!KEBAB.test(value)) throw new ScaffoldError('HFS_ADD_OPTION_INVALID', `--${need} ${value} must be kebab-case`);
|
|
107
|
-
values[need] = value;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
for (const [key, rule] of Object.entries(spec.defaults ?? {})) if (values[key] === undefined) values[key] = rule === '@name' ? name : String(rule);
|
|
111
|
-
return values;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
31
|
/**
|
|
115
32
|
* Generates the tree of one noun in the app at `repoRoot` (the folder of hfs.json).
|
|
116
33
|
*
|
|
@@ -126,80 +43,32 @@ export function addKind({ repoRoot, noun, name, options = {}, now = Date.now })
|
|
|
126
43
|
const result = addApp({ root: repoRoot, name });
|
|
127
44
|
return { ...result, registered: { patterns: [], kinds: [] } };
|
|
128
45
|
}
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (!fs.existsSync(declarationFile)) throw new ScaffoldError('HFS_ADD_NOT_AN_APP', 'starci app add runs at the app root (the folder of hfs.json)');
|
|
135
|
-
const repo = readRepoDeclaration(manifest, repoRoot);
|
|
136
|
-
if (!repo.sides?.be) throw new ScaffoldError('HFS_ADD_NO_BACK_END', 'this app has no back-end side');
|
|
137
|
-
const liteCliBuiltIn = noun === 'cli' && repo.edition === 'lite' && (name === 'migrate' || name === 'seed');
|
|
138
|
-
if (liteCliBuiltIn) {
|
|
139
|
-
if (repo.sides.be.apps.some(app => app.kind === 'cli'))
|
|
140
|
-
throw new ScaffoldError('HFS_ADD_EXISTS', `the lite cli already carries its built-in ${name} group`);
|
|
141
|
-
return { created: ensureLiteCli({ root: repoRoot, manifest, repo }), registered: { patterns: [], kinds: ['cli'] } };
|
|
142
|
-
}
|
|
143
|
-
const nouns = [noun, ...(spec.also ?? [])];
|
|
144
|
-
const specs = nouns.map((each) => [each, params.addKinds[each]]);
|
|
145
|
-
const patterns = [...new Set(specs.flatMap(([, eachSpec]) => eachSpec.patterns))];
|
|
146
|
-
const topics = new Set([...specs.map(([, eachSpec]) => eachSpec.topic), ...topicsOfPatterns(patterns)]);
|
|
147
|
-
const liteCli = noun === 'cli' && repo.edition === 'lite';
|
|
148
|
-
const liteApi = noun === 'api' && repo.edition === 'lite';
|
|
149
|
-
// Lite has every pattern (owner ruling 2026-10-12), but a topic with a liteAdapter is generated only from full templates.
|
|
150
|
-
refuseFullAdapterInLite({ edition: repo.edition, topics: [...topics].map(readTopic), command: `add ${noun}` });
|
|
151
|
-
const trees = new Map([...topics].map((topic) => {
|
|
152
|
-
// Lite keeps every colocated unit spec of a tree (owner ruling 2026-10-12); only the webhook door and its spec are lite's own.
|
|
153
|
-
const entries = repo.edition === 'lite' ? selectLiteWebhookEntries(readTree(topic)) : readTree(topic);
|
|
154
|
-
return [topic, liteApi && topic === spec.topic ? selectLiteApiEntries(entries) : entries];
|
|
155
|
-
}));
|
|
156
|
-
// The slots the noun's trees name decide whether the edition has the noun (a slot lite does not carry or forbids
|
|
157
|
-
// makes it a full-edition capability); never the noun's name. Optional entries are never generated, so they gate nothing.
|
|
158
|
-
const writtenSlots = new Set();
|
|
159
|
-
for (const [each, eachSpec] of specs) for (const entry of trees.get(eachSpec.topic)) if (!entry.path.startsWith(PLATFORM_PREFIX) && entry.optional !== true && entry.slot) writtenSlots.add(entry.slot);
|
|
160
|
-
for (const topic of topics) for (const entry of trees.get(topic)) if (entry.path.startsWith(PLATFORM_PREFIX) && entry.optional !== true && entry.slot) writtenSlots.add(entry.slot);
|
|
161
|
-
refuseInEdition({ resolver: createSlotResolver(manifest, repo), slotIds: [...writtenSlots], command: `add ${noun}` });
|
|
162
|
-
const values = Object.assign({}, ...specs.map(([each, eachSpec]) => variablesOf({ spec: eachSpec, noun: each, name, options })), variablesOf({ spec, noun, name, options }));
|
|
163
|
-
const stamp = String(now()).padStart(13, '0').slice(0, 13);
|
|
164
|
-
const withStamp = { ...values, epochMs13: stamp };
|
|
165
|
-
const forms = formsOf(withStamp);
|
|
166
|
-
const beRoot = path.join(repoRoot, 'be');
|
|
167
|
-
|
|
168
|
-
const planned = [];
|
|
169
|
-
const consider = (entry, topic, instance, shared = false) => {
|
|
170
|
-
if (entry.optional === true) return;
|
|
171
|
-
const target = fillPath(entry.path, withStamp);
|
|
172
|
-
if (target === null) return;
|
|
173
|
-
const absolute = path.join(beRoot, ...target.split('/'));
|
|
174
|
-
// A platform capability is whole or absent: it exists when its index.ts does, and then none of its files is written again.
|
|
175
|
-
if (!instance && fs.existsSync(path.join(beRoot, ...target.split('/').slice(0, 4), 'index.ts'))) return;
|
|
176
|
-
if ((!instance || shared) && fs.existsSync(absolute)) return;
|
|
177
|
-
if (!entry.template) throw new ScaffoldError('HFS_ADD_TEMPLATE_MISSING', `${topic}.yaml lists ${entry.path} without a template, so it cannot be generated`);
|
|
178
|
-
const templateFile = path.join(PATTERN_TEMPLATES, ...entry.template.split('/'));
|
|
179
|
-
if (!fs.existsSync(templateFile)) throw new ScaffoldError('HFS_ADD_TEMPLATE_MISSING', `${topic}.yaml names template ${entry.template} for ${entry.path}, which the package does not carry`);
|
|
180
|
-
planned.push({ target, absolute, body: renderBody(fs.readFileSync(templateFile, 'utf8'), withStamp, forms, entry.template), instance });
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
for (const [each, eachSpec] of specs) for (const entry of trees.get(eachSpec.topic)) if (!entry.path.startsWith(PLATFORM_PREFIX)) consider(entry, eachSpec.topic, true, each !== noun);
|
|
184
|
-
for (const topic of topics) for (const entry of trees.get(topic)) if (entry.path.startsWith(PLATFORM_PREFIX)) consider(entry, topic, false);
|
|
185
|
-
|
|
186
|
-
const clash = planned.filter((file) => file.instance && fs.existsSync(file.absolute)).map((file) => file.target);
|
|
187
|
-
if (clash.length) throw new ScaffoldError('HFS_ADD_EXISTS', `${noun} ${name} already exists: ${clash.join(', ')}`);
|
|
46
|
+
const context = readAddContext({ repoRoot, noun, name, options, now });
|
|
47
|
+
const builtin = addBuiltinLiteCli(context);
|
|
48
|
+
if (builtin) return builtin;
|
|
49
|
+
return writeKind({ ...context, ...planKind(context) });
|
|
50
|
+
}
|
|
188
51
|
|
|
189
|
-
|
|
190
|
-
for (const file of planned) {
|
|
191
|
-
fs.mkdirSync(path.dirname(file.absolute), { recursive: true });
|
|
192
|
-
fs.writeFileSync(file.absolute, file.body);
|
|
193
|
-
}
|
|
52
|
+
function registerFeatureExports({ repoRoot, repo, noun, values }) {
|
|
194
53
|
if (noun === 'webhook' || (repo.edition === 'lite' && noun === 'api')) {
|
|
195
54
|
registerLiteExports({ root: repoRoot, generator: repo.edition === 'lite' ? noun : 'webhook-full' });
|
|
196
55
|
}
|
|
197
|
-
if (repo.edition === 'lite' &&
|
|
56
|
+
if (repo.edition === 'lite' && noun === 'api') {
|
|
198
57
|
registerLiteDomainService({ root: repoRoot, moduleSpecifier: values.serviceModule, service: values.service });
|
|
199
58
|
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function writeKind({ repoRoot, noun, name, now, manifest, repo, spec, declarationFile, values, forms, beRoot, patterns, planned }) {
|
|
62
|
+
if (noun === 'api' || noun === 'webhook') preflightFeatureComposition({ repoRoot, repo, noun, name });
|
|
63
|
+
const bootstrapCreated = noun === 'cli' && repo.edition === 'lite' ? ensureLiteCli({ root: repoRoot, manifest, repo }) : [];
|
|
64
|
+
for (const file of planned) {
|
|
65
|
+
fs.mkdirSync(path.dirname(file.absolute), { recursive: true });
|
|
66
|
+
fs.writeFileSync(file.absolute, file.body);
|
|
67
|
+
}
|
|
68
|
+
registerFeatureExports({ repoRoot, repo, noun, values });
|
|
200
69
|
if (spec.wire) wire({ beRoot, wire: spec.wire, forms, noun, name });
|
|
201
|
-
const platformCreated = repo.edition === 'lite' && (noun === 'api' || noun === 'webhook') ? wireLiteFeature({ repoRoot, repo, noun, name }) : [];
|
|
202
70
|
const inboxCreated = addLiteWebhookInbox({ root: repoRoot, repo, noun, provider: name, values, now });
|
|
71
|
+
const platformCreated = noun === 'api' || noun === 'webhook' ? wireFeature({ repoRoot, repo, noun, name }) : [];
|
|
203
72
|
const graphqlDoor = noun === 'api' ? ensureGraphqlPeers(repoRoot) : [];
|
|
204
73
|
const registered = register({ declarationFile, spec: { patterns, trigger: spec.trigger, optionalSlots: noun === 'api' ? [GRAPHQL_CONTRACT_SLOT] : [] } });
|
|
205
74
|
return { created: [...bootstrapCreated, ...planned.map((file) => `be/${file.target}`), ...platformCreated, ...inboxCreated, ...graphqlDoor], registered };
|
|
@@ -223,15 +92,59 @@ function ensureGraphqlPeers(repoRoot) {
|
|
|
223
92
|
return ['package.json'];
|
|
224
93
|
}
|
|
225
94
|
|
|
226
|
-
/**
|
|
227
|
-
function
|
|
228
|
-
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
95
|
+
/** The skeleton registrations that move into area groups when the first transport is added. */
|
|
96
|
+
function skeletonComposition(lines, edition) {
|
|
97
|
+
const active = compositionCode(lines.join('\n')).split('\n');
|
|
98
|
+
const candidates = [
|
|
99
|
+
{ area: 'domain', symbol: 'IdentityModule', source: '@modules/domain/identity', entry: edition === 'lite' ? 'IdentityModule.register({ isGlobal: true, verifyAccessToken })' : 'IdentityModule.register({ isGlobal: true })' },
|
|
100
|
+
{ area: 'domain', symbol: 'LivenessModule', source: '@modules/domain/liveness', entry: 'LivenessModule.register({ isGlobal: true })' },
|
|
101
|
+
{ area: 'api', symbol: 'SystemHealthHttpModule', source: '@features/api/system-health', entry: 'SystemHealthHttpModule.register({})' },
|
|
102
|
+
{ area: 'api', symbol: 'GraphqlModule', source: '@modules/platform/graphql', entry: 'GraphqlModule.register({ isGlobal: true })' },
|
|
103
|
+
];
|
|
104
|
+
if (edition === 'full') candidates.push({ area: 'domain', symbol: 'NoteModule', source: '@modules/domain/note', entry: 'NoteModule.register({ isGlobal: true })' });
|
|
105
|
+
return candidates.filter(({ entry }) => active.some(line => line.trim() === `${entry},`));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Existing groups that the root spreads must survive when another area is generated. */
|
|
109
|
+
function referencedComposeAreas(lines) {
|
|
110
|
+
const code = compositionCode(lines.join('\n'));
|
|
111
|
+
return ['domain', 'system', 'api', 'webhooks'].filter((area) => code.includes(`...${area}Modules(`));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function featureCompositionRoot({ repoRoot, repo, noun }) {
|
|
115
|
+
const app = repo.sides.be.apps.find(entry => entry.kind === 'api');
|
|
116
|
+
if (!app) throw new ScaffoldError('HFS_ADD_NO_API_OWNER', `add ${noun} needs a declared api app`);
|
|
117
|
+
const relative = `be/apps/${app.name}/src/app.module.ts`;
|
|
118
|
+
const file = path.join(repoRoot, ...relative.split('/'));
|
|
119
|
+
if (!fs.existsSync(file)) throw new ScaffoldError('HFS_ADD_WIRE_MISSING', `add ${noun} registers its transport in ${relative}, which does not exist`);
|
|
120
|
+
return { app, relative, file, lines: fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n').split('\n') };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Refuse an incompatible root or area group before generating the feature or changing its capability exports. */
|
|
124
|
+
function preflightFeatureComposition({ repoRoot, repo, noun, name }) {
|
|
125
|
+
const { app, relative, lines } = featureCompositionRoot({ repoRoot, repo, noun });
|
|
126
|
+
if (noun === 'webhook' && repo.edition === 'lite') {
|
|
127
|
+
if (!compositionCode(lines.join('\n')).split('\n').some(line => /^\s*imports: \[$/.test(line)))
|
|
128
|
+
throw new ScaffoldError('HFS_ADD_WIRE_INVALID', `add webhook needs a multiline imports array in ${relative}`);
|
|
129
|
+
planLiteWebhookConfig({ repoRoot, app: app.name, provider: name });
|
|
130
|
+
}
|
|
131
|
+
const areas = new Set([noun === 'api' ? 'api' : 'webhooks', ...referencedComposeAreas(lines), ...skeletonComposition(lines, repo.edition).map(entry => entry.area)]);
|
|
132
|
+
for (const area of areas) {
|
|
133
|
+
readComposeGroup({ repoRoot, repo, app: app.name, area, noun });
|
|
134
|
+
const argument = area === 'domain' && repo.edition === 'lite' ? 'verifyAccessToken' : '';
|
|
135
|
+
addModuleImport(lines, { importLine: `import { ${area}Modules } from "./compose/${area}"`, entry: `...${area}Modules(${argument})`, relative, noun });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Remove only a moved skeleton registration and its named import, keeping guards imported from the same barrel. */
|
|
140
|
+
function removeSkeletonRegistration(lines, { symbol, source, entry }) {
|
|
141
|
+
const entryAt = compositionCode(lines.join('\n')).split('\n').findIndex(line => line.trim() === `${entry},`);
|
|
142
|
+
lines.splice(entryAt, 1);
|
|
143
|
+
const importAt = lines.findIndex(line => line.startsWith('import { ') && line.replace(/;\s*$/, '').endsWith(` } from "${source}"`));
|
|
144
|
+
if (importAt < 0) return;
|
|
145
|
+
const symbols = lines[importAt].slice('import { '.length, lines[importAt].indexOf(' } from ')).split(',').map(value => value.trim()).filter(value => value !== symbol);
|
|
146
|
+
if (symbols.length) lines[importAt] = `import { ${symbols.join(', ')} } from "${source}"`;
|
|
147
|
+
else lines.splice(importAt, 1);
|
|
235
148
|
}
|
|
236
149
|
|
|
237
150
|
/** The GraphQL server capability (be/src/modules/platform/graphql), written whole by the first GraphQL feature of an app that has none. */
|
|
@@ -249,38 +162,62 @@ function ensureGraphqlPlatform(repoRoot) {
|
|
|
249
162
|
return written;
|
|
250
163
|
}
|
|
251
164
|
|
|
252
|
-
/**
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
*/
|
|
256
|
-
function wireLiteFeature({ repoRoot, repo, noun, name }) {
|
|
257
|
-
const app = repo.sides.be.apps.find(entry => entry.kind === 'api');
|
|
258
|
-
if (!app) throw new ScaffoldError('HFS_ADD_NO_API_OWNER', `add ${noun} needs a declared lite api app`);
|
|
259
|
-
const relative = `be/apps/${app.name}/src/app.module.ts`;
|
|
260
|
-
const file = path.join(repoRoot, ...relative.split('/'));
|
|
261
|
-
if (!fs.existsSync(file)) throw new ScaffoldError('HFS_ADD_WIRE_MISSING', `add ${noun} registers its transport in ${relative}, which does not exist`);
|
|
262
|
-
const lines = fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n').split('\n');
|
|
263
|
-
const created = [];
|
|
165
|
+
/** The typed area members of one transport, including lite transactional handoff capabilities. */
|
|
166
|
+
function transportComposition(noun, name, edition) {
|
|
167
|
+
const entries = [];
|
|
264
168
|
if (noun === 'api') {
|
|
265
|
-
|
|
266
|
-
addModuleImport(lines, { importLine: 'import { GraphqlModule } from "@modules/platform/graphql"', entry: 'GraphqlModule.register({ isGlobal: true })', relative, noun });
|
|
169
|
+
entries.push({ importLine: 'import { GraphqlModule } from "@modules/platform/graphql"', entry: 'GraphqlModule.register({ isGlobal: true })' });
|
|
267
170
|
const symbol = `${pascalOf(name)}GraphqlModule`;
|
|
268
|
-
|
|
171
|
+
entries.push({ importLine: `import { ${symbol} } from "@features/api/${name}"`, entry: symbol });
|
|
269
172
|
} else {
|
|
270
173
|
const symbol = `${pascalOf(name)}HttpModule`;
|
|
271
|
-
|
|
174
|
+
entries.push({ importLine: `import { ${symbol} } from "@features/webhooks/${name}"`, entry: symbol });
|
|
175
|
+
if (edition === 'lite') {
|
|
176
|
+
const inbox = `${pascalOf(name)}InboxModule`;
|
|
177
|
+
const importLine = `import { ${inbox} } from "@modules/domain/${name}-inbox"`;
|
|
178
|
+
const entry = `${inbox}.register({ isGlobal: true })`;
|
|
179
|
+
entries.push({ importLine, entry });
|
|
180
|
+
for (const platform of ['InboxModule', 'EventBusModule']) {
|
|
181
|
+
const capability = platform === 'InboxModule' ? 'inbox' : 'event-bus';
|
|
182
|
+
entries.push({ importLine: `import { ${platform} } from "@modules/platform/${capability}"`, entry: `${platform}.register({ isGlobal: true })` });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return entries;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Compose each generated transport in its declared API owner through an explicitly typed area group. The root only spreads
|
|
191
|
+
* the group; it never acquires a second feature list. Lite's inbox capability belongs to its webhook group as well.
|
|
192
|
+
*/
|
|
193
|
+
function wireFeature({ repoRoot, repo, noun, name }) {
|
|
194
|
+
const { app, relative, file, lines } = featureCompositionRoot({ repoRoot, repo, noun });
|
|
195
|
+
const created = [];
|
|
196
|
+
const area = noun === 'api' ? 'api' : 'webhooks';
|
|
197
|
+
const groups = new Map([...referencedComposeAreas(lines), area].map((group) => [group, []]));
|
|
198
|
+
for (const migration of skeletonComposition(lines, repo.edition)) {
|
|
199
|
+
const entries = groups.get(migration.area) ?? [];
|
|
200
|
+
entries.push({ importLine: `import { ${migration.symbol} } from "${migration.source}"`, entry: migration.entry });
|
|
201
|
+
groups.set(migration.area, entries);
|
|
202
|
+
removeSkeletonRegistration(lines, migration);
|
|
203
|
+
}
|
|
204
|
+
const entries = groups.get(area);
|
|
205
|
+
if (noun === 'api') created.push(...ensureGraphqlPlatform(repoRoot));
|
|
206
|
+
entries.push(...transportComposition(noun, name, repo.edition));
|
|
207
|
+
for (const [group, members] of groups) {
|
|
208
|
+
const symbol = `${group}Modules`;
|
|
209
|
+
const argument = group === 'domain' && repo.edition === 'lite' ? 'verifyAccessToken' : '';
|
|
210
|
+
addModuleImport(lines, { importLine: `import { ${symbol} } from "./compose/${group}"`, entry: `...${symbol}(${argument})`, relative, noun });
|
|
211
|
+
created.push(...writeComposeGroup({ repoRoot, repo, app: app.name, area: group, entries: members, noun }));
|
|
272
212
|
}
|
|
273
|
-
fs.writeFileSync(file, `${lines.join('\n').
|
|
274
|
-
if (noun === 'webhook') wireLiteWebhookConfig({ repoRoot, app: app.name, provider: name });
|
|
213
|
+
fs.writeFileSync(file, `${lines.join('\n').trimEnd()}\n`);
|
|
214
|
+
if (noun === 'webhook' && repo.edition === 'lite') wireLiteWebhookConfig({ repoRoot, app: app.name, provider: name });
|
|
275
215
|
return created;
|
|
276
216
|
}
|
|
277
217
|
|
|
278
|
-
/** Adds
|
|
279
|
-
function
|
|
280
|
-
const
|
|
281
|
-
const file = path.join(repoRoot, ...relative.split('/'));
|
|
282
|
-
let text = fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n');
|
|
283
|
-
const httpSecurityImport = /^import \{ ([^}]+) \} from "@modules\/platform\/http-security"$/m;
|
|
218
|
+
/** Adds the provider config reader to the existing typed HTTP security import. */
|
|
219
|
+
function webhookConfigImport(text, relative) {
|
|
220
|
+
const httpSecurityImport = /^import \{ ([^}]+) \} from "@modules\/platform\/http-security";?$/m;
|
|
284
221
|
const importMatch = httpSecurityImport.exec(text);
|
|
285
222
|
if (!importMatch) throw new ScaffoldError('HFS_ADD_WIRE_INVALID', `add webhook could not find the HTTP security import in ${relative}`);
|
|
286
223
|
const imported = importMatch[1].split(',').map(symbol => symbol.trim()).filter(Boolean);
|
|
@@ -289,53 +226,98 @@ function wireLiteWebhookConfig({ repoRoot, app, provider }) {
|
|
|
289
226
|
imported.splice(configIndex < 0 ? imported.length : configIndex + 1, 0, 'parseWebhookProviderConfig');
|
|
290
227
|
text = text.replace(httpSecurityImport, `import { ${imported.join(', ')} } from "@modules/platform/http-security"`);
|
|
291
228
|
}
|
|
229
|
+
return text;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Declares a signing key by name and leaves custody/provisioning to the environment owner. */
|
|
233
|
+
function webhookRosterPatches(repoRoot, provider) {
|
|
234
|
+
const stacks = path.join(repoRoot, '.starcistacks');
|
|
235
|
+
if (!fs.existsSync(stacks)) return [];
|
|
236
|
+
const key = formsOf({ provider }).providerUpper + '_WEBHOOK_SECRET';
|
|
237
|
+
const patches = [];
|
|
238
|
+
for (const entry of fs.readdirSync(stacks, { withFileTypes: true })) {
|
|
239
|
+
if (!entry.isDirectory()) continue;
|
|
240
|
+
const file = path.join(stacks, entry.name, 'runtime', 'env', 'KEYS.md');
|
|
241
|
+
if (!fs.existsSync(file)) continue;
|
|
242
|
+
const before = fs.readFileSync(file, 'utf8');
|
|
243
|
+
if (before.includes('\x60' + key + '\x60')) continue;
|
|
244
|
+
const lines = before.replace(/\r\n/g, '\n').split('\n');
|
|
245
|
+
const lastRow = lines.reduce((last, line, index) => line.startsWith('|') ? index : last, -1);
|
|
246
|
+
const row = '| \x60' + key + '\x60 | API signed ' + provider + ' webhook; owner must seal this key and provision its versioned external runtime secret before deployment |';
|
|
247
|
+
lines.splice(lastRow + 1, 0, row);
|
|
248
|
+
patches.push({ file, text: lines.join('\n') });
|
|
249
|
+
}
|
|
250
|
+
return patches;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Adds one generated webhook provider to the lite API's typed env reader (or an existing inline bootstrap) and enables exact raw-body verification. */
|
|
254
|
+
function planLiteWebhookConfig({ repoRoot, app, provider }) {
|
|
255
|
+
const envRelative = `be/apps/${app}/src/${app}.env.ts`;
|
|
256
|
+
const relative = fs.existsSync(path.join(repoRoot, ...envRelative.split('/')))
|
|
257
|
+
? envRelative
|
|
258
|
+
: `be/apps/${app}/src/main.ts`;
|
|
259
|
+
const file = path.join(repoRoot, ...relative.split('/'));
|
|
260
|
+
if (!fs.existsSync(file)) throw new ScaffoldError('HFS_ADD_WIRE_MISSING', `add webhook needs ${relative}`);
|
|
261
|
+
let text = fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n');
|
|
262
|
+
text = webhookConfigImport(text, relative);
|
|
292
263
|
const providerKey = /^[a-z][a-z0-9]*$/.test(provider) ? provider : JSON.stringify(provider);
|
|
293
|
-
const
|
|
264
|
+
const indent = /^([ \t]*)httpSecurity:/m.exec(text)?.[1] ?? ' ';
|
|
265
|
+
const providerLine = `${indent} ${providerKey}: parseWebhookProviderConfig(env, ${JSON.stringify(formsOf({ provider }).providerUpper)}),`;
|
|
294
266
|
if (!text.includes(providerLine)) {
|
|
295
267
|
const empty = /httpSecurity:\s*parseHttpSecurityConfig\(env\)/;
|
|
296
268
|
if (empty.test(text)) {
|
|
297
|
-
text = text.replace(empty, ['httpSecurity: {',
|
|
269
|
+
text = text.replace(empty, ['httpSecurity: {', `${indent} ...parseHttpSecurityConfig(env),`, `${indent} webhooks: {`, providerLine, `${indent} },`, `${indent}}`].join('\n'));
|
|
298
270
|
} else {
|
|
299
|
-
const found =
|
|
271
|
+
const found = /^[ \t]*webhooks: \{$/m.exec(text);
|
|
300
272
|
if (!found) throw new ScaffoldError('HFS_ADD_WIRE_INVALID', `add webhook could not find httpSecurity options in ${relative}`);
|
|
301
273
|
const insertAt = found.index + found[0].length;
|
|
302
274
|
text = `${text.slice(0, insertAt)}\n${providerLine}${text.slice(insertAt)}`;
|
|
303
275
|
}
|
|
304
276
|
}
|
|
305
277
|
text = text.replace('NestFactory.create(AppModule.register(options))', 'NestFactory.create(AppModule.register(options), { rawBody: true })');
|
|
306
|
-
|
|
278
|
+
const planned = [{ file, text }];
|
|
279
|
+
if (relative === envRelative) {
|
|
280
|
+
const main = path.join(repoRoot, 'be', 'apps', app, 'src', 'main.ts');
|
|
281
|
+
if (!fs.existsSync(main)) throw new ScaffoldError('HFS_ADD_WIRE_MISSING', `add webhook needs be/apps/${app}/src/main.ts`);
|
|
282
|
+
const entry = fs.readFileSync(main, 'utf8').replace('NestFactory.create(AppModule.register(options))', 'NestFactory.create(AppModule.register(options), { rawBody: true })');
|
|
283
|
+
planned.push({ file: main, text: entry });
|
|
284
|
+
}
|
|
285
|
+
planned.push(...webhookRosterPatches(repoRoot, provider));
|
|
286
|
+
return planned;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Applies the preflighted provider configuration and exact raw-body bootstrap option. */
|
|
290
|
+
function wireLiteWebhookConfig(input) {
|
|
291
|
+
const patches = planLiteWebhookConfig(input);
|
|
292
|
+
const originals = new Map(patches.map(({ file }) => [file, fs.readFileSync(file)]));
|
|
293
|
+
try {
|
|
294
|
+
for (const { file, text } of patches) fs.writeFileSync(file, text);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
for (const [file, text] of originals) fs.writeFileSync(file, text);
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
307
299
|
}
|
|
308
300
|
|
|
309
301
|
/**
|
|
310
302
|
* Registers the generated member in the static module that lists its siblings (the cli feature root lists each group module): one import
|
|
311
|
-
* line after the last import and one more entry of the `imports` array of its `@Module
|
|
303
|
+
* line after the last import and one more entry of the `imports` array of its `@Module`, written `Symbol.register({})` (every module has a module definition). Nothing happens when the member is already listed.
|
|
312
304
|
*/
|
|
313
305
|
function wire({ beRoot, wire: target, forms, noun, name }) {
|
|
314
306
|
const file = path.join(beRoot, ...target.file.split('/'));
|
|
315
307
|
if (!fs.existsSync(file)) throw new ScaffoldError('HFS_ADD_WIRE_MISSING', `starci app add ${noun} ${name} registers the member in ${target.file}, which does not exist`);
|
|
316
|
-
const fill = (text) => text
|
|
308
|
+
const fill = (text) => fillKnownForms(text, forms);
|
|
317
309
|
const symbol = fill(target.symbol);
|
|
318
310
|
let text = fs.readFileSync(file, 'utf8');
|
|
319
|
-
if (new RegExp(
|
|
311
|
+
if (new RegExp(String.raw`\b${symbol}\b`).test(text)) return;
|
|
320
312
|
const array = /@Module\(\{ imports: \[([^\]]*)\] \}\)/.exec(text);
|
|
321
313
|
const lines = text.split('\n');
|
|
322
314
|
const lastImport = lines.map((line) => line.startsWith('import ')).lastIndexOf(true);
|
|
323
315
|
if (!array || lastImport < 0) throw new ScaffoldError('HFS_ADD_WIRE_FAILED', `${target.file} has no \`@Module({ imports: [...] })\` to register ${symbol} in`);
|
|
324
316
|
lines.splice(lastImport + 1, 0, `import { ${symbol} } from "${fill(target.import)}"`);
|
|
325
|
-
text = lines.join('\n').replace(array[0], `@Module({ imports: [${[array[1].trim(), symbol].filter(Boolean).join(', ')}] })`);
|
|
317
|
+
text = lines.join('\n').replace(array[0], `@Module({ imports: [${[array[1].trim(), `${symbol}.register({})`].filter(Boolean).join(', ')}] })`);
|
|
326
318
|
fs.writeFileSync(file, text);
|
|
327
319
|
}
|
|
328
320
|
|
|
329
|
-
/** The topics that carry the platform capability of each pattern: the topic whose `pattern` key names it. */
|
|
330
|
-
function topicsOfPatterns(patterns) {
|
|
331
|
-
const topics = new Set();
|
|
332
|
-
for (const file of fs.readdirSync(TOPICS_DIR).filter((entry) => entry.endsWith('.yaml'))) {
|
|
333
|
-
const doc = parseYaml(fs.readFileSync(path.join(TOPICS_DIR, file), 'utf8'));
|
|
334
|
-
if (patterns.includes(doc?.pattern)) topics.add(file.slice(0, -'.yaml'.length));
|
|
335
|
-
}
|
|
336
|
-
return topics;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
321
|
/** Adds the patterns, the trigger kind and the opt-in slots of the noun to sides.be of hfs.json, keeping its other content and key order. */
|
|
340
322
|
function register({ declarationFile, spec }) {
|
|
341
323
|
const declaration = JSON.parse(fs.readFileSync(declarationFile, 'utf8'));
|