@starci/hfs 4.0.8 → 4.0.9
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 +9 -0
- package/README.md +81 -39
- package/emit/contracts.mjs +56 -9
- package/emit/db-types.mjs +169 -0
- package/emit/events.mjs +41 -0
- package/emit/operations-worker.mjs +1 -1
- package/emit/operations.mjs +2 -2
- package/emit/schema-worker.mjs +1 -1
- package/lint/bound-sys.cjs +50 -0
- package/lint/run.mjs +53 -19
- package/package.json +14 -9
- package/report/sonar.mjs +8 -8
- package/runtime/engine/config.mjs +494 -0
- package/runtime/engine/invalid-config.mjs +15 -0
- package/runtime/engine/orca-config.mjs +27 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +160 -29
- package/runtime/knowledge/hfs/peer-integrations.yaml +2 -2
- package/runtime/knowledge/hfs/rules.yaml +2059 -0
- package/runtime/knowledge/hfs/slots.yaml +912 -120
- package/runtime/knowledge/patterns/be/api.yaml +369 -0
- package/runtime/knowledge/patterns/be/cli.yaml +297 -0
- package/runtime/knowledge/patterns/be/domain.yaml +113 -0
- package/runtime/knowledge/patterns/be/event-bus.yaml +285 -0
- package/runtime/knowledge/patterns/be/jobs.yaml +261 -0
- package/runtime/knowledge/patterns/be/projections.yaml +178 -0
- package/runtime/knowledge/patterns/be/queues.yaml +236 -0
- package/runtime/knowledge/patterns/be/reactors.yaml +140 -0
- package/runtime/knowledge/patterns/be/realtime.yaml +222 -0
- package/runtime/knowledge/patterns/be/saga.yaml +248 -0
- package/runtime/knowledge/patterns/be/webhooks.yaml +221 -0
- package/runtime/knowledge/patterns/fe/folder.yaml +131 -25
- package/runtime/knowledge/sonar-gate.yaml +52 -18
- package/runtime/modules/kernel/failure-codes.yaml +571 -51
- package/runtime/modules/models/registry.yaml +591 -0
- package/runtime/modules/models/runtimes.yaml +701 -0
- package/runtime/scripts/api/fs/forbidden-root.mjs +51 -0
- package/runtime/scripts/api/fs/is-link-like.mjs +25 -0
- package/runtime/scripts/api/fs/lib.mjs +10 -2
- package/runtime/scripts/api/fs/safe-remove.mjs +12 -132
- package/runtime/scripts/api/git/config-get.mjs +10 -0
- package/runtime/scripts/api/git/diff.mjs +5 -0
- package/runtime/scripts/api/git/lib.mjs +16 -26
- package/runtime/scripts/api/git/log.mjs +5 -0
- package/runtime/scripts/api/git/ls-files.mjs +6 -0
- package/runtime/scripts/api/git/ls-tree.mjs +5 -0
- package/runtime/scripts/api/git/merge-base.mjs +9 -0
- package/runtime/scripts/api/git/remote.mjs +5 -0
- package/runtime/scripts/api/git/rev-parse-query.mjs +5 -0
- package/runtime/scripts/api/git/show.mjs +6 -0
- package/runtime/scripts/api/sops/decrypt.mjs +9 -5
- package/runtime/scripts/api/sops/lib.mjs +35 -0
- package/runtime/scripts/api/sops/seal.mjs +18 -0
- package/runtime/scripts/hfs/architecture/ast-walks.mjs +220 -0
- package/runtime/scripts/hfs/architecture/backend.mjs +16 -23
- package/runtime/scripts/hfs/architecture/background-unowned.mjs +16 -34
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +3 -1
- package/runtime/scripts/hfs/architecture/clones.mjs +4 -4
- package/runtime/scripts/hfs/architecture/config.mjs +9 -6
- package/runtime/scripts/hfs/architecture/connection-map.mjs +20 -8
- package/runtime/scripts/hfs/architecture/context-coupling.mjs +88 -0
- package/runtime/scripts/hfs/architecture/context-map.mjs +237 -0
- package/runtime/scripts/hfs/architecture/context-owner.mjs +82 -0
- package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +47 -0
- package/runtime/scripts/hfs/architecture/context-transaction.mjs +60 -0
- package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +4 -10
- package/runtime/scripts/hfs/architecture/contracts.mjs +33 -162
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +58 -29
- package/runtime/scripts/hfs/architecture/doc-language.mjs +1 -1
- package/runtime/scripts/hfs/architecture/entrypoint.mjs +2 -1
- package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +1 -1
- package/runtime/scripts/hfs/architecture/feature-shape.mjs +5 -2
- package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
- package/runtime/scripts/hfs/architecture/frontend.mjs +17 -43
- package/runtime/scripts/hfs/architecture/hfs.mjs +24 -39
- package/runtime/scripts/hfs/architecture/i18n-keys.mjs +1 -1
- package/runtime/scripts/hfs/architecture/index.mjs +17 -4
- package/runtime/scripts/hfs/architecture/machine-ast.mjs +44 -11
- package/runtime/scripts/hfs/architecture/managed-scripts.mjs +27 -0
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +30 -6
- package/runtime/scripts/hfs/architecture/next-data.mjs +22 -63
- package/runtime/scripts/hfs/architecture/owners.mjs +35 -15
- package/runtime/scripts/hfs/architecture/register-once.mjs +2 -1
- package/runtime/scripts/hfs/architecture/registration.mjs +23 -81
- package/runtime/scripts/hfs/architecture/required-files.mjs +20 -12
- package/runtime/scripts/hfs/architecture/schema-owner.mjs +17 -114
- package/runtime/scripts/hfs/architecture/server-action.mjs +24 -0
- package/runtime/scripts/hfs/architecture/side-boundary.mjs +16 -0
- package/runtime/scripts/hfs/architecture/source-names.mjs +27 -147
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +59 -18
- package/runtime/scripts/hfs/architecture/sql-returning.mjs +64 -0
- package/runtime/scripts/hfs/architecture/supabase-ast.mjs +111 -0
- package/runtime/scripts/hfs/architecture/supabase-be.mjs +131 -0
- package/runtime/scripts/hfs/architecture/supabase-tables.mjs +42 -0
- package/runtime/scripts/hfs/architecture/supabase.mjs +434 -0
- package/runtime/scripts/hfs/architecture/surface.mjs +9 -3
- package/runtime/scripts/hfs/architecture/test-world-files.mjs +9 -19
- package/runtime/scripts/hfs/architecture/tiers.mjs +47 -5
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -3
- package/runtime/scripts/hfs/architecture/typescript.mjs +12 -26
- package/runtime/scripts/hfs/architecture.mjs +4 -4
- package/runtime/scripts/hfs/check.mjs +75 -72
- package/runtime/scripts/hfs/coverage-scope.mjs +162 -0
- package/runtime/scripts/hfs/declaration-shape.mjs +62 -0
- package/runtime/scripts/hfs/declaration-slots.mjs +75 -0
- package/runtime/scripts/hfs/edition-slots.mjs +66 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +109 -32
- package/runtime/scripts/hfs/param-names.mjs +18 -0
- package/runtime/scripts/hfs/path-findings.mjs +1 -1
- package/runtime/scripts/hfs/repo-identity.mjs +9 -6
- package/runtime/scripts/hfs/rule-params-shape.mjs +50 -0
- package/runtime/scripts/hfs/rules/app-root.mjs +51 -0
- package/runtime/scripts/hfs/rules/cli.mjs +37 -0
- package/runtime/scripts/hfs/rules/contract-compat.mjs +71 -0
- package/runtime/scripts/hfs/rules/contract.mjs +10 -9
- package/runtime/scripts/hfs/rules/database-config.mjs +102 -0
- package/runtime/scripts/hfs/rules/database-constants.mjs +13 -0
- package/runtime/scripts/hfs/rules/database-migrations.mjs +111 -0
- package/runtime/scripts/hfs/rules/database-plpgsql.mjs +233 -0
- package/runtime/scripts/hfs/rules/database-sql.mjs +336 -0
- package/runtime/scripts/hfs/rules/database.mjs +68 -0
- package/runtime/scripts/hfs/rules/deps.mjs +1 -1
- package/runtime/scripts/hfs/rules/docker.mjs +259 -0
- package/runtime/scripts/hfs/rules/edition.mjs +111 -0
- package/runtime/scripts/hfs/rules/event-bus.mjs +153 -0
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +1 -1
- package/runtime/scripts/hfs/rules/fe-no-tests.mjs +6 -5
- package/runtime/scripts/hfs/rules/frontend-tree.mjs +9 -9
- package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -6
- package/runtime/scripts/hfs/rules/kinds.mjs +84 -0
- package/runtime/scripts/hfs/rules/lint-suppression.mjs +1 -1
- package/runtime/scripts/hfs/rules/monorepo.mjs +179 -0
- package/runtime/scripts/hfs/rules/peer-integrations.mjs +3 -3
- package/runtime/scripts/hfs/rules/pipeline.mjs +11 -21
- package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -2
- package/runtime/scripts/hfs/rules/read.mjs +1 -1
- package/runtime/scripts/hfs/rules/repo-local-checks.mjs +2 -2
- package/runtime/scripts/hfs/rules/saga.mjs +170 -0
- package/runtime/scripts/hfs/rules/secrets.mjs +3 -3
- package/runtime/scripts/hfs/rules/services.mjs +220 -0
- package/runtime/scripts/hfs/rules/spec-placement.mjs +1 -1
- package/runtime/scripts/hfs/rules/stacks.mjs +3 -3
- package/runtime/scripts/hfs/rules/supabase-secrets.mjs +89 -0
- package/runtime/scripts/hfs/rules/test-topology.mjs +2 -2
- package/runtime/scripts/hfs/secret.mjs +175 -0
- package/runtime/scripts/hfs/slots.mjs +78 -108
- package/runtime/scripts/hfs/sql/pg-parse.mjs +46 -0
- package/runtime/scripts/hfs/test-world-slot.mjs +6 -0
- package/runtime/scripts/hfs/tree.mjs +5 -4
- package/runtime/scripts/hfs/typescript-programs.mjs +0 -5
- package/runtime/scripts/hfs/view.mjs +3 -2
- package/runtime/scripts/lib/dockerfile.mjs +124 -0
- package/runtime/scripts/lib/env.mjs +10 -0
- package/runtime/scripts/lib/event-contract.mjs +103 -0
- package/runtime/scripts/lib/git.mjs +36 -0
- package/runtime/scripts/lib/graphql-contract.mjs +1 -1
- package/runtime/scripts/lib/i18n.mjs +86 -0
- package/runtime/scripts/lib/json.mjs +13 -3
- package/runtime/scripts/lib/language.mjs +24 -5
- package/runtime/scripts/lib/list.mjs +20 -0
- package/runtime/scripts/lib/package-at.mjs +37 -0
- package/runtime/scripts/lib/path-key.mjs +57 -2
- package/runtime/scripts/lib/read-text.mjs +34 -0
- package/runtime/scripts/lib/regex.mjs +4 -0
- package/runtime/scripts/lib/same-text.mjs +5 -0
- package/runtime/scripts/lib/secret-patterns.mjs +4 -3
- package/runtime/scripts/lib/sleep-sync.mjs +2 -1
- package/runtime/scripts/lib/sops-envelope.mjs +58 -0
- package/runtime/scripts/lib/stack-services.mjs +7 -31
- package/runtime/scripts/lib/ts-ast.mjs +35 -0
- package/runtime/scripts/lib/walk.mjs +4 -3
- package/scaffold/add-app.mjs +174 -0
- package/scaffold/add-cli-lite.mjs +257 -0
- package/scaffold/add-inbox-lite.mjs +314 -0
- package/scaffold/add-table.mjs +344 -0
- package/scaffold/add.mjs +299 -0
- package/scaffold/app.mjs +358 -69
- package/scaffold/edition-gate.mjs +25 -0
- package/scaffold/image.mjs +19 -0
- package/scaffold/lite-exports.mjs +186 -0
- package/scaffold/service.mjs +32 -26
- package/scaffold/supabase-ports.mjs +26 -0
- package/src/main.mjs +327 -0
- package/sync/cli.mjs +6 -9
- package/sync/format.mjs +1 -1
- package/sync/hygiene.mjs +10 -10
- package/sync/index.mjs +167 -104
- package/sync/managed.mjs +19 -11
- package/sync/sonar-key.mjs +5 -0
- package/templates/app/ci-workflows/github/workflows/ci.yml +14 -14
- package/templates/app/ci-workflows/github/workflows/images.yml +37 -0
- package/templates/app/ci-workflows/sonar-steps.yml +9 -0
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +66 -0
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +31 -0
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +37 -0
- package/templates/app/codegen-lite/scripts/codegen.mjs +32 -0
- package/templates/app/docker-ignore/dockerignore +50 -0
- package/templates/app/gitignore-lite +35 -0
- package/templates/app/hooks/husky/pre-commit +3 -8
- package/templates/app/hooks/husky/pre-push +45 -6
- package/templates/app/package-scripts/core.json +8 -0
- package/templates/app/package-scripts/package.json +3 -10
- package/templates/app/package-scripts-lite/package.json +15 -0
- package/templates/app/quality-config/codecov.yml +15 -3
- package/templates/app/quality-config/sonar-base.properties +4 -0
- package/templates/app/quality-config/sonar-project.properties +1 -4
- package/templates/app/quality-config-lite/sonar-project.properties +5 -0
- package/templates/app/skeleton/.starcistacks/dev/seeds/primary-notes.sql +5 -0
- package/templates/app/skeleton/.starciwork/features/system-health/index.yaml +4 -0
- package/templates/app/skeleton/.starciwork/{features/index.yaml → index.yaml} +2 -2
- package/templates/app/skeleton/README.md +7 -5
- package/templates/app/skeleton-lite/.sops.yaml +3 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +5 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/secrets/.gitkeep +1 -0
- package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +55 -0
- package/templates/app/skeleton-lite/README.md +40 -0
- package/templates/app/skeleton-lite/supabase/config.toml +41 -0
- package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +56 -0
- package/templates/app/skeleton-lite/supabase/seed.sql +0 -0
- package/templates/app/task-graph/turbo.json +23 -0
- package/templates/app/tool-config-lite/prettierignore +2 -0
- package/templates/app/tool-config-lite/prettierrc +1 -0
- package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +38 -0
- package/templates/app/upgrade-full/.starcistacks/dev/README.md +10 -0
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +13 -0
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +3 -0
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +12 -0
- package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +7 -0
- package/templates/be/common/main-listen.ts.partial +14 -0
- package/templates/be/common/sql-ident.ts.partial +7 -0
- package/templates/be/common/sql-tag.ts.partial +5 -0
- package/templates/be/image/api/Dockerfile +38 -0
- package/templates/be/image/cli/Dockerfile +38 -0
- package/templates/be/image/worker/Dockerfile +36 -0
- package/templates/be/inbox/database-client.ts.tpl +50 -0
- package/templates/be/inbox/index.ts.tpl +2 -0
- package/templates/be/inbox/migration.sql.tpl +29 -0
- package/templates/be/inbox/module-definition.ts.tpl +6 -0
- package/templates/be/inbox/module.ts.tpl +14 -0
- package/templates/be/inbox/options.ts.tpl +2 -0
- package/templates/be/inbox/service.ts.tpl +33 -0
- package/templates/be/inbox/sql.ts.tpl +16 -0
- package/templates/be/inbox/webhook-door.ts.tpl +36 -0
- package/templates/be/patterns/api/application.command.http.ts.tpl +10 -0
- package/templates/be/patterns/api/application.command.ts.tpl +10 -0
- package/templates/be/patterns/api/application.contracts.ts.tpl +11 -0
- package/templates/be/patterns/api/application.handler.http.ts.tpl +22 -0
- package/templates/be/patterns/api/application.handler.ts.tpl +22 -0
- package/templates/be/patterns/api/feature.index.http.ts.tpl +1 -0
- package/templates/be/patterns/api/feature.index.ts.tpl +1 -0
- package/templates/be/patterns/api/feature.module.ts.tpl +6 -0
- package/templates/be/patterns/api/graphql.input.ts.tpl +13 -0
- package/templates/be/patterns/api/graphql.mapper.ts.tpl +9 -0
- package/templates/be/patterns/api/graphql.module.ts.tpl +7 -0
- package/templates/be/patterns/api/graphql.resolver.ts.tpl +27 -0
- package/templates/be/patterns/api/graphql.type.ts.tpl +9 -0
- package/templates/be/patterns/api/http.controller.ts.tpl +27 -0
- package/templates/be/patterns/api/http.mapper.ts.tpl +9 -0
- package/templates/be/patterns/api/http.module.ts.tpl +7 -0
- package/templates/be/patterns/api/http.request.ts.tpl +10 -0
- package/templates/be/patterns/api/http.response.ts.tpl +5 -0
- package/templates/be/patterns/cli/group.cli.spec.ts.tpl +15 -0
- package/templates/be/patterns/cli/group.cli.ts.tpl +11 -0
- package/templates/be/patterns/cli/group.module.ts.tpl +7 -0
- package/templates/be/patterns/cli/sub.run.cli.spec.ts.tpl +17 -0
- package/templates/be/patterns/cli/sub.run.cli.ts.tpl +15 -0
- package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +22 -0
- package/templates/be/patterns/event-bus/platform/event-bus.config.ts.tpl +19 -0
- package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +58 -0
- package/templates/be/patterns/event-bus/platform/event-bus.decorators.ts.tpl +46 -0
- package/templates/be/patterns/event-bus/platform/event-bus.log-events.ts.tpl +12 -0
- package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +10 -0
- package/templates/be/patterns/event-bus/platform/event-bus.module.ts.tpl +52 -0
- package/templates/be/patterns/event-bus/platform/event-bus.options.ts.tpl +19 -0
- package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +28 -0
- package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +165 -0
- package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +93 -0
- package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +90 -0
- package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +58 -0
- package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +136 -0
- package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +56 -0
- package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +272 -0
- package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +128 -0
- package/templates/be/patterns/event-bus/platform/event-transport.port.ts.tpl +180 -0
- package/templates/be/patterns/event-bus/platform/event.policy.spec.ts.tpl +67 -0
- package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +38 -0
- package/templates/be/patterns/event-bus/platform/index.ts.tpl +11 -0
- package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.spec.ts.tpl +348 -0
- package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +241 -0
- package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +15 -0
- package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +8 -0
- package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +37 -0
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +11 -0
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +12 -0
- package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +26 -0
- package/templates/be/patterns/jobs/job.index.ts.tpl +1 -0
- package/templates/be/patterns/jobs/job.module.ts.tpl +6 -0
- package/templates/be/patterns/jobs/job.processor.ts.tpl +20 -0
- package/templates/be/patterns/jobs/job.queue.module.ts.tpl +20 -0
- package/templates/be/patterns/jobs/job.step.ts.tpl +19 -0
- package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +31 -0
- package/templates/be/patterns/jobs/platform/fenced.processor.ts.tpl +13 -0
- package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +150 -0
- package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +100 -0
- package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +138 -0
- package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +69 -0
- package/templates/be/patterns/jobs/platform/jobs.config.ts.tpl +11 -0
- package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +62 -0
- package/templates/be/patterns/jobs/platform/jobs.decorators.ts.tpl +31 -0
- package/templates/be/patterns/jobs/platform/jobs.error.ts.tpl +22 -0
- package/templates/be/patterns/jobs/platform/jobs.index.ts.tpl +11 -0
- package/templates/be/patterns/jobs/platform/jobs.log-events.ts.tpl +9 -0
- package/templates/be/patterns/jobs/platform/jobs.messages.ts.tpl +15 -0
- package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +10 -0
- package/templates/be/patterns/jobs/platform/jobs.module.ts.tpl +32 -0
- package/templates/be/patterns/jobs/platform/jobs.options.ts.tpl +11 -0
- package/templates/be/patterns/jobs/platform/jobs.port.ts.tpl +36 -0
- package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +8 -0
- package/templates/be/patterns/jobs/platform/persistence.job.entity.ts.tpl +53 -0
- package/templates/be/patterns/jobs/platform/persistence.jobs.rows.ts.tpl +13 -0
- package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +33 -0
- package/templates/be/patterns/jobs/platform/persistence.migration.ts.tpl +29 -0
- package/templates/be/patterns/outbox/platform/index.ts.tpl +2 -0
- package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +96 -0
- package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +78 -0
- package/templates/be/patterns/outbox/platform/outbox.log-events.ts.tpl +5 -0
- package/templates/be/patterns/projections/migration.ts.tpl +18 -0
- package/templates/be/patterns/projections/persistence.connection.ts.tpl +8 -0
- package/templates/be/patterns/projections/projection.entity.ts.tpl +9 -0
- package/templates/be/patterns/projections/projection.index.ts.tpl +3 -0
- package/templates/be/patterns/projections/projection.module-definition.ts.tpl +7 -0
- package/templates/be/patterns/projections/projection.module.ts.tpl +18 -0
- package/templates/be/patterns/projections/projection.options.ts.tpl +2 -0
- package/templates/be/patterns/projections/projection.projection.spec.ts.tpl +55 -0
- package/templates/be/patterns/projections/projection.projection.ts.tpl +21 -0
- package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +142 -0
- package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +81 -0
- package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +8 -0
- package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +25 -0
- package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +25 -0
- package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +9 -0
- package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +12 -0
- package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +48 -0
- package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +17 -0
- package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +128 -0
- package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +48 -0
- package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +115 -0
- package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +91 -0
- package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +45 -0
- package/templates/be/patterns/queues/platform/queue.config.ts.tpl +15 -0
- package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +31 -0
- package/templates/be/patterns/queues/platform/queue.decorators.ts.tpl +45 -0
- package/templates/be/patterns/queues/platform/queue.index.ts.tpl +7 -0
- package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +5 -0
- package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +10 -0
- package/templates/be/patterns/queues/platform/queue.module.ts.tpl +62 -0
- package/templates/be/patterns/queues/platform/queue.options.ts.tpl +22 -0
- package/templates/be/patterns/queues/platform/queue.policy.spec.ts.tpl +19 -0
- package/templates/be/patterns/queues/platform/queue.policy.ts.tpl +11 -0
- package/templates/be/patterns/queues/platform/queue.port.ts.tpl +14 -0
- package/templates/be/patterns/queues/queue.index.ts.tpl +3 -0
- package/templates/be/patterns/queues/queue.module.ts.tpl +6 -0
- package/templates/be/patterns/queues/queue.queue.ts.tpl +23 -0
- package/templates/be/patterns/reactors/application.command.ts.tpl +10 -0
- package/templates/be/patterns/reactors/application.contracts.ts.tpl +12 -0
- package/templates/be/patterns/reactors/application.handler.ts.tpl +22 -0
- package/templates/be/patterns/reactors/consumer.ts.tpl +19 -0
- package/templates/be/patterns/reactors/message.module.ts.tpl +20 -0
- package/templates/be/patterns/reactors/reactor.index.ts.tpl +1 -0
- package/templates/be/patterns/reactors/reactor.module.ts.tpl +6 -0
- package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +6 -0
- package/templates/be/patterns/realtime/channel.index.ts.tpl +1 -0
- package/templates/be/patterns/realtime/channel.input.ts.tpl +12 -0
- package/templates/be/patterns/realtime/channel.subscription.spec.ts.tpl +51 -0
- package/templates/be/patterns/realtime/channel.subscription.ts.tpl +32 -0
- package/templates/be/patterns/realtime/channel.type.ts.tpl +9 -0
- package/templates/be/patterns/saga/saga.compensate.command.ts.tpl +11 -0
- package/templates/be/patterns/saga/saga.compensate.contracts.ts.tpl +7 -0
- package/templates/be/patterns/saga/saga.compensate.handler.ts.tpl +22 -0
- package/templates/be/patterns/saga/saga.compensation.ts.tpl +21 -0
- package/templates/be/patterns/saga/saga.complete.command.ts.tpl +11 -0
- package/templates/be/patterns/saga/saga.complete.contracts.ts.tpl +7 -0
- package/templates/be/patterns/saga/saga.complete.handler.ts.tpl +22 -0
- package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +21 -0
- package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +21 -0
- package/templates/be/patterns/saga/saga.index.ts.tpl +1 -0
- package/templates/be/patterns/saga/saga.message.module.ts.tpl +23 -0
- package/templates/be/patterns/saga/saga.module.ts.tpl +20 -0
- package/templates/be/patterns/saga/saga.service.spec.ts.tpl +70 -0
- package/templates/be/patterns/saga/saga.service.ts.tpl +44 -0
- package/templates/be/patterns/saga/saga.state.ts.tpl +14 -0
- package/templates/be/patterns/saga/saga.step.ts.tpl +11 -0
- package/templates/be/patterns/saga/saga.undo.command.ts.tpl +10 -0
- package/templates/be/patterns/saga/saga.undo.contracts.ts.tpl +5 -0
- package/templates/be/patterns/saga/saga.undo.handler.ts.tpl +21 -0
- package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +72 -0
- package/templates/be/patterns/webhooks/webhook.door.ts.tpl +36 -0
- package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +6 -0
- package/templates/be/patterns/webhooks/webhook.index.ts.tpl +1 -0
- package/templates/be/patterns/webhooks/webhook.request.ts.tpl +9 -0
- package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +3 -0
- package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -2
- package/templates/be/skeleton/apps/__app__/src/main.ts +7 -15
- package/templates/be/skeleton/apps/cli/src/app.module.ts +30 -0
- package/templates/be/skeleton/apps/cli/src/cli.options.ts +22 -0
- package/templates/be/skeleton/apps/cli/src/main.ts +24 -0
- package/templates/be/skeleton/src/features/cli/cli.module.ts +7 -0
- package/templates/be/skeleton/src/features/cli/index.ts +1 -0
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +17 -0
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +15 -0
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +7 -0
- package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.spec.ts +17 -0
- package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.ts +18 -0
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +17 -0
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +15 -0
- package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +7 -0
- package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +33 -0
- package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.ts +34 -0
- package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +13 -0
- package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +40 -0
- package/templates/be/skeleton/src/modules/domain/identity/messages/identity.messages.ts +1 -1
- package/templates/be/skeleton/src/modules/domain/note/errors/note.error.ts +16 -0
- package/templates/be/skeleton/src/modules/domain/note/index.ts +3 -0
- package/templates/be/skeleton/src/modules/domain/note/note.contracts.ts +19 -0
- package/templates/be/skeleton/src/modules/domain/note/note.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/domain/note/note.module.ts +19 -0
- package/templates/be/skeleton/src/modules/domain/note/note.options.ts +2 -0
- package/templates/be/skeleton/src/modules/domain/note/note.service.spec.ts +76 -0
- package/templates/be/skeleton/src/modules/domain/note/note.service.ts +37 -0
- package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +8 -0
- package/templates/be/skeleton/src/modules/domain/note/persistence/entities/note.entity.ts +17 -0
- package/templates/be/skeleton/src/modules/domain/note/persistence/migrations/1790000000000-init-note.ts +22 -0
- package/templates/be/skeleton/src/modules/domain/note/persistence/note.rows.ts +17 -0
- package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +4 -0
- package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +20 -0
- package/templates/be/skeleton/src/modules/platform/config/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +18 -0
- package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +41 -0
- package/templates/be/skeleton/src/modules/platform/database/connection-source.client.ts +17 -0
- package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +13 -0
- package/templates/be/skeleton/src/modules/platform/database/database.module.ts +77 -0
- package/templates/be/skeleton/src/modules/platform/database/database.options.ts +30 -0
- package/templates/be/skeleton/src/modules/platform/database/database.port.ts +65 -0
- package/templates/be/skeleton/src/modules/platform/database/database.sql.ts +47 -0
- package/templates/be/skeleton/src/modules/platform/database/errors/database.error.ts +16 -0
- package/templates/be/skeleton/src/modules/platform/database/index.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +30 -0
- package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.spec.ts +84 -0
- package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.ts +23 -0
- package/templates/be/skeleton/src/modules/platform/database/primary.config.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/database/primary.connection.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +10 -0
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +108 -0
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +75 -0
- package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.spec.ts +88 -0
- package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.ts +29 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +48 -0
- package/templates/be/skeleton/src/modules/platform/errors/messages/errors.messages.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +12 -0
- package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -0
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +8 -1
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.decorators.ts +8 -0
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +14 -1
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +12 -0
- package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +10 -2
- package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.spec.ts +63 -0
- package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +108 -0
- package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.spec.ts +39 -0
- package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.ts +25 -0
- package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +172 -0
- package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.ts +66 -0
- package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +3 -1
- package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +52 -0
- package/templates/be/skeleton-lite/apps/api/Dockerfile +37 -0
- package/templates/be/skeleton-lite/apps/api/src/api.options.ts +16 -0
- package/templates/be/skeleton-lite/apps/api/src/app.module.ts +81 -0
- package/templates/be/skeleton-lite/apps/api/src/main.ts +34 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +14 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +50 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +26 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +19 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +26 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +7 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +6 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +19 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +10 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +24 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +8 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +17 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +32 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +24 -0
- package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +54 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +23 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +10 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +49 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +21 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +11 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +28 -0
- package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +25 -0
- package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +9 -0
- package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +5 -0
- package/templates/be/table/error.ts.tpl +16 -0
- package/templates/be/table/index.ts.tpl +2 -0
- package/templates/be/table/migration.sql.tpl +51 -0
- package/templates/be/table/module-definition.ts.tpl +7 -0
- package/templates/be/table/module.ts.tpl +14 -0
- package/templates/be/table/options.ts.tpl +2 -0
- package/templates/be/table/rows.ts.tpl +4 -0
- package/templates/be/table/service.ts.tpl +23 -0
- package/templates/be/table/sql.ts.tpl +4 -0
- package/templates/be/tool-config/jest.config.js +3 -1
- package/templates/be/upgrade-full/apps/cli/src/app.module.ts +14 -0
- package/templates/be/upgrade-full/apps/cli/src/main.ts +26 -0
- package/templates/be/upgrade-full/src/features/cli/cli.module.ts +6 -0
- package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +23 -0
- package/templates/common/gitignore.base +1 -2
- package/templates/fe/app/package.json.tpl +1 -0
- package/templates/fe/app/tsconfig.json +12 -0
- package/templates/fe/common/app-layout.tsx.partial +8 -0
- package/templates/fe/common/site-shell.tsx.partial +9 -0
- package/templates/fe/image/next/Dockerfile +42 -0
- package/templates/fe/skeleton/apps/app/next.config.ts +23 -0
- package/templates/fe/skeleton/apps/app/postcss.config.mjs +7 -0
- package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/error.tsx +2 -2
- package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +22 -0
- package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/loading.tsx +2 -2
- package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/not-found.tsx +2 -2
- package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +9 -0
- package/templates/fe/skeleton/apps/{__app__ → app}/src/app/global-error.tsx +2 -2
- package/templates/fe/skeleton/apps/app/src/app/globals.css +10 -0
- package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +18 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/component.tsx +19 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/index.tsx +11 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppGlobalErrorPage/index.tsx +16 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/component.tsx +10 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +15 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +8 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +9 -0
- package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +4 -0
- package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +28 -0
- package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -0
- package/templates/fe/skeleton/apps/app/src/modules/routes/index.ts +4 -0
- package/templates/fe/skeleton/apps/app/src/proxy.ts +1 -0
- package/templates/fe/skeleton/apps/{__app__ → landing}/next.config.ts +4 -2
- package/templates/fe/skeleton/apps/landing/postcss.config.mjs +7 -0
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/error.tsx +10 -0
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +22 -0
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/loading.tsx +6 -0
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/not-found.tsx +6 -0
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +9 -0
- package/templates/fe/skeleton/apps/landing/src/app/global-error.tsx +10 -0
- package/templates/fe/skeleton/apps/landing/src/app/globals.css +10 -0
- package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +24 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/component.tsx +18 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/index.tsx +11 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingGlobalErrorPage/index.tsx +16 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +8 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +9 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/component.tsx +24 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +16 -0
- package/templates/fe/skeleton/apps/landing/src/modules/config/index.ts +12 -0
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +7 -0
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/vi.json +31 -0
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -0
- package/templates/fe/skeleton/apps/landing/src/modules/routes/index.ts +4 -0
- package/templates/fe/skeleton/apps/landing/src/proxy.ts +1 -0
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +49 -0
- package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +24 -0
- package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +20 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/branches/Providers/index.tsx +23 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/ErrorNotice/index.tsx +14 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/GlobalErrorNotice/index.tsx +25 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/LoadingNotice/index.tsx +9 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/LocaleShell/index.tsx +21 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +19 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/classNames.ts +4 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/index.tsx +19 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +6 -0
- package/templates/fe/skeleton-lite/apps/web/next.config.ts +22 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/error.tsx +10 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +37 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/loading.tsx +6 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/not-found.tsx +6 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/providers.tsx +26 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/auth/callback/route.ts +24 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/global-error.tsx +10 -0
- package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/src/app/globals.css +1 -1
- package/templates/fe/skeleton-lite/apps/web/src/app/health/live/route.ts +5 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/component.tsx +29 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +41 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/component.tsx +17 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/index.tsx +38 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/ErrorNotice/index.tsx +13 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/GlobalErrorNotice/index.tsx +24 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/LoadingNotice/index.tsx +7 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +18 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/classNames.ts +4 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/index.tsx +13 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +31 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/component.tsx +12 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/index.tsx +10 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppGlobalErrorPage/index.tsx +15 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx +14 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +30 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +8 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/brand/brand.css +20 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/brand/index.ts +2 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +20 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/read-session.ts +25 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-in.ts +25 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +17 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/browser.ts +14 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/index.ts +3 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/outcome.ts +59 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/principal.ts +29 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/server.ts +70 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +50 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/index.ts +8 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +48 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +16 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +14 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/routes/index.ts +7 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/routes/safe-next-path.ts +26 -0
- package/templates/fe/skeleton-lite/apps/web/src/proxy.ts +43 -0
- package/templates/fe/table/read.ts.tpl +16 -0
- package/templates/fe/table/write.ts.tpl +16 -0
- package/upgrade/check.mjs +137 -0
- package/upgrade/index.mjs +524 -0
- package/bin/hfs.mjs +0 -233
- package/runtime/scripts/api/fs/rmdir-link.mjs +0 -12
- package/runtime/scripts/lib/test-secrets.mjs +0 -116
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/layout.tsx +0 -20
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/page.tsx +0 -9
- package/templates/fe/skeleton/apps/__app__/src/components/composites/FailureScreen/index.tsx +0 -28
- package/templates/fe/skeleton/apps/__app__/src/features/layouts/LocaleShell/index.tsx +0 -35
- package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/component.tsx +0 -19
- package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/index.tsx +0 -11
- package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/component.tsx +0 -24
- package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/index.tsx +0 -14
- package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/component.tsx +0 -21
- package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/index.tsx +0 -15
- package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/component.tsx +0 -23
- package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/index.tsx +0 -8
- package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/component.tsx +0 -28
- package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/index.tsx +0 -8
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/config.ts +0 -8
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/index.ts +0 -2
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages/vi.json +0 -25
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/request.ts +0 -19
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/routing.ts +0 -9
- package/templates/fe/skeleton/apps/__app__/src/modules/routes/index.ts +0 -4
- package/templates/fe/skeleton/apps/__app__/src/proxy.ts +0 -10
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.contracts.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.handler.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.query.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/index.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/system-health.module.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/live.controller.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/system-health-http.module.ts +0 -0
- /package/templates/fe/skeleton/apps/{__app__ → app}/src/app/health/live/route.ts +0 -0
- /package/templates/fe/skeleton/apps/{__app__ → app}/src/modules/config/index.ts +0 -0
- /package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/postcss.config.mjs +0 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// dockerfile.mjs - a Dockerfile read by its own grammar (no regular-expression matching of instruction names inside the text):
|
|
2
|
+
// one statement per logical line (a trailing backslash continues it, comment lines inside a continuation are dropped), the
|
|
3
|
+
// instruction keyword is the first word, the stages open at each FROM. Parser directives (`# syntax=`, `# escape=`) and
|
|
4
|
+
// comments are kept apart from the instructions. Here-documents are not part of the subset the canon uses and are read as text.
|
|
5
|
+
|
|
6
|
+
/** Split a shell-form or exec-form argument string into its words (quotes group a word and are removed). */
|
|
7
|
+
export function words(text) {
|
|
8
|
+
const out = [];
|
|
9
|
+
let current = '';
|
|
10
|
+
let quote = null;
|
|
11
|
+
let started = false;
|
|
12
|
+
for (const char of text) {
|
|
13
|
+
if (quote) {
|
|
14
|
+
if (char === quote) quote = null;
|
|
15
|
+
else current += char;
|
|
16
|
+
} else if (char === '"' || char === "'") {
|
|
17
|
+
quote = char;
|
|
18
|
+
started = true;
|
|
19
|
+
} else if (char === ' ' || char === '\t' || char === '\n') {
|
|
20
|
+
if (started || current) out.push(current);
|
|
21
|
+
current = '';
|
|
22
|
+
started = false;
|
|
23
|
+
} else {
|
|
24
|
+
current += char;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (started || current) out.push(current);
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The exec-form array of an instruction's argument text (`["node", "x.js"]`), or null when it is not a JSON array of strings. */
|
|
32
|
+
export function execForm(text) {
|
|
33
|
+
const trimmed = text.trim();
|
|
34
|
+
if (!trimmed.startsWith('[')) return null;
|
|
35
|
+
try {
|
|
36
|
+
const value = JSON.parse(trimmed);
|
|
37
|
+
return Array.isArray(value) && value.every((item) => typeof item === 'string') ? value : null;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The commands of a shell-form RUN body: the text split at `&&`, `||`, `;` and `|` outside quotes, each as its words. */
|
|
44
|
+
export function shellCommands(body) {
|
|
45
|
+
const commands = [];
|
|
46
|
+
let current = '';
|
|
47
|
+
let quote = null;
|
|
48
|
+
const flush = () => {
|
|
49
|
+
const parts = words(current);
|
|
50
|
+
if (parts.length) commands.push(parts);
|
|
51
|
+
current = '';
|
|
52
|
+
};
|
|
53
|
+
for (let i = 0; i < body.length; i += 1) {
|
|
54
|
+
const char = body[i];
|
|
55
|
+
if (quote) {
|
|
56
|
+
current += char;
|
|
57
|
+
if (char === quote) quote = null;
|
|
58
|
+
} else if (char === '"' || char === "'") {
|
|
59
|
+
quote = char;
|
|
60
|
+
current += char;
|
|
61
|
+
} else if (char === ';' || char === '|' || (char === '&' && body[i + 1] === '&')) {
|
|
62
|
+
if (char !== ';' && body[i + 1] === char) i += 1;
|
|
63
|
+
flush();
|
|
64
|
+
} else if (char === '\\' && body[i + 1] === '\n') {
|
|
65
|
+
i += 1;
|
|
66
|
+
} else {
|
|
67
|
+
current += char;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
flush();
|
|
71
|
+
return commands;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Parse a Dockerfile: { instructions: [{ keyword, text, line, stage }], stages: [{ index, name, image, line, instructions }],
|
|
76
|
+
* comments: [{ text, line }], preamble: [instruction before the first FROM (ARG)] }. `keyword` is upper-case; `text` is the
|
|
77
|
+
* argument text of the logical line; `stage` is the index of the stage the instruction belongs to (-1 before the first FROM).
|
|
78
|
+
*/
|
|
79
|
+
export function parseDockerfile(source) {
|
|
80
|
+
const lines = String(source).replace(/\r\n/g, '\n').split('\n');
|
|
81
|
+
const instructions = [];
|
|
82
|
+
const comments = [];
|
|
83
|
+
let escape = '\\';
|
|
84
|
+
let directives = true;
|
|
85
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
86
|
+
const raw = lines[i];
|
|
87
|
+
const trimmed = raw.trim();
|
|
88
|
+
if (trimmed === '') { directives = false; continue; }
|
|
89
|
+
if (trimmed.startsWith('#')) {
|
|
90
|
+
const body = trimmed.slice(1).trim();
|
|
91
|
+
const directive = directives ? /^(syntax|escape|check)\s*=\s*(\S+)$/i.exec(body) : null;
|
|
92
|
+
if (directive) { if (directive[1].toLowerCase() === 'escape') escape = directive[2]; continue; }
|
|
93
|
+
directives = false;
|
|
94
|
+
comments.push({ text: body, line: i + 1 });
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
directives = false;
|
|
98
|
+
const start = i + 1;
|
|
99
|
+
let logical = trimmed;
|
|
100
|
+
while (logical.endsWith(escape) && i + 1 < lines.length) {
|
|
101
|
+
logical = logical.slice(0, -1).trimEnd();
|
|
102
|
+
i += 1;
|
|
103
|
+
while (i < lines.length && lines[i].trim().startsWith('#')) i += 1;
|
|
104
|
+
if (i >= lines.length) break;
|
|
105
|
+
logical = `${logical} ${lines[i].trim()}`;
|
|
106
|
+
}
|
|
107
|
+
const split = logical.search(/\s/);
|
|
108
|
+
const keyword = (split < 0 ? logical : logical.slice(0, split)).toUpperCase();
|
|
109
|
+
instructions.push({ keyword, text: split < 0 ? '' : logical.slice(split).trim(), line: start, stage: -1 });
|
|
110
|
+
}
|
|
111
|
+
const stages = [];
|
|
112
|
+
const preamble = [];
|
|
113
|
+
for (const instruction of instructions) {
|
|
114
|
+
if (instruction.keyword === 'FROM') {
|
|
115
|
+
const parts = words(instruction.text).filter((part) => !part.startsWith('--'));
|
|
116
|
+
const asAt = parts.findIndex((part, index) => index > 0 && part.toUpperCase() === 'AS');
|
|
117
|
+
stages.push({ index: stages.length, name: asAt > 0 ? parts[asAt + 1] ?? null : null, image: parts[0] ?? '', line: instruction.line, instructions: [] });
|
|
118
|
+
}
|
|
119
|
+
instruction.stage = stages.length - 1;
|
|
120
|
+
if (stages.length) stages.at(-1).instructions.push(instruction);
|
|
121
|
+
else preamble.push(instruction);
|
|
122
|
+
}
|
|
123
|
+
return { instructions, stages, comments, preamble };
|
|
124
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// env.mjs - the one place the runtime reads a named environment variable (smell S8-01/S8-02/S11-04; scripts/checks/check-env.mjs).
|
|
2
|
+
// modules/schemas/env.yaml catalogues every variable with its purpose and kind. Production code reads a variable here
|
|
3
|
+
// (readEnv) or takes an injected `env` parameter; it never reads `process.env.NAME` itself. The test runner is observed
|
|
4
|
+
// in exactly one place, isSpecRun: code that must not touch the host's real state during a spec asks it.
|
|
5
|
+
|
|
6
|
+
/** The value of the environment variable `name` in `env` (default: the process environment), or undefined. */
|
|
7
|
+
export const readEnv = (name, env = process.env) => env[name];
|
|
8
|
+
|
|
9
|
+
/** True when `env` (default: the process environment) belongs to a spec run (`node --test` marks its children with NODE_TEST_CONTEXT). */
|
|
10
|
+
export const isSpecRun = (env = process.env) => Boolean(env.NODE_TEST_CONTEXT);
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// event-contract.mjs - the async contract between the services of one product (R166 HFS_EVENT_CONTRACT).
|
|
2
|
+
// The one source of the contract is the typed event classes of a service, `be/src/modules/events/<service>/<event>.event.ts`:
|
|
3
|
+
// export interface OrderPlacedPayload { readonly orderId: string; readonly totalMinorUnits: number }
|
|
4
|
+
// export class OrderPlacedEvent extends BaseEvent {
|
|
5
|
+
// static readonly eventName = "order.placed"; static readonly version = 1
|
|
6
|
+
// static readonly compensates = "order.placed" // only an event that reports the failure of a step
|
|
7
|
+
// static create(payload: OrderPlacedPayload): OrderPlacedEvent { ... }
|
|
8
|
+
// }
|
|
9
|
+
// `starci app emit` writes `be/contracts/<service>/events.json` from them (name, version, compensates, and the payload fields of the
|
|
10
|
+
// interface `create` takes); `starci app check` compares the committed file with the same reader. Nothing is executed: both walk the TypeScript
|
|
11
|
+
// syntax tree. The functions take the `ts` module the caller loaded (the repository's own, else the runtime's); this file imports nothing.
|
|
12
|
+
|
|
13
|
+
const EVENT_CONTRACT_SCHEMA = 'starci/event-contract@1';
|
|
14
|
+
const FIELD_TYPE = /^(?:string|number|boolean|string\[\]|number\[\])\??$/;
|
|
15
|
+
|
|
16
|
+
const hasStatic = (ts, member) => member.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword) === true;
|
|
17
|
+
|
|
18
|
+
/** The literal of a `static readonly <name> = <literal>` member: `{ text }` for a string, `{ number }` for an integer, or null. */
|
|
19
|
+
function staticLiteral(ts, member) {
|
|
20
|
+
if (!ts.isPropertyDeclaration(member) || !hasStatic(ts, member) || !ts.isIdentifier(member.name) || member.initializer === undefined) return null;
|
|
21
|
+
let value = member.initializer;
|
|
22
|
+
while (ts.isAsExpression(value) || ts.isParenthesizedExpression(value)) value = value.expression;
|
|
23
|
+
if (ts.isStringLiteralLike(value)) return { name: member.name.text, text: value.text };
|
|
24
|
+
if (ts.isNumericLiteral(value) && Number.isInteger(Number(value.text))) return { name: member.name.text, number: Number(value.text) };
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The text of a type node when it is one of the wire field types (`string`, `number`, `boolean`, `string[]`, `number[]`), else null. */
|
|
29
|
+
function fieldType(ts, node) {
|
|
30
|
+
if (node.kind === ts.SyntaxKind.StringKeyword) return 'string';
|
|
31
|
+
if (node.kind === ts.SyntaxKind.NumberKeyword) return 'number';
|
|
32
|
+
if (node.kind === ts.SyntaxKind.BooleanKeyword) return 'boolean';
|
|
33
|
+
if (ts.isArrayTypeNode(node)) {
|
|
34
|
+
const element = fieldType(ts, node.elementType);
|
|
35
|
+
return element === 'string' || element === 'number' ? `${element}[]` : null;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The payload fields of the interface or type literal named `typeName` in the file: `{ <field>: <type> }`, optional fields with a `?`. */
|
|
41
|
+
function payloadOf(ts, sourceFile, typeName, problems, where) {
|
|
42
|
+
let members = null;
|
|
43
|
+
for (const statement of sourceFile.statements) {
|
|
44
|
+
if (ts.isInterfaceDeclaration(statement) && statement.name.text === typeName) members = statement.members;
|
|
45
|
+
if (ts.isTypeAliasDeclaration(statement) && statement.name.text === typeName && ts.isTypeLiteralNode(statement.type)) members = statement.type.members;
|
|
46
|
+
}
|
|
47
|
+
if (members === null) {
|
|
48
|
+
problems.push(`${where}: the payload type ${typeName} is not an interface or type literal of the file`);
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
const payload = {};
|
|
52
|
+
for (const member of members) {
|
|
53
|
+
if (!ts.isPropertySignature(member) || !ts.isIdentifier(member.name) || member.type === undefined) {
|
|
54
|
+
problems.push(`${where}: the payload ${typeName} holds a member that is not \`name: type\``);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const type = fieldType(ts, member.type);
|
|
58
|
+
const written = type === null ? null : `${type}${member.questionToken ? '?' : ''}`;
|
|
59
|
+
if (written === null || !FIELD_TYPE.test(written)) problems.push(`${where}: payload field ${member.name.text} must be string, number, boolean, string[] or number[]`);
|
|
60
|
+
else payload[member.name.text] = written;
|
|
61
|
+
}
|
|
62
|
+
return payload;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The event classes of one source text: `{ events: [{ className, name, version, compensates, payload }], problems: [text] }`. A class is an event
|
|
67
|
+
* when it declares `static readonly eventName`; `version` is required, `create(payload: <Interface>)` names the payload.
|
|
68
|
+
*/
|
|
69
|
+
export function readEventClasses(ts, text, file = 'event.ts') {
|
|
70
|
+
const sourceFile = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
71
|
+
const events = [];
|
|
72
|
+
const problems = [];
|
|
73
|
+
for (const statement of sourceFile.statements) {
|
|
74
|
+
if (!ts.isClassDeclaration(statement) || statement.name === undefined) continue;
|
|
75
|
+
const literals = statement.members.map((member) => staticLiteral(ts, member)).filter((literal) => literal !== null);
|
|
76
|
+
const name = literals.find((literal) => literal.name === 'eventName')?.text;
|
|
77
|
+
if (name === undefined) continue;
|
|
78
|
+
const className = statement.name.text;
|
|
79
|
+
const version = literals.find((literal) => literal.name === 'version')?.number;
|
|
80
|
+
const compensates = literals.find((literal) => literal.name === 'compensates')?.text;
|
|
81
|
+
if (version === undefined || version < 1) problems.push(`${className} needs \`static readonly version\` as a positive integer`);
|
|
82
|
+
const create = statement.members.find((member) => ts.isMethodDeclaration(member) && hasStatic(ts, member) && ts.isIdentifier(member.name) && member.name.text === 'create');
|
|
83
|
+
const parameter = create?.parameters[0]?.type;
|
|
84
|
+
if (parameter === undefined || !ts.isTypeReferenceNode(parameter) || !ts.isIdentifier(parameter.typeName)) {
|
|
85
|
+
problems.push(`${className} needs \`static create(payload: <Interface>)\` naming its payload interface`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
events.push({ className, name, version: version ?? 0, ...(compensates === undefined ? {} : { compensates }), payload: payloadOf(ts, sourceFile, parameter.typeName.text, problems, className) });
|
|
89
|
+
}
|
|
90
|
+
return { events, problems };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The sorted object: keys in code-point order, recursively (what the snapshot prints). */
|
|
94
|
+
const sorted = (value) => (value !== null && typeof value === 'object' && !Array.isArray(value) ? Object.fromEntries(Object.keys(value).sort().map((key) => [key, sorted(value[key])])) : value);
|
|
95
|
+
|
|
96
|
+
/** The text of the snapshot of a service from its event classes: 2-space JSON, sorted keys, one final newline. */
|
|
97
|
+
export function snapshotText(service, events) {
|
|
98
|
+
const table = Object.fromEntries(events.map(({ name, version, compensates, payload }) => [name, { version, payload, ...(compensates === undefined ? {} : { compensates }) }]));
|
|
99
|
+
return `${JSON.stringify(sorted({ schema: EVENT_CONTRACT_SCHEMA, service, events: table }), null, 2)}\n`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The snapshot's text with line endings folded, for comparing a committed file to a fresh one. */
|
|
103
|
+
export const folded = (text) => text.replace(/\r\n/g, '\n');
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// git.mjs — pure helpers over git's text output, spawn results and env. The git spawn itself lives in scripts/api/git/ (lib.mjs and its call files).
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* git's C-quoted diff path decoded - `"b\303\251"` a `+++ ` header line prints when core.quotePath
|
|
5
|
+
* covers the name. Octal escapes become \u00XX for JSON.parse (which answers \n, \t, \" and \\);
|
|
6
|
+
* a value not wrapped in quotes passes through unchanged.
|
|
7
|
+
*/
|
|
8
|
+
export const unquoteDiffPath = (value) =>
|
|
9
|
+
/^".*"$/.test(value)
|
|
10
|
+
? JSON.parse(value.replace(/\\([0-7]{3})/g, (_, octal) => `\\u00${Number.parseInt(octal, 8).toString(16).padStart(2, '0')}`))
|
|
11
|
+
: value;
|
|
12
|
+
|
|
13
|
+
// git's repository-local variables (git rev-parse --local-env-vars). A hook or alias run in a linked worktree exports
|
|
14
|
+
// GIT_DIR=<main>/.git/worktrees/<wt>; a spec inheriting it pointed every fixture git at the LIVE .claude repo and re-inited
|
|
15
|
+
// it core.bare=true (2026-09-29, a6f60352c). tests/setup/isolated-registry.mjs drops the same list.
|
|
16
|
+
export const GIT_LOCAL_ENV_VARS = ['GIT_DIR', 'GIT_COMMON_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_OBJECT_DIRECTORY', 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_IMPLICIT_WORK_TREE', 'GIT_PREFIX', 'GIT_CONFIG', 'GIT_CONFIG_PARAMETERS', 'GIT_CONFIG_COUNT', 'GIT_GRAFT_FILE', 'GIT_NO_REPLACE_OBJECTS', 'GIT_REPLACE_REF_BASE', 'GIT_SHALLOW_FILE'];
|
|
17
|
+
/** `parent` (the caller's env) without git's repository-local variables (GIT_CONFIG_KEY_n/VALUE_n go with GIT_CONFIG_COUNT). Pure. */
|
|
18
|
+
export function withoutGitLocalEnv(parent) {
|
|
19
|
+
const env = { ...parent };
|
|
20
|
+
for (const key of Object.keys(env)) if (GIT_LOCAL_ENV_VARS.includes(key) || /^GIT_CONFIG_(KEY|VALUE)_\d+$/.test(key)) delete env[key];
|
|
21
|
+
return env;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A git spawn result (a scripts/api/git call file's) as {ok, stdout, error}: ok only on a clean exit, error from stderr or the spawn. Pure. */
|
|
25
|
+
export const gitResultOf = (r) => ({
|
|
26
|
+
ok: !r.error && r.status === 0,
|
|
27
|
+
stdout: r.stdout ?? '',
|
|
28
|
+
error: (r.stderr ?? '').trim() || String(r.error?.message ?? (r.status == null ? 'timed out' : `exit ${r.status}`)),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/** A git spawn result's stdout text; throws the spawn error, or an Error naming `what` with git's stderr and exit status, unless git exited 0. Pure. */
|
|
32
|
+
export function gitOutputOf(r, what = 'git') {
|
|
33
|
+
if (r.error) throw r.error;
|
|
34
|
+
if (r.status !== 0) throw Object.assign(new Error(`${what} exited ${r.status}: ${(r.stderr ?? '').trim()}`), { status: r.status, stderr: r.stderr });
|
|
35
|
+
return r.stdout;
|
|
36
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// graphql-contract.mjs - the GraphQL a back-end contract snapshot holds and the checks a front-end document must pass
|
|
2
|
-
// against it. It reads the SDL `
|
|
2
|
+
// against it. It reads the SDL `starci app emit` writes (`be/contracts/<service>/schema.graphql`: object, input, enum,
|
|
3
3
|
// scalar, interface and union types, descriptions and directives) and the executable documents a front end keeps in
|
|
4
4
|
// `.graphql` files (operations with variables, field arguments, object literals, inline fragments), with no dependency:
|
|
5
5
|
// the runtime ships no GraphQL library, and an app's own install is not always resolvable from where hfs runs.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// i18n.mjs - the ONE mechanism for text the owner must read in Vietnamese. Runtime source, comments and messages are English;
|
|
2
|
+
// a message the owner reads (a Telegram notice, a why line, a progress report) is written once in English at its call site and
|
|
3
|
+
// translated through a DECLARED catalog keyed from that English source: modules/i18n/messages/<area>.yaml, a list of
|
|
4
|
+
// `{en, vi}` entries. Each area belongs to a scope: `runtime` for Node callers or `ui` for the generated browser catalog.
|
|
5
|
+
// The `vi` field is declared in scripts/lib/language.mjs, so it is the only Vietnamese source. There is no per-module
|
|
6
|
+
// `TEXT.vi` table and no second catalog mechanism.
|
|
7
|
+
//
|
|
8
|
+
// const tr = translator(config.language); // 'vi' translates, anything else returns the English source
|
|
9
|
+
// tr('Workflow {id} needs your decision', { id })
|
|
10
|
+
//
|
|
11
|
+
// A placeholder is `{name}`; the English and the Vietnamese of one entry carry exactly the same placeholders
|
|
12
|
+
// (scripts/checks/check-i18n-catalog.mjs). An English source with no entry is returned as is: a missing translation degrades
|
|
13
|
+
// to English, never to a blank.
|
|
14
|
+
import { escapeRegExp } from './regex.mjs';
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { skillRoot } from '../../engine/runtime-root.mjs';
|
|
18
|
+
import { parseYaml } from '../../engine/yaml.mjs';
|
|
19
|
+
import { DEFAULT_OWNER_LANGUAGE, loadConfig } from '../../engine/config.mjs';
|
|
20
|
+
|
|
21
|
+
export const CATALOG_DIR = 'modules/i18n/messages';
|
|
22
|
+
export const PLACEHOLDER = /\{([A-Za-z_][\w]*)\}/g;
|
|
23
|
+
|
|
24
|
+
const cache = new Map();
|
|
25
|
+
|
|
26
|
+
/** The catalog files of a runtime root: repository-relative paths, sorted. */
|
|
27
|
+
export function catalogFiles(root = skillRoot) {
|
|
28
|
+
const dir = path.join(root, ...CATALOG_DIR.split('/'));
|
|
29
|
+
try { return fs.readdirSync(dir).filter((name) => /\.ya?ml$/.test(name)).sort().map((name) => `${CATALOG_DIR}/${name}`); } catch { return []; }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Every entry in one catalog scope: `[{ en, vi, area, scope, file }]`. */
|
|
33
|
+
export function catalogEntries(root = skillRoot, { scope = 'runtime' } = {}) {
|
|
34
|
+
return catalogFiles(root).flatMap((rel) => {
|
|
35
|
+
const doc = parseYaml(fs.readFileSync(path.join(root, ...rel.split('/')), 'utf8'));
|
|
36
|
+
const documentScope = typeof doc?.scope === 'string' ? doc.scope : 'runtime';
|
|
37
|
+
if (documentScope !== scope) return [];
|
|
38
|
+
return (Array.isArray(doc?.messages) ? doc.messages : []).map((entry) => ({
|
|
39
|
+
en: entry?.en, vi: entry?.vi, area: doc?.area, scope: documentScope, file: rel,
|
|
40
|
+
}));
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** One scoped catalog as a Map from the English source to the Vietnamese text (cached per root and scope). */
|
|
45
|
+
export function loadCatalog(root = skillRoot, { scope = 'runtime' } = {}) {
|
|
46
|
+
const key = `${root}\0${scope}`;
|
|
47
|
+
if (!cache.has(key)) cache.set(key, new Map(catalogEntries(root, { scope }).filter((e) => typeof e.en === 'string' && typeof e.vi === 'string').map((e) => [e.en, e.vi])));
|
|
48
|
+
return cache.get(key);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Forget the cached catalogs (a spec that rewrites a catalog file). */
|
|
52
|
+
export const resetCatalogCache = () => cache.clear();
|
|
53
|
+
|
|
54
|
+
/** The names each `pattern` match captures (group 1) in `text`, in match order (`sort` sorts them). */
|
|
55
|
+
export const captureNames = (text, pattern, { sort = false } = {}) => {
|
|
56
|
+
const names = [...String(text).matchAll(pattern)].map((m) => m[1]);
|
|
57
|
+
return sort ? names.sort() : names;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** The names of the placeholders of a message. */
|
|
61
|
+
export const placeholdersOf = (text) => captureNames(text, PLACEHOLDER, { sort: true });
|
|
62
|
+
|
|
63
|
+
/** `text` with each `{name}` replaced by vars[name] (an unknown name is left as written). */
|
|
64
|
+
export const fill = (text, vars = {}) => String(text).replace(PLACEHOLDER, (whole, name) => (Object.hasOwn(vars, name) ? String(vars[name]) : whole));
|
|
65
|
+
|
|
66
|
+
/** The message `en` in `language` (`vi` translates through the catalog; any other language is the English source). */
|
|
67
|
+
export function translate(en, vars = {}, { language = DEFAULT_OWNER_LANGUAGE, root = skillRoot, scope = 'runtime' } = {}) {
|
|
68
|
+
const vi = language === 'vi' ? loadCatalog(root, { scope }).get(en) : undefined;
|
|
69
|
+
return fill(vi ?? en, vars);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The owner's language (config.yaml `language`); `fallback` when there is no readable config (DEFAULT_OWNER_LANGUAGE of engine/config.mjs). */
|
|
73
|
+
export function ownerLanguage(fallback = DEFAULT_OWNER_LANGUAGE) {
|
|
74
|
+
try { return loadConfig()?.language ?? fallback; } catch { return fallback; }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** A translator bound to one language: `tr(en, vars)`. */
|
|
78
|
+
export const translator = (language = DEFAULT_OWNER_LANGUAGE, { root = skillRoot, scope = 'runtime' } = {}) =>
|
|
79
|
+
(en, vars = {}) => translate(en, vars, { language, root, scope });
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* A pattern source matching `en` in the owner's language or in English: each language's rendering of the English source,
|
|
83
|
+
* escaped, with the placeholder `slot` (a placeholder name of `en`) replaced by `slotPattern`. Never hardcodes a spelling.
|
|
84
|
+
*/
|
|
85
|
+
export const translatedPattern = (en, slot, slotPattern, languages = ['en', 'vi']) =>
|
|
86
|
+
`(?:${languages.map((language) => escapeRegExp(translate(en, { [slot]: '@@' }, { language })).replace('@@', slotPattern)).join('|')})`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// json.mjs — the ledger's forgiving JSON read. Every one of these rows may hold text a peer wrote or a
|
|
2
2
|
// truncated payload, so a malformed column is the caller's fallback, never a throw. The parse of the
|
|
3
3
|
// literal 'null' is kept as null (callers that want {} instead write `parseJsonOr`).
|
|
4
|
-
import
|
|
4
|
+
import { readParsedFile } from './read-text.mjs';
|
|
5
5
|
|
|
6
6
|
/** `text` as JSON, or `fallback` when it does not parse. */
|
|
7
7
|
export const parseJson = (text, fallback = null) => {
|
|
@@ -17,6 +17,16 @@ export const parseJsonOr = (text, fallback = {}) => parseJson(text) ?? fallback;
|
|
|
17
17
|
export const withPayload = (row, fallback = {}) => (row ? { ...row, payload: parseJsonOr(row.payload_json, fallback) } : row);
|
|
18
18
|
|
|
19
19
|
/** `file` read and parsed as JSON, or `fallback` when the file is missing, unreadable or malformed. */
|
|
20
|
-
export const readJsonFile = (file, fallback = null) =>
|
|
21
|
-
|
|
20
|
+
export const readJsonFile = (file, fallback = null) => readParsedFile(file, JSON.parse, fallback);
|
|
21
|
+
|
|
22
|
+
/** `stdout` as JSON: the trimmed text, else its first-{-to-last-} span; null when neither parses. */
|
|
23
|
+
export const jsonFromStdout = (stdout) => {
|
|
24
|
+
const text = String(stdout ?? '').trim();
|
|
25
|
+
if (!text) return null;
|
|
26
|
+
try { return JSON.parse(text); } catch { /* fall through */ }
|
|
27
|
+
const first = text.indexOf('{'), last = text.lastIndexOf('}');
|
|
28
|
+
if (first >= 0 && last > first) {
|
|
29
|
+
try { return JSON.parse(text.slice(first, last + 1)); } catch { /* not json */ }
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
22
32
|
};
|
|
@@ -39,6 +39,8 @@ export const FAILURE_CODE_VIETNAMESE_FIELDS = Object.freeze(['title_vi', 'meanin
|
|
|
39
39
|
* - modules/ops/_labels.yaml: the op-label catalogue, one `{ vi, en }` pair per op (the `vi` field is the localized label).
|
|
40
40
|
* - modules/goal/archetypes.yaml: the Vietnamese phrase lexicons matched against owner input (the signal phrase sets, and the
|
|
41
41
|
* phrase lists of the archetype recognisers).
|
|
42
|
+
* - modules/goal/source-phrases.yaml: the Vietnamese phrase lists runtime source matchers read through
|
|
43
|
+
* scripts/lib/source-phrases.mjs (owner replies, report wording, product copy) - each leaf list under `phrases`.
|
|
42
44
|
* An op manifest (modules/ops/ops/<id>.yaml, the starci/op@1 documents check-op-manifest types) is declared by
|
|
43
45
|
* OP_MANIFEST_VIETNAMESE_FIELDS: its text objects are {en, vi} (modules/schemas/op.schema.yaml $defs text), and `vi` is the
|
|
44
46
|
* owner's reading of the same rule.
|
|
@@ -52,13 +54,30 @@ export const DECLARED_VIETNAMESE_FIELDS = Object.freeze({
|
|
|
52
54
|
'phrases', 'requires', 'excludes', 'backend', 'frontend', 'package',
|
|
53
55
|
]),
|
|
54
56
|
}),
|
|
57
|
+
'modules/goal/source-phrases.yaml': Object.freeze({
|
|
58
|
+
fields: Object.freeze([
|
|
59
|
+
'prefer', 'avoid', 'negation', 'refused', 'accept', 'golden',
|
|
60
|
+
'missingPaths', 'grantTooNarrow', 'toolTimeout', 'testGap', 'checkerUnavailable',
|
|
61
|
+
'supervisorVerbs', 'ownerWord', 'orWord', 'beyondAuthority', 'ownerOnly', 'workerDied', 'contractConflict', 'decision',
|
|
62
|
+
'product', 'grammar', 'knowledge', 'sourceLabel', 'internalVocabulary', 'actor', 'success',
|
|
63
|
+
'wizard', 'form', 'dashboard', 'gap', 'anatomy', 'otpLabel',
|
|
64
|
+
]),
|
|
65
|
+
}),
|
|
55
66
|
});
|
|
56
67
|
|
|
68
|
+
/**
|
|
69
|
+
* The Vietnamese catalog of owner-visible messages (scripts/lib/i18n.mjs): modules/i18n/messages/<area>.yaml, a list of
|
|
70
|
+
* `{en, vi}` entries keyed from the English source. The `vi` field (block or inline flow) is the declared Vietnamese.
|
|
71
|
+
*/
|
|
72
|
+
const I18N_CATALOG_VIETNAMESE_FIELDS = Object.freeze({ fields: Object.freeze(['vi']), flowFields: Object.freeze(['vi']) });
|
|
73
|
+
const I18N_CATALOG_DIR = 'modules/i18n/messages/';
|
|
74
|
+
|
|
57
75
|
/** The `vi` field of an op manifest's text objects (modules/schemas/op.schema.yaml $defs text). */
|
|
58
|
-
|
|
76
|
+
const OP_MANIFEST_VIETNAMESE_FIELDS = Object.freeze({ fields: Object.freeze(['vi']) });
|
|
59
77
|
const OP_MANIFEST_DIR = 'modules/ops/ops/';
|
|
60
78
|
/** The declared Vietnamese fields of `rel`: its own entry, or the op-manifest text field for a manifest of modules/ops/ops. */
|
|
61
79
|
export const declaredVietnameseFieldsOf = (rel) => DECLARED_VIETNAMESE_FIELDS[rel]
|
|
80
|
+
?? (rel.startsWith(I18N_CATALOG_DIR) && !rel.slice(I18N_CATALOG_DIR.length).includes('/') && /\.ya?ml$/.test(rel) ? I18N_CATALOG_VIETNAMESE_FIELDS : null)
|
|
62
81
|
?? (rel.startsWith(OP_MANIFEST_DIR) && !rel.slice(OP_MANIFEST_DIR.length).includes('/') && /\.ya?ml$/.test(rel) ? OP_MANIFEST_VIETNAMESE_FIELDS : null);
|
|
63
82
|
|
|
64
83
|
/**
|
|
@@ -66,7 +85,7 @@ export const declaredVietnameseFieldsOf = (rel) => DECLARED_VIETNAMESE_FIELDS[re
|
|
|
66
85
|
* fixture reproduces a real localized string a parser or formatter must accept. Placement is the whole marker - there is no
|
|
67
86
|
* comment pragma and no path pattern. The slots are declared in knowledge/hfs/slots.yaml.
|
|
68
87
|
*/
|
|
69
|
-
|
|
88
|
+
const LOCALIZED_TEXT_SLOTS = Object.freeze([
|
|
70
89
|
'be.domain.messages', 'be.feature.messages', 'be.tests.fixtures.i18n',
|
|
71
90
|
'fe.modules.i18n', 'fe.package.i18n',
|
|
72
91
|
]);
|
|
@@ -78,7 +97,7 @@ export const isLocalizedDataFile = (rel, slot) => LOCALIZED_TEXT_SLOTS.includes(
|
|
|
78
97
|
* The key each line of a YAML text belongs to: a `key:` line owns itself, and every deeper-indented line after it (a list item,
|
|
79
98
|
* a folded block, a wrapped scalar) belongs to the same key. A line at or above the key's indent that is not a key belongs to none.
|
|
80
99
|
*/
|
|
81
|
-
|
|
100
|
+
function yamlKeyOfEachLine(text) {
|
|
82
101
|
let key = null;
|
|
83
102
|
let keyIndent = -1;
|
|
84
103
|
return String(text).split(/\r?\n/).map((raw) => {
|
|
@@ -105,13 +124,13 @@ export function documentLanguageHits(rel, text) {
|
|
|
105
124
|
if (declared.fields?.includes(keys[hit.line - 1])) return false;
|
|
106
125
|
if (!declared.flowFields) return true;
|
|
107
126
|
// an inline flow map: drop the declared fields' values and judge what is left of the line
|
|
108
|
-
const rest = declared.flowFields.reduce((line, field) => line.replace(new RegExp(`\\b${field}\\s*:\\s*[^,}]
|
|
127
|
+
const rest = declared.flowFields.reduce((line, field) => line.replace(new RegExp(`\\b${field}\\s*:\\s*(?:"(?:[^"\\\\]|\\\\.)*"|'(?:[^'\\\\]|\\\\.)*'|[^,}]*)`, 'g'), ''), lines[hit.line - 1]);
|
|
109
128
|
return hasSecondLanguage(rest);
|
|
110
129
|
});
|
|
111
130
|
}
|
|
112
131
|
|
|
113
132
|
/** The file extensions of a prose document (Markdown and YAML). */
|
|
114
|
-
|
|
133
|
+
const DOCUMENT_EXTENSIONS = Object.freeze(['.md', '.yaml', '.yml']);
|
|
115
134
|
|
|
116
135
|
/** Whether `rel` is a document a language check reads. */
|
|
117
136
|
export const isDocument = (rel) => DOCUMENT_EXTENSIONS.includes(path.posix.extname(rel));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// list.mjs — the array-or-empty coercion half the scripts re-declared by hand.
|
|
2
|
+
// `v` when it already is an array, else []. Scalars are NOT wrapped: a caller that wants
|
|
3
|
+
// `[v]` for a lone value writes its own coercion (see contract-version's list).
|
|
4
|
+
export const list = (v) => (Array.isArray(v) ? v : []);
|
|
5
|
+
|
|
6
|
+
/** `v` as an array: itself when already one, [] for null/undefined, else [v]. */
|
|
7
|
+
export const asList = (v) => (Array.isArray(v) ? v : v == null ? [] : [v]);
|
|
8
|
+
|
|
9
|
+
/** The object entries of `v` (a non-array reads as []). */
|
|
10
|
+
export const objectList = (v) => (Array.isArray(v) ? v : []).filter((item) => item && typeof item === 'object');
|
|
11
|
+
|
|
12
|
+
/** Every entry of `v` a non-empty string (`blank: false` also rejects all-whitespace entries). */
|
|
13
|
+
export const stringList = (v, { blank = true } = {}) =>
|
|
14
|
+
Array.isArray(v) && v.every((s) => typeof s === 'string' && s.length > 0 && (blank || s.trim().length > 0));
|
|
15
|
+
|
|
16
|
+
/** The items of a `sep`-separated list: trimmed, empties dropped; `dedupe` keeps first occurrences only. */
|
|
17
|
+
export const splitList = (v, { sep = ',', dedupe = false } = {}) => {
|
|
18
|
+
const items = String(v ?? '').split(sep).map((s) => s.trim()).filter(Boolean);
|
|
19
|
+
return dedupe ? [...new Set(items)] : items;
|
|
20
|
+
};
|
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
// scripts/lib/package-at.mjs — a package the runtime does not ship (playwright, esbuild, tailwindcss) comes from the
|
|
2
2
|
// project that owns it: node resolution from that project's directory, walking up its node_modules.
|
|
3
|
+
import fs from 'node:fs';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { createRequire } from 'node:module';
|
|
6
|
+
import { skillRoot } from '../../engine/runtime-root.mjs';
|
|
5
7
|
|
|
6
8
|
/** The package.json of `name` as resolved from `dir`, or null. */
|
|
7
9
|
export const packageAt = (dir, name) => {
|
|
8
10
|
try { return createRequire(path.join(dir, 'package.json')).resolve(`${name}/package.json`); } catch { return null; }
|
|
9
11
|
};
|
|
10
12
|
|
|
13
|
+
/** This runtime's own `starci` bin (packages/cli is the only package that declares one). */
|
|
14
|
+
const RUNTIME_STARCI_BIN = path.join(skillRoot, 'packages', 'cli', 'bin', 'starci.mjs');
|
|
15
|
+
|
|
16
|
+
/** The `starci` bin of the @starci/cli that `dir` installs (a product runs its own pinned CLI), else this runtime's own. */
|
|
17
|
+
export function starciBin(dir) {
|
|
18
|
+
const packageFile = packageAt(dir, '@starci/cli');
|
|
19
|
+
if (packageFile) {
|
|
20
|
+
try {
|
|
21
|
+
const { bin } = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
|
|
22
|
+
const rel = typeof bin === 'string' ? bin : bin?.starci;
|
|
23
|
+
const file = rel ? path.join(path.dirname(packageFile), rel) : null;
|
|
24
|
+
if (file && fs.existsSync(file)) return file;
|
|
25
|
+
} catch { /* an unreadable install falls back to the runtime's own CLI */ }
|
|
26
|
+
}
|
|
27
|
+
return RUNTIME_STARCI_BIN;
|
|
28
|
+
}
|
|
29
|
+
|
|
11
30
|
/** The first of `names` resolvable from the first of `dirs` that has one: {name, root, packageFile, version} or null. */
|
|
12
31
|
export function findPackage(dirs, names) {
|
|
13
32
|
for (const dir of dirs.filter(Boolean)) {
|
|
@@ -23,3 +42,21 @@ export function findPackage(dirs, names) {
|
|
|
23
42
|
|
|
24
43
|
/** The CommonJS entry of a package found by findPackage. */
|
|
25
44
|
export const requirePackage = (found) => createRequire(found.packageFile)(found.name);
|
|
45
|
+
|
|
46
|
+
/** The TypeScript compiler resolvable from the first of `dirs` (an app root, then the caller's own), or null. */
|
|
47
|
+
export const loadTypescript = (...dirs) => {
|
|
48
|
+
const located = findPackage(dirs, ['typescript']);
|
|
49
|
+
return located ? requirePackage(located) : null;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The app's command line and the app implementation behind it: `bin` the `starci` bin of the app's installed @starci/cli, else
|
|
54
|
+
* this runtime's packages/cli/bin/starci.mjs (starciBin); `dir` the @starci/hfs that answers
|
|
55
|
+
* `starci app` - the app's own install, else the one beside its @starci/cli, else this runtime's packages/hfs - whose runtime
|
|
56
|
+
* slice the gate's base lint (scripts/gates/gate.mjs) and the READ digest (scripts/gates/read-digest.mjs) load in-process.
|
|
57
|
+
*/
|
|
58
|
+
export function hfsEntry(root) {
|
|
59
|
+
const cli = packageAt(root, '@starci/cli');
|
|
60
|
+
const manifest = packageAt(root, '@starci/hfs') ?? (cli && packageAt(path.dirname(cli), '@starci/hfs'));
|
|
61
|
+
return { dir: manifest ? path.dirname(manifest) : path.join(skillRoot, 'packages', 'hfs'), bin: starciBin(root) };
|
|
62
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// path-key.mjs — spelling and comparing paths the way this host's filesystem does.
|
|
2
|
+
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
|
|
4
5
|
const WIN = process.platform === 'win32';
|
|
@@ -13,7 +14,61 @@ export const sameOrUnder = (file, prefix) => file === prefix || file.startsWith(
|
|
|
13
14
|
export const foldCase = (p) => (WIN ? p.toLowerCase() : p);
|
|
14
15
|
/** Whether two spellings name the same path on this host's filesystem. */
|
|
15
16
|
export const samePath = (a, b) => foldCase(a) === foldCase(b);
|
|
16
|
-
/**
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* A path's comparable identity: absolute, forward slashes, no trailing slash, case-folded on Windows.
|
|
19
|
+
* `fold: true` folds case on every platform (a comparison against a lowercase-stored root needs it).
|
|
20
|
+
*/
|
|
21
|
+
export const pathKey = (p, { fold = WIN } = {}) => {
|
|
22
|
+
const key = slash(path.resolve(p)).replace(/\/+$/, '');
|
|
23
|
+
return fold ? key.toLowerCase() : key;
|
|
24
|
+
};
|
|
25
|
+
/** `a` is `b` or under it, or the reverse: two path spellings whose trees touch. */
|
|
26
|
+
export const pathsOverlap = (a, b) => sameOrUnder(a, b) || sameOrUnder(b, a);
|
|
27
|
+
/**
|
|
28
|
+
* A path-ish string in compare form, options in spelling order:
|
|
29
|
+
* `trim` blanks at the ends, `collapse` folds '//' runs to '/', `dot` drops a leading './',
|
|
30
|
+
* `glob` strips a trailing glob ('star': '*'/'**' with their slash, 'double': a '/**' only),
|
|
31
|
+
* `fold` lowercases. A trailing '/' always goes. Null reads as ''.
|
|
32
|
+
*/
|
|
33
|
+
export const normPath = (p, { trim = false, collapse = false, dot = false, glob = null, fold = false } = {}) => {
|
|
34
|
+
let s = String(p ?? '');
|
|
35
|
+
if (trim) s = s.trim();
|
|
36
|
+
s = slash(s);
|
|
37
|
+
if (collapse) s = s.replace(/\/{2,}/g, '/');
|
|
38
|
+
if (dot) s = s.replace(/^\.\//, '');
|
|
39
|
+
if (glob === 'star') s = s.replace(/\/?\*+$/, '');
|
|
40
|
+
else if (glob === 'double') s = s.replace(/\/\*\*$/, '');
|
|
41
|
+
s = s.replace(/\/+$/, '');
|
|
42
|
+
return fold ? s.toLowerCase() : s;
|
|
43
|
+
};
|
|
18
44
|
/** A work-relative path in one spelling: forward slashes, no leading ./, no trailing slash. */
|
|
19
45
|
export const normWork = (p) => String(p ?? '').trim().replaceAll('\\', '/').replace(/^\.\/+/, '').replace(/\/+$/, '');
|
|
46
|
+
|
|
47
|
+
/** `p` resolved and dereferenced (realpath where the path exists) — the canonical spelling of a checkout dir. */
|
|
48
|
+
export const realPath = (p) => {
|
|
49
|
+
const resolved = path.resolve(p);
|
|
50
|
+
try { return fs.realpathSync.native(resolved); } catch { return resolved; }
|
|
51
|
+
};
|
|
52
|
+
/** `p` resolved and case-folded where the filesystem ignores case — the Map key of an absolute path. */
|
|
53
|
+
export const resolvedKey = (p) => foldCase(path.resolve(p));
|
|
54
|
+
/** Whether `a` and `b` resolve to the same path (always case-folded, so differently-cased spellings count). */
|
|
55
|
+
export const sameResolvedPath = (a, b) => pathKey(a, { fold: true }) === pathKey(b, { fold: true });
|
|
56
|
+
/**
|
|
57
|
+
* Whether `file` sits strictly under `root`. `key` normalizes both sides first (e.g. the registry's
|
|
58
|
+
* realpath-folded treeKey) when the callers' spellings need it.
|
|
59
|
+
*/
|
|
60
|
+
export const insidePath = (root, file, { key = (p) => p } = {}) => {
|
|
61
|
+
const rel = path.relative(key(root), key(file));
|
|
62
|
+
return Boolean(rel) && !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* A path-ish value (a string or a {path} entry) in relative compare form: trimmed, forward slashes,
|
|
66
|
+
* no trailing '/**' glob or slashes, no leading './'; `fold` lowercases for a case-insensitive key.
|
|
67
|
+
*/
|
|
68
|
+
export const normRel = (p, { fold = false } = {}) => {
|
|
69
|
+
const s = String(typeof p === 'string' ? p : p?.path ?? '').trim().replace(/\\/g, '/').replace(/\/\*\*$/, '').replace(/\/+$/, '').replace(/^\.\//, '');
|
|
70
|
+
return fold ? s.toLowerCase() : s;
|
|
71
|
+
};
|
|
72
|
+
/** Whether `file` equals or sits under one of `prefixes` (posix spellings; `dot` counts a '.' prefix as covering all). */
|
|
73
|
+
export const underAny = (file, prefixes, { dot = false } = {}) =>
|
|
74
|
+
(prefixes ?? []).some((p) => (dot && p === '.') || sameOrUnder(file, p));
|