@starci/hfs 4.0.7 → 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 +22 -0
- package/README.md +86 -43
- 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 +54 -20
- 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/engine/runtime-root.mjs +5 -0
- package/runtime/engine/yaml.mjs +3 -3
- package/runtime/knowledge/hfs/canon-pins.yaml +163 -32
- package/runtime/knowledge/hfs/peer-integrations.yaml +2 -2
- package/runtime/knowledge/hfs/rules.yaml +2059 -0
- package/runtime/knowledge/hfs/slots.yaml +915 -123
- 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 +133 -27
- package/runtime/knowledge/sonar-gate.yaml +53 -19
- package/runtime/modules/kernel/failure-codes.yaml +580 -50
- 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 +14 -0
- package/runtime/scripts/api/fs/safe-remove.mjs +104 -0
- 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 +17 -27
- 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 +10 -6
- package/runtime/scripts/api/sops/lib.mjs +35 -0
- package/runtime/scripts/api/sops/seal.mjs +18 -0
- package/runtime/scripts/{lib/hfs-allows.mjs → hfs/allows.mjs} +3 -3
- package/runtime/scripts/hfs/architecture/ast-walks.mjs +220 -0
- package/runtime/scripts/{checks → hfs}/architecture/backend.mjs +17 -24
- package/runtime/scripts/{checks → hfs}/architecture/background-unowned.mjs +16 -34
- package/runtime/scripts/{checks → hfs}/architecture/client-reaches-server.mjs +3 -1
- package/runtime/scripts/{checks → hfs}/architecture/clones.mjs +4 -4
- package/runtime/scripts/{checks → hfs}/architecture/config.mjs +11 -8
- package/runtime/scripts/{checks → hfs}/architecture/connection-map.mjs +21 -9
- 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/{checks → hfs}/architecture/contract-fixture-guard.mjs +5 -11
- package/runtime/scripts/{checks → hfs}/architecture/contracts.mjs +33 -162
- package/runtime/scripts/{checks → hfs}/architecture/dead-exports.mjs +58 -29
- package/runtime/scripts/{checks → hfs}/architecture/doc-language.mjs +1 -1
- package/runtime/scripts/{checks → hfs}/architecture/entrypoint.mjs +2 -1
- package/runtime/scripts/{checks → hfs}/architecture/fe-slot-allows.mjs +4 -4
- package/runtime/scripts/{checks → hfs}/architecture/feature-shape.mjs +6 -3
- package/runtime/scripts/{checks → hfs}/architecture/framework-pinned.mjs +1 -1
- package/runtime/scripts/{checks → hfs}/architecture/frontend.mjs +17 -43
- package/runtime/scripts/{checks → hfs}/architecture/hfs-graph.mjs +1 -1
- package/runtime/scripts/{checks → hfs}/architecture/hfs.mjs +27 -42
- package/runtime/scripts/{checks → hfs}/architecture/i18n-keys.mjs +1 -1
- package/runtime/scripts/{checks → hfs}/architecture/index.mjs +17 -4
- package/runtime/scripts/{checks → hfs}/architecture/machine-ast.mjs +44 -11
- package/runtime/scripts/hfs/architecture/managed-scripts.mjs +27 -0
- package/runtime/scripts/{checks → hfs}/architecture/module-per-transport.mjs +30 -6
- package/runtime/scripts/hfs/architecture/next-data-contract.mjs +96 -0
- package/runtime/scripts/{checks → hfs}/architecture/next-data.mjs +40 -168
- package/runtime/scripts/{checks → hfs}/architecture/owners.mjs +35 -15
- package/runtime/scripts/{checks → hfs}/architecture/register-once.mjs +2 -1
- package/runtime/scripts/{checks → hfs}/architecture/registration.mjs +23 -81
- package/runtime/scripts/{checks → hfs}/architecture/required-files.mjs +21 -13
- package/runtime/scripts/{checks → 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/{checks → hfs}/architecture/source-names.mjs +27 -147
- package/runtime/scripts/{checks → 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/{checks → hfs}/architecture/surface.mjs +10 -4
- package/runtime/scripts/{checks → hfs}/architecture/test-world-files.mjs +11 -21
- package/runtime/scripts/{checks → hfs}/architecture/tiers.mjs +47 -5
- package/runtime/scripts/{checks → hfs}/architecture/transport-owner.mjs +3 -3
- package/runtime/scripts/{checks → hfs}/architecture/typescript.mjs +13 -27
- package/runtime/scripts/{checks → hfs}/architecture.mjs +5 -5
- package/runtime/scripts/{lib/hfs-check.mjs → hfs/check.mjs} +95 -84
- 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 +247 -0
- package/runtime/scripts/hfs/param-names.mjs +18 -0
- package/runtime/scripts/{lib/hfs-path-findings.mjs → hfs/path-findings.mjs} +7 -7
- package/runtime/scripts/{lib → 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/{lib/hfs-rules → hfs/rules}/contract.mjs +11 -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/{lib/hfs-rules → 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 +66 -0
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/fe-no-tests.mjs +6 -5
- package/runtime/scripts/{lib/hfs-rules/frontend.mjs → hfs/rules/frontend-tree.mjs} +9 -9
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/integration-specs.mjs +3 -6
- package/runtime/scripts/hfs/rules/kinds.mjs +84 -0
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/lint-suppression.mjs +1 -1
- package/runtime/scripts/hfs/rules/monorepo.mjs +179 -0
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/peer-integrations.mjs +3 -3
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/pipeline.mjs +11 -21
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/proof-commands.mjs +2 -2
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/read.mjs +1 -1
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/repo-local-checks.mjs +2 -2
- package/runtime/scripts/hfs/rules/saga.mjs +170 -0
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/secrets.mjs +4 -4
- package/runtime/scripts/hfs/rules/services.mjs +220 -0
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/spec-placement.mjs +1 -1
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/stacks.mjs +5 -5
- package/runtime/scripts/hfs/rules/supabase-secrets.mjs +89 -0
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/test-topology.mjs +2 -2
- package/runtime/scripts/hfs/secret.mjs +175 -0
- package/runtime/scripts/{lib/hfs-slots.mjs → hfs/slots.mjs} +114 -150
- package/runtime/scripts/hfs/sql/pg-parse.mjs +46 -0
- package/runtime/scripts/hfs/test-world-slot.mjs +6 -0
- package/runtime/scripts/{lib/hfs-tree.mjs → hfs/tree.mjs} +6 -5
- package/runtime/scripts/{checks → hfs}/typescript-programs.mjs +0 -5
- package/runtime/scripts/{lib/hfs-view.mjs → hfs/view.mjs} +6 -5
- 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 +429 -0
- package/runtime/scripts/lib/i18n.mjs +86 -0
- package/runtime/scripts/lib/is-main.mjs +13 -0
- package/runtime/scripts/lib/json.mjs +13 -3
- package/runtime/scripts/lib/language.mjs +26 -7
- package/runtime/scripts/lib/list.mjs +20 -0
- package/runtime/scripts/lib/package-at.mjs +37 -0
- package/runtime/scripts/lib/path-key.mjs +59 -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-declaration.mjs +2 -2
- package/runtime/scripts/lib/stack-services.mjs +7 -31
- package/runtime/scripts/lib/ts-ast.mjs +35 -0
- package/runtime/scripts/{checks/common.mjs → lib/walk.mjs} +5 -15
- 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 +359 -70
- package/scaffold/edition-gate.mjs +25 -0
- package/scaffold/image.mjs +19 -0
- package/scaffold/lite-exports.mjs +186 -0
- package/scaffold/service.mjs +33 -27
- 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 +17 -17
- package/sync/index.mjs +171 -71
- 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 +2 -5
- 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/origin.guard.ts +2 -2
- 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 +3 -3
- 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/landing/next.config.ts +21 -0
- 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/apps/__app__ → skeleton-lite/apps/web}/src/modules/i18n/request.ts +4 -2
- 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/engine/admission.mjs +0 -284
- package/runtime/engine/digest.mjs +0 -10
- package/runtime/engine/ledger-db.mjs +0 -1245
- package/runtime/engine/machine-db.mjs +0 -1565
- package/runtime/engine/migrations/machine/0001-init.sql +0 -887
- package/runtime/engine/migrations/machine/0002-worktrees-no-workflow-kind.sql +0 -13
- package/runtime/engine/migrations/machine/0003-worktrees-workflow-orca.sql +0 -21
- package/runtime/engine/migrations/runtime/0001-init.sql +0 -1072
- package/runtime/engine/migrations/runtime/0003-usage-unavailable.sql +0 -13
- package/runtime/engine/migrations/runtime/0004-attempt-why.sql +0 -43
- package/runtime/engine/migrations/runtime/0005-ended-workflow-views.sql +0 -93
- package/runtime/scripts/api/fs/rmdir-link.mjs +0 -12
- package/runtime/scripts/lib/artifact-hold.mjs +0 -89
- package/runtime/scripts/lib/artifact-store.mjs +0 -103
- package/runtime/scripts/lib/redact.mjs +0 -148
- package/runtime/scripts/lib/safe-remove.mjs +0 -275
- package/runtime/scripts/lib/test-secrets.mjs +0 -116
- package/templates/fe/skeleton/apps/__app__/next.config.ts +0 -11
- 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 -21
- package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/index.tsx +0 -17
- package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/component.tsx +0 -26
- package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/index.tsx +0 -15
- package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/component.tsx +0 -25
- 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 -27
- 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 -32
- 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/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/runtime/scripts/{checks → hfs}/architecture/config-unread.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/constructor-deps.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/cross-app-duplicate.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/default-deny.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/error-codes.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/error-masked.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/hooks-are-hooks.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/injection-token-exported.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/package-shape.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/reachability.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/route-files-thin.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/sql-tokens.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/symbols.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/unit-spec-providers.mjs +0 -0
- /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
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
// safe-remove.mjs — the ONE way the runtime deletes a directory tree (nivo-fe inc-c8fbf76aa499).
|
|
2
|
-
//
|
|
3
|
-
// At 05:47 on 2026-09-25 D:/Repositories/nivo-fe lost 674 tracked working-tree files and its node_modules: a
|
|
4
|
-
// recursive delete of a scratch tree followed directory links (Windows junctions) into the live repository.
|
|
5
|
-
// Git for Windows' `git worktree remove --force` follows a junction inside the worktree and empties its target
|
|
6
|
-
// (proven on this host, git 2.52), and a PowerShell/cmd/third-party recursive delete may do the same. A tree
|
|
7
|
-
// that holds links to live checkouts is exactly what the runtime makes (node_modules junctions in land and
|
|
8
|
-
// worker staging checkouts, push-mains scratch worktrees, dependency mirrors).
|
|
9
|
-
//
|
|
10
|
-
// safeRemoveTree walks the tree itself with lstat and NEVER descends into a link: every symlink, junction or
|
|
11
|
-
// other reparse point is unlinked as a link (the link only, never what it points at) and verified gone. A
|
|
12
|
-
// directory counts as a link when lstat says so, when readlink resolves it, or when its real path is not
|
|
13
|
-
// its own path under its parent's real path (any name-surrogate reparse point). A link that cannot be
|
|
14
|
-
// unlinked stops the removal of everything above it; nothing is ever deleted through it.
|
|
15
|
-
//
|
|
16
|
-
// safeRemoveWorktree removes a git worktree: every link removed as a link first (found without following one), zero
|
|
17
|
-
// links asserted, only then `git worktree remove --force`, and the main checkout asserted untouched afterwards.
|
|
18
|
-
import fs from 'node:fs';
|
|
19
|
-
import os from 'node:os';
|
|
20
|
-
import path from 'node:path';
|
|
21
|
-
import { fileURLToPath } from 'node:url';
|
|
22
|
-
import { sleepSync } from './sleep-sync.mjs';
|
|
23
|
-
import { samePath } from './path-key.mjs';
|
|
24
|
-
import { gitSpawn } from '../api/git/lib.mjs';
|
|
25
|
-
import { rmdirLink } from '../api/fs/rmdir-link.mjs';
|
|
26
|
-
import { artifactHoldReason } from './artifact-hold.mjs';
|
|
27
|
-
import { realpathOr } from './fs-kind.mjs';
|
|
28
|
-
|
|
29
|
-
const WIN = process.platform === 'win32';
|
|
30
|
-
const same = samePath;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* True when `p` is a link of any kind: a symlink, a junction, or another reparse point that redirects it.
|
|
34
|
-
* `parentReal` (the real path of p's parent, when the caller walked to it through real directories) lets the
|
|
35
|
-
* real-path test catch a reparse point lstat and readlink do not report. A missing path is not a link.
|
|
36
|
-
*/
|
|
37
|
-
export function isLinkLike(p, { parentReal = null, stat = null } = {}) {
|
|
38
|
-
let st = stat;
|
|
39
|
-
if (!st) { try { st = fs.lstatSync(p); } catch { return false; } }
|
|
40
|
-
if (st.isSymbolicLink()) return true;
|
|
41
|
-
if (!st.isDirectory()) return false;
|
|
42
|
-
try { fs.readlinkSync(p); return true; } catch { /* not a link readlink can read */ }
|
|
43
|
-
const parent = parentReal ?? realpathOr(path.dirname(p));
|
|
44
|
-
const real = realpathOr(p);
|
|
45
|
-
if (!parent || !real) return true; // cannot prove it is a plain directory: treat it as a link
|
|
46
|
-
return !same(real, path.join(parent, path.basename(p)));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Remove a link itself, never its target. True when nothing is left at `p`. */
|
|
50
|
-
/** Every link (symlink, junction, other reparse point) under root, found with lstat; the walk never enters one. */
|
|
51
|
-
export function linksUnder(root) {
|
|
52
|
-
const found = [];
|
|
53
|
-
const visit = (p, parentReal) => {
|
|
54
|
-
let stat;
|
|
55
|
-
try { stat = fs.lstatSync(p); } catch { return; }
|
|
56
|
-
if (isLinkLike(p, { parentReal, stat })) { found.push(p); return; }
|
|
57
|
-
if (!stat.isDirectory()) return;
|
|
58
|
-
let real, names;
|
|
59
|
-
try { real = fs.realpathSync.native(p); names = fs.readdirSync(p); } catch { return; }
|
|
60
|
-
for (const name of names) visit(path.join(p, name), real);
|
|
61
|
-
};
|
|
62
|
-
visit(path.resolve(root), null);
|
|
63
|
-
return found;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function unlinkOnly(p) {
|
|
67
|
-
try { fs.lstatSync(p); } catch (error) { return error?.code === 'ENOENT'; }
|
|
68
|
-
try { fs.unlinkSync(p); } catch { try { fs.rmdirSync(p); } catch { /* verified below */ } }
|
|
69
|
-
try { fs.lstatSync(p); return false; } catch (error) { return error?.code === 'ENOENT'; }
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const retrying = (fn, retries) => {
|
|
73
|
-
for (let attempt = 0; ; attempt += 1) {
|
|
74
|
-
try { fn(); return null; } catch (error) {
|
|
75
|
-
if (error?.code === 'ENOENT') return null;
|
|
76
|
-
if (attempt >= retries || !['EBUSY', 'EPERM', 'EACCES', 'ENOTEMPTY'].includes(error?.code)) return error;
|
|
77
|
-
sleepSync(25 * (attempt + 1));
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
const removeFile = (p, retries) => retrying(() => {
|
|
82
|
-
try { fs.unlinkSync(p); } catch (error) {
|
|
83
|
-
if (WIN && (error?.code === 'EPERM' || error?.code === 'EACCES')) { fs.chmodSync(p, 0o666); fs.unlinkSync(p); } else throw error;
|
|
84
|
-
}
|
|
85
|
-
}, retries);
|
|
86
|
-
|
|
87
|
-
const SKILL_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
88
|
-
/**
|
|
89
|
-
* True when `p` sits strictly below `root` by real path: no link on the way, not `root` itself. The one place a
|
|
90
|
-
* primary checkout may be removed is a disposable fixture a caller names by its temp root (hk-tmp).
|
|
91
|
-
*/
|
|
92
|
-
export function strictlyInsideReal(p, root) {
|
|
93
|
-
if (!root) return false;
|
|
94
|
-
const rel = path.relative(path.resolve(root), path.resolve(p));
|
|
95
|
-
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return false;
|
|
96
|
-
const real = realpathOr(p);
|
|
97
|
-
const rootReal = realpathOr(root);
|
|
98
|
-
// Its real path must be the same relative place under the root's real path: no link between them.
|
|
99
|
-
return Boolean(real && rootReal) && same(real, path.join(rootReal, rel));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* A path the runtime must never remove whole: a filesystem root, the home or temp directory, the runtime, the
|
|
104
|
-
* repository that hosts it, the repositories root, or a primary git checkout (its .git is a directory; the
|
|
105
|
-
* scratch trees the runtime removes are temp directories and linked worktrees, whose .git is a file).
|
|
106
|
-
* `checkoutsUnder` names a disposable root (hk-tmp's temp root): a checkout strictly inside it by real path
|
|
107
|
-
* is a spec fixture, not a live repository, and is not refused for its .git. Every other refusal stands.
|
|
108
|
-
* A tree holding an indexed job artifact, or inside an evidence directory holding one, is refused whatever the
|
|
109
|
-
* workflow's phase (artifact-hold.mjs).
|
|
110
|
-
*/
|
|
111
|
-
export function forbiddenRoot(p, { checkoutsUnder = null } = {}) {
|
|
112
|
-
const resolved = path.resolve(p);
|
|
113
|
-
if (path.parse(resolved).root === resolved || same(path.dirname(resolved), resolved)) return 'a filesystem root';
|
|
114
|
-
for (const [name, dir] of [['the home directory', os.homedir()], ['the temp directory', os.tmpdir()], ['the runtime', SKILL_ROOT],
|
|
115
|
-
['the repository hosting the runtime', path.dirname(SKILL_ROOT)], ['the repositories root', path.dirname(path.dirname(SKILL_ROOT))]]) {
|
|
116
|
-
if (dir && same(path.resolve(dir), resolved)) return name;
|
|
117
|
-
}
|
|
118
|
-
const held = artifactHoldReason(resolved);
|
|
119
|
-
if (held) return held;
|
|
120
|
-
let checkout = false;
|
|
121
|
-
try { checkout = fs.lstatSync(path.join(resolved, '.git')).isDirectory(); } catch { /* no .git directory */ }
|
|
122
|
-
if (checkout && !strictlyInsideReal(resolved, checkoutsUnder)) return 'a git checkout';
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Remove `root` and everything under it without ever following a link. Links are unlinked (the link only);
|
|
128
|
-
* plain files and directories are deleted bottom-up. `checkoutsUnder` is forbiddenRoot's disposable root. Returns {ok, root, removed: {files, dirs, links},
|
|
129
|
-
* errors: [{path, code, message}]}; ok is true only when `root` is gone. A missing root is ok.
|
|
130
|
-
*/
|
|
131
|
-
export function safeRemoveTree(root, { retries = 5, checkoutsUnder = null } = {}) {
|
|
132
|
-
const target = path.resolve(String(root ?? ''));
|
|
133
|
-
const out = { ok: false, root: target, removed: { files: 0, dirs: 0, links: 0 }, errors: [] };
|
|
134
|
-
const fail = (p, error) => { out.errors.push({ path: p, code: error?.code ?? 'ERROR', message: String(error?.message ?? error) }); };
|
|
135
|
-
const refused = root ? forbiddenRoot(target, { checkoutsUnder }) : 'no path';
|
|
136
|
-
if (refused) { fail(target, { code: 'REFUSED', message: `refusing to remove ${refused}` }); return out; }
|
|
137
|
-
let st;
|
|
138
|
-
try { st = fs.lstatSync(target); } catch (error) {
|
|
139
|
-
if (error?.code === 'ENOENT') { out.ok = true; return out; }
|
|
140
|
-
fail(target, error); return out;
|
|
141
|
-
}
|
|
142
|
-
// CONTAINMENT (the .claude/node_modules wipe, 2026-09-28): nothing is ever deleted whose real path is not
|
|
143
|
-
// the root itself or strictly under the root's real path. A link is unlinked above (the link only); any other
|
|
144
|
-
// entry that resolves outside the tree is refused, whatever made it look like a plain entry.
|
|
145
|
-
const rootReal = realpathOr(target);
|
|
146
|
-
const insideRoot = (p) => { const real = realpathOr(p); if (!real || !rootReal) return false; if (same(real, rootReal)) return true;
|
|
147
|
-
const rel = path.relative(rootReal, real); return Boolean(rel) && !rel.startsWith('..') && !path.isAbsolute(rel); };
|
|
148
|
-
const walk = (dir, st, parentReal) => {
|
|
149
|
-
if (isLinkLike(dir, { parentReal, stat: st })) {
|
|
150
|
-
if (unlinkOnly(dir)) { out.removed.links += 1; return true; }
|
|
151
|
-
fail(dir, { code: 'LINK_STUCK', message: 'a link could not be unlinked; nothing above it is removed' });
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
if (!insideRoot(dir)) {
|
|
155
|
-
fail(dir, { code: 'OUTSIDE_ROOT', message: `refusing to delete ${dir}: its real path ${realpathOr(dir) ?? '?'} is outside ${rootReal ?? target}` });
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
if (!st.isDirectory()) {
|
|
159
|
-
const error = removeFile(dir, retries);
|
|
160
|
-
if (error) { fail(dir, error); return false; }
|
|
161
|
-
out.removed.files += 1; return true;
|
|
162
|
-
}
|
|
163
|
-
const real = realpathOr(dir);
|
|
164
|
-
if (!real) { fail(dir, { code: 'REALPATH', message: 'cannot resolve the directory' }); return false; }
|
|
165
|
-
let entries;
|
|
166
|
-
try { entries = fs.readdirSync(dir); } catch (error) { fail(dir, error); return false; }
|
|
167
|
-
let clear = true;
|
|
168
|
-
for (const name of entries) {
|
|
169
|
-
const child = path.join(dir, name);
|
|
170
|
-
let childStat;
|
|
171
|
-
try { childStat = fs.lstatSync(child); } catch (error) { if (error?.code !== 'ENOENT') { fail(child, error); clear = false; } continue; }
|
|
172
|
-
if (!walk(child, childStat, real)) clear = false;
|
|
173
|
-
}
|
|
174
|
-
if (!clear) return false;
|
|
175
|
-
const error = retrying(() => fs.rmdirSync(dir), retries);
|
|
176
|
-
if (error) { fail(dir, error); return false; }
|
|
177
|
-
out.removed.dirs += 1; return true;
|
|
178
|
-
};
|
|
179
|
-
walk(target, st, null);
|
|
180
|
-
out.ok = !fs.existsSync(target) && (() => { try { fs.lstatSync(target); return false; } catch { return true; } })();
|
|
181
|
-
return out;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/** Remove one link as a link, never its target: `cmd /c rmdir <link>` on Windows (no /s), then unlinkOnly. */
|
|
185
|
-
export function removeLink(p) {
|
|
186
|
-
if (WIN) {
|
|
187
|
-
let st = null;
|
|
188
|
-
try { st = fs.lstatSync(p); } catch { return true; }
|
|
189
|
-
if (st.isDirectory() || st.isSymbolicLink()) rmdirLink(p);
|
|
190
|
-
}
|
|
191
|
-
return unlinkOnly(p);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/** The main checkout's state a removal must never change: its tracked deletions and its node_modules entry counts. */
|
|
195
|
-
export function mainCheckoutGuard(mainRoot, { git = null } = {}) {
|
|
196
|
-
const run = git ?? ((args, opts) => gitSpawn('git', args, { cwd: opts.cwd, maxBuffer: 64 * 1024 * 1024 }));
|
|
197
|
-
const count = (rel) => { try { return fs.readdirSync(path.join(mainRoot, rel)).length; } catch { return null; } };
|
|
198
|
-
// porcelain v2 ("1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>"): no leading blank a runner's trim could eat.
|
|
199
|
-
const st = run(['status', '--porcelain=v2', '--untracked-files=no'], { cwd: mainRoot });
|
|
200
|
-
const text = String(st?.stdout ?? st?.out ?? '');
|
|
201
|
-
const ok = st?.ok ?? (!st?.error && st?.status === 0);
|
|
202
|
-
const deleted = text.split(/\r?\n/).map((l) => l.trim().split(' ')).filter((f) => f[0] === '1' && f.length >= 9 && f[1].includes('D')).map((f) => f.slice(8).join(' '));
|
|
203
|
-
return { ok: Boolean(ok), deleted: new Set(deleted),
|
|
204
|
-
nodeModules: count('node_modules'), packagesNodeModules: count(path.join('packages', 'node_modules')) };
|
|
205
|
-
}
|
|
206
|
-
/** What changed in the main checkout between two guards: [] when nothing. */
|
|
207
|
-
export function mainCheckoutDamage(before, after) {
|
|
208
|
-
const out = [];
|
|
209
|
-
if (before.ok && after.ok) for (const f of after.deleted) if (!before.deleted.has(f)) out.push(`tracked file deleted: ${f}`);
|
|
210
|
-
if (before.nodeModules !== after.nodeModules) out.push(`node_modules entries ${before.nodeModules} -> ${after.nodeModules}`);
|
|
211
|
-
if (before.packagesNodeModules !== after.packagesNodeModules) out.push(`packages/node_modules entries ${before.packagesNodeModules} -> ${after.packagesNodeModules}`);
|
|
212
|
-
return out;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* The link step of every worktree removal (git's here, Orca's in scripts/api/orca/worktree-remove.mjs removeOrcaWorktree): every link
|
|
217
|
-
* under `target` found WITHOUT following one (linksUnder), each removed as a link (removeLink: `cmd /c rmdir <link>`, never
|
|
218
|
-
* /s), outermost first, then a re-scan that must find ZERO. {ok, links, errors: [{path, code, message}]}; ok false: a link
|
|
219
|
-
* is stuck and the caller removes nothing.
|
|
220
|
-
*/
|
|
221
|
-
export function removeLinksUnder(target) {
|
|
222
|
-
const out = { ok: false, links: 0, errors: [] };
|
|
223
|
-
if (!fs.existsSync(target)) { out.ok = true; return out; }
|
|
224
|
-
for (const link of linksUnder(target)) { if (removeLink(link)) out.links += 1; else out.errors.push({ path: link, code: 'LINK_STUCK', message: 'a link could not be removed' }); }
|
|
225
|
-
for (const l of linksUnder(target)) if (!out.errors.some((e) => e.path === l)) out.errors.push({ path: l, code: 'LINK_STUCK', message: 'a link is still there after removal' });
|
|
226
|
-
out.ok = out.errors.length === 0;
|
|
227
|
-
return out;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Remove a git worktree (the one algorithm; the 490-file .claude incident and nivo-fe inc-c8fbf76aa499):
|
|
232
|
-
* 1. enumerate every link in it WITHOUT following one (linksUnder);
|
|
233
|
-
* 2. remove each as a link (removeLink: `cmd /c rmdir <link>`, never /s), outermost first;
|
|
234
|
-
* 3. re-scan the same way and refuse (link-stuck, nothing deleted) unless ZERO links remain;
|
|
235
|
-
* 4. only then `git worktree remove --force` (a link-free tree: git cannot walk out of it); a directory git does not know
|
|
236
|
-
* goes through safeRemoveTree (never follows a link); `git worktree prune`;
|
|
237
|
-
* 5. assert the main checkout is untouched: no new tracked deletion, node_modules and packages/node_modules entry counts
|
|
238
|
-
* unchanged - a violation is {ok:false, fatal:true, reason:'main-checkout-damaged'}: the caller (the GC) stops.
|
|
239
|
-
* Never robocopy, rm -rf or rmdir /s. `git(args, {cwd})` is the caller's git runner; `repo` any checkout of the repository.
|
|
240
|
-
* {ok, root, links, removed, errors, damage?}
|
|
241
|
-
*/
|
|
242
|
-
export function safeRemoveWorktree(worktree, { repo, git = null, retries = 5 } = {}) {
|
|
243
|
-
const target = path.resolve(String(worktree ?? ''));
|
|
244
|
-
const run = git ?? ((args, opts) => gitSpawn('git', args, { cwd: opts.cwd, maxBuffer: 64 * 1024 * 1024 }));
|
|
245
|
-
const out = { ok: false, root: target, links: 0, removed: { files: 0, dirs: 0, links: 0 }, errors: [] };
|
|
246
|
-
const list = repo ? String((run(['worktree', 'list', '--porcelain'], { cwd: repo }) ?? {}).stdout ?? '') : '';
|
|
247
|
-
const trees = list.split(/\r?\n/).filter((l) => l.startsWith('worktree ')).map((l) => path.resolve(l.slice(9).trim()));
|
|
248
|
-
const mainRoot = trees[0] ?? null;
|
|
249
|
-
if (mainRoot && same(mainRoot, target)) { out.errors.push({ path: target, code: 'REFUSED', message: 'refusing to remove the main checkout' }); return out; }
|
|
250
|
-
const refused = forbiddenRoot(target);
|
|
251
|
-
if (refused) { out.errors.push({ path: target, code: 'REFUSED', message: `refusing to remove ${refused}` }); return out; }
|
|
252
|
-
const before = mainRoot ? mainCheckoutGuard(mainRoot, { git: run }) : null;
|
|
253
|
-
if (fs.existsSync(target)) {
|
|
254
|
-
const unlinked = removeLinksUnder(target);
|
|
255
|
-
out.links = unlinked.links;
|
|
256
|
-
if (!unlinked.ok) { out.errors.push(...unlinked.errors); out.reason = 'link-stuck'; return out; }
|
|
257
|
-
out.removed.links = out.links;
|
|
258
|
-
const registered = trees.some((t) => same(t, target));
|
|
259
|
-
if (registered && repo) run(['worktree', 'remove', '--force', target], { cwd: repo });
|
|
260
|
-
if (fs.existsSync(target)) {
|
|
261
|
-
if (linksUnder(target).length) { out.errors.push({ path: target, code: 'LINK_STUCK', message: 'a link appeared during removal' }); out.reason = 'link-stuck'; return out; }
|
|
262
|
-
const rm = safeRemoveTree(target, { retries });
|
|
263
|
-
out.removed.files += rm.removed.files; out.removed.dirs += rm.removed.dirs;
|
|
264
|
-
out.errors.push(...rm.errors);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
if (repo) { try { run(['worktree', 'prune'], { cwd: repo }); } catch { /* the registration is pruned on the next prune */ } }
|
|
268
|
-
if (before) {
|
|
269
|
-
const damage = mainCheckoutDamage(before, mainCheckoutGuard(mainRoot, { git: run }));
|
|
270
|
-
if (damage.length) { out.damage = damage; out.fatal = true; out.reason = 'main-checkout-damaged'; out.errors.push({ path: mainRoot, code: 'main-checkout-damaged', message: damage.join('; ') }); return out; }
|
|
271
|
-
}
|
|
272
|
-
out.ok = !fs.existsSync(target) && (() => { try { fs.lstatSync(target); return false; } catch { return true; } })();
|
|
273
|
-
if (!out.ok && !out.reason) out.reason = 'remove-failed';
|
|
274
|
-
return out;
|
|
275
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// test-secrets.mjs — test credentials follow the product repository's own `.starcistacks` convention (owner ruling
|
|
3
|
-
// push-scan-test-secrets-encrypted, 2026-09-28: they work exactly like the stack custody and a plaintext password is never pushed).
|
|
4
|
-
// There is no store of the runtime's own: a test credential lives at `<repo>/.starcistacks/<stack>/secrets/test/<name>`,
|
|
5
|
-
// its plaintext git-ignored by the repo's `.starcistacks/**` rules and only the sops-encrypted twin `<name>.enc`
|
|
6
|
-
// committed. It is written with the repository's existing command (`node scripts/stack-secret.mjs set
|
|
7
|
-
// <stack>/secrets/test/<name>`, npm run secret:set), which encrypts against the recipients `.sops.yaml` names and
|
|
8
|
-
// removes the plaintext; this module only READS: the local plaintext when present, else the `.enc` decrypted by sops
|
|
9
|
-
// with the shared age identity (SOPS_AGE_KEY_FILE, default ~/.starci/master.identity) into memory.
|
|
10
|
-
// A credential that need not be stable across runs (a disposable account registered per run) is generated per run
|
|
11
|
-
// and stored nowhere. A value is never logged.
|
|
12
|
-
//
|
|
13
|
-
// node scripts/lib/test-secrets.mjs get <name> --repo <path> [--stack dev] [--reveal] prints nothing unless --reveal
|
|
14
|
-
//
|
|
15
|
-
// In a script: import { testSecret } from '<runtime>/scripts/lib/test-secrets.mjs';
|
|
16
|
-
// const password = testSecret('login-capture-password', { repo });
|
|
17
|
-
// The push secret scan (scripts/supervisor/push-mains.mjs) passes a `.enc` only when isSopsEnvelope holds.
|
|
18
|
-
import fs from 'node:fs';
|
|
19
|
-
import path from 'node:path';
|
|
20
|
-
import { sopsDecrypt } from '../api/sops/decrypt.mjs';
|
|
21
|
-
import { fileURLToPath } from 'node:url';
|
|
22
|
-
import { parseYaml } from '../../engine/yaml.mjs';
|
|
23
|
-
|
|
24
|
-
const NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
25
|
-
const STACK = /^[a-z0-9][a-z0-9-]{0,31}$/;
|
|
26
|
-
/** One sops-encrypted leaf: `ENC[AES256_GCM,data:...,iv:...,tag:...,type:...]` (data may be empty). */
|
|
27
|
-
const SOPS_VALUE = /^ENC\[AES256_GCM,data:[A-Za-z0-9+/=]*,iv:[A-Za-z0-9+/=]+,tag:[A-Za-z0-9+/=]+,type:[a-z]+\]$/;
|
|
28
|
-
|
|
29
|
-
/** Where test credential `name` of `stack` lives in `repo`: {plain, enc, rel}. `rel` is the path the repo's
|
|
30
|
-
* `stack-secret.mjs set` takes. */
|
|
31
|
-
export function testSecretPaths(name, { repo, stack = 'dev' } = {}) {
|
|
32
|
-
if (!NAME.test(String(name ?? ''))) throw new Error(`test secret name must match ${NAME} (got ${JSON.stringify(name)})`);
|
|
33
|
-
if (!STACK.test(String(stack ?? ''))) throw new Error(`stack must match ${STACK} (got ${JSON.stringify(stack)})`);
|
|
34
|
-
if (!repo) throw new Error('test secret needs {repo}: the product repository whose .starcistacks holds it');
|
|
35
|
-
const rel = `${stack}/secrets/test/${name}`;
|
|
36
|
-
const plain = path.join(path.resolve(String(repo)), '.starcistacks', stack, 'secrets', 'test', name);
|
|
37
|
-
return { plain, enc: `${plain}.enc`, rel };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** The command that stores a test credential, in the product repository's own tooling. */
|
|
41
|
-
export const setCommand = (name = '<name>', stack = 'dev') => `node scripts/stack-secret.mjs set ${stack}/secrets/test/${name}`;
|
|
42
|
-
|
|
43
|
-
/** sops' format for a plaintext path, as the repository's stack-secret.mjs formatFor decides it. */
|
|
44
|
-
export const sopsFormatFor = (file) => (file.endsWith('.env') ? 'dotenv' : /\.json$/.test(file) ? 'json' : /\.(ya?ml|kubeconfig)$/.test(file) ? 'yaml' : 'binary');
|
|
45
|
-
|
|
46
|
-
/** The sops binary: PATH, then winget's Links and Packages (spawn without a shell ignores PATHEXT). */
|
|
47
|
-
export function resolveSops(env = process.env) {
|
|
48
|
-
const win = process.platform === 'win32';
|
|
49
|
-
const names = win ? ['sops.exe', 'sops'] : ['sops'];
|
|
50
|
-
const dirs = String(env.PATH ?? '').split(win ? ';' : ':').filter(Boolean);
|
|
51
|
-
if (win && env.LOCALAPPDATA) {
|
|
52
|
-
const winget = path.join(env.LOCALAPPDATA, 'Microsoft', 'WinGet');
|
|
53
|
-
dirs.push(path.join(winget, 'Links'));
|
|
54
|
-
const packages = path.join(winget, 'Packages');
|
|
55
|
-
try { for (const e of fs.readdirSync(packages)) if (/sops/i.test(e)) dirs.push(path.join(packages, e)); } catch { /* none */ }
|
|
56
|
-
}
|
|
57
|
-
for (const dir of dirs) for (const n of names) { const f = path.join(dir, n); try { if (fs.statSync(f).isFile()) return f; } catch { /* next */ } }
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Read test credential `name`: the local plaintext when present, else its committed `.enc` through sops. */
|
|
62
|
-
export function testSecret(name, { repo, stack = 'dev', env = process.env, sops = null } = {}) {
|
|
63
|
-
const { plain, enc, rel } = testSecretPaths(name, { repo, stack });
|
|
64
|
-
if (fs.existsSync(plain)) return fs.readFileSync(plain, 'utf8').replace(/\r?\n$/, '');
|
|
65
|
-
if (!fs.existsSync(enc)) throw new Error(`test secret ${name} is not in .starcistacks/${rel}.enc; store it with \`${setCommand(name, stack)}\` in ${repo}`);
|
|
66
|
-
const bin = sops ?? resolveSops(env);
|
|
67
|
-
if (!bin) throw new Error('sops is not installed (Windows: winget install Mozilla.SOPS)');
|
|
68
|
-
const format = sopsFormatFor(plain);
|
|
69
|
-
const r = sopsDecrypt(bin, ['--decrypt', '--input-type', format, '--output-type', format, enc], { cwd: path.resolve(String(repo)), env, maxBuffer: 1024 * 1024 });
|
|
70
|
-
if (r.status !== 0) throw new Error(`test secret ${name}: sops could not decrypt .starcistacks/${rel}.enc (${String(r.stderr ?? r.error?.message ?? '').trim().split(/\r?\n/).pop()})`);
|
|
71
|
-
return String(r.stdout).replace(/\r?\n$/, '');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** Every leaf outside the `sops` block is an ENC[...] value (or null); the block itself carries a mac. */
|
|
75
|
-
const allEncrypted = (node) => {
|
|
76
|
-
if (node === null) return true;
|
|
77
|
-
if (Array.isArray(node)) return node.every(allEncrypted);
|
|
78
|
-
if (typeof node === 'object') return Object.values(node).every(allEncrypted);
|
|
79
|
-
return typeof node === 'string' && SOPS_VALUE.test(node);
|
|
80
|
-
};
|
|
81
|
-
const sopsTree = (doc) => !!doc && typeof doc === 'object' && !Array.isArray(doc) && !!doc.sops && typeof doc.sops === 'object'
|
|
82
|
-
&& typeof doc.sops.mac === 'string' && SOPS_VALUE.test(doc.sops.mac)
|
|
83
|
-
&& Object.keys(doc).length > 1 && Object.entries(doc).every(([k, v]) => k === 'sops' || allEncrypted(v));
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* True only for a sops-encrypted file with no plaintext value: binary/json form ({"data": ENC[...], "sops": {...}}),
|
|
87
|
-
* yaml form (every value ENC[...] plus the sops block) or dotenv form (KEY=ENC[...] lines plus sops_* metadata).
|
|
88
|
-
* The sops metadata must carry its mac. A key kept plaintext through `unencrypted_suffix` does not pass.
|
|
89
|
-
*/
|
|
90
|
-
export function isSopsEnvelope(text) {
|
|
91
|
-
const src = String(text ?? '').replace(/^/, '');
|
|
92
|
-
if (!src.trim()) return false;
|
|
93
|
-
try { return sopsTree(JSON.parse(src)); } catch { /* not JSON */ }
|
|
94
|
-
const lines = src.split(/\r?\n/).filter((l) => l.trim() && !/^\s*#/.test(l));
|
|
95
|
-
if (lines.length && lines.every((l) => /^[A-Za-z_][A-Za-z0-9_]*=/.test(l))) {
|
|
96
|
-
const pairs = lines.map((l) => [l.slice(0, l.indexOf('=')), l.slice(l.indexOf('=') + 1)]);
|
|
97
|
-
const meta = pairs.filter(([k]) => k.startsWith('sops_'));
|
|
98
|
-
const vals = pairs.filter(([k]) => !k.startsWith('sops_'));
|
|
99
|
-
return vals.length > 0 && vals.every(([, v]) => SOPS_VALUE.test(v)) && meta.some(([k, v]) => k === 'sops_mac' && SOPS_VALUE.test(v));
|
|
100
|
-
}
|
|
101
|
-
try { return sopsTree(parseYaml(src)); } catch { return false; }
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
105
|
-
const [cmd, name, ...rest] = process.argv.slice(2);
|
|
106
|
-
const opt = (f) => { const i = rest.indexOf(f); return i >= 0 ? rest[i + 1] : undefined; };
|
|
107
|
-
try {
|
|
108
|
-
if (cmd === 'get') {
|
|
109
|
-
const value = testSecret(name, { repo: opt('--repo'), stack: opt('--stack') ?? 'dev' });
|
|
110
|
-
if (rest.includes('--reveal')) process.stdout.write(`${value}\n`);
|
|
111
|
-
} else {
|
|
112
|
-
console.error(`usage: test-secrets.mjs get <name> --repo <path> [--stack dev] [--reveal]\nstore one with the product repo's own command: ${setCommand()}`);
|
|
113
|
-
process.exitCode = 2;
|
|
114
|
-
}
|
|
115
|
-
} catch (error) { console.error(String(error?.message ?? error)); process.exitCode = 1; }
|
|
116
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { NextConfig } from "next"
|
|
2
|
-
import createNextIntlPlugin from "next-intl/plugin"
|
|
3
|
-
|
|
4
|
-
const withNextIntl = createNextIntlPlugin("./src/modules/i18n/request.ts")
|
|
5
|
-
|
|
6
|
-
/** Next config of the {{app}} app: next-intl wired to the request config, Turbopack rooted at the repository. */
|
|
7
|
-
const nextConfig: NextConfig = {
|
|
8
|
-
turbopack: { root: process.cwd() },
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export default withNextIntl(nextConfig)
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { Metadata } from "next"
|
|
2
|
-
import type { ReactNode } from "react"
|
|
3
|
-
import { LocaleShell, localeMetadata } from "@/features/layouts/LocaleShell"
|
|
4
|
-
import "../globals.css"
|
|
5
|
-
|
|
6
|
-
type LayoutProps = {
|
|
7
|
-
readonly children: ReactNode
|
|
8
|
-
readonly params: Promise<{ readonly locale: string }>
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/** The localized document title of this route segment. */
|
|
12
|
-
export const generateMetadata = async (props: LayoutProps): Promise<Metadata> =>
|
|
13
|
-
localeMetadata((await props.params).locale)
|
|
14
|
-
|
|
15
|
-
/** The locale segment's layout slot: it mounts the locale shell around the segment. */
|
|
16
|
-
const Layout = async (props: LayoutProps) => (
|
|
17
|
-
<LocaleShell lang={(await props.params).locale}>{props.children}</LocaleShell>
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
export default Layout
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { HomePage, homeMetadata } from "@/features/pages/HomePage"
|
|
2
|
-
|
|
3
|
-
/** The document title of this page, from the catalog of the requested locale. */
|
|
4
|
-
export const generateMetadata = homeMetadata
|
|
5
|
-
|
|
6
|
-
/** The locale root's page slot: it mounts the home page and nothing else. */
|
|
7
|
-
const Page = () => <HomePage />
|
|
8
|
-
|
|
9
|
-
export default Page
|
package/templates/fe/skeleton/apps/__app__/src/components/composites/FailureScreen/index.tsx
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { Button, GrammarRoot, Heading, PageContainer, WorkspaceShell } from "@starci/grammar/common"
|
|
2
|
-
|
|
3
|
-
/** Props for {@link FailureScreen}. */
|
|
4
|
-
type FailureScreenProps = {
|
|
5
|
-
/** What failed, said to the reader. */
|
|
6
|
-
readonly title: string
|
|
7
|
-
/** The label of the one recovery action. */
|
|
8
|
-
readonly retryLabel: string
|
|
9
|
-
/** Runs the recovery: the boundary re-renders what failed. */
|
|
10
|
-
readonly onRetry: () => void
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** A whole-screen failure with its one recovery action: the drawing both error boundaries share. */
|
|
14
|
-
export const FailureScreen = (props: FailureScreenProps) => (
|
|
15
|
-
<GrammarRoot>
|
|
16
|
-
<WorkspaceShell
|
|
17
|
-
primaryLabel={props.title}
|
|
18
|
-
primary={
|
|
19
|
-
<PageContainer measure="reading">
|
|
20
|
-
<Heading level={1}>{props.title}</Heading>
|
|
21
|
-
<Button variant="danger" onPress={props.onRetry}>
|
|
22
|
-
{props.retryLabel}
|
|
23
|
-
</Button>
|
|
24
|
-
</PageContainer>
|
|
25
|
-
}
|
|
26
|
-
/>
|
|
27
|
-
</GrammarRoot>
|
|
28
|
-
)
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { Metadata } from "next"
|
|
2
|
-
import { notFound } from "next/navigation"
|
|
3
|
-
import { hasLocale, NextIntlClientProvider } from "next-intl"
|
|
4
|
-
import { getMessages, getTranslations, setRequestLocale } from "next-intl/server"
|
|
5
|
-
import type { ReactNode } from "react"
|
|
6
|
-
import { siteUrl } from "@/modules/config"
|
|
7
|
-
import { routing } from "@/modules/i18n"
|
|
8
|
-
|
|
9
|
-
type LocaleShellProps = {
|
|
10
|
-
readonly lang: string
|
|
11
|
-
readonly children: ReactNode
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** The document title and the absolute base of every metadata URL, for the language in the address. */
|
|
15
|
-
export const localeMetadata = async (lang: string): Promise<Metadata> => {
|
|
16
|
-
if (!hasLocale(routing.locales, lang)) notFound()
|
|
17
|
-
const t = await getTranslations({ locale: lang, namespace: "app" })
|
|
18
|
-
return { title: t("title"), metadataBase: new URL(siteUrl()) }
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** The document of one route language: `<html lang>` from the address and the catalog handed to client components. */
|
|
22
|
-
export const LocaleShell = async (props: LocaleShellProps) => {
|
|
23
|
-
if (!hasLocale(routing.locales, props.lang)) notFound()
|
|
24
|
-
setRequestLocale(props.lang)
|
|
25
|
-
const messages = await getMessages()
|
|
26
|
-
return (
|
|
27
|
-
<html lang={props.lang}>
|
|
28
|
-
<body className="min-h-dvh">
|
|
29
|
-
<NextIntlClientProvider locale={props.lang} messages={messages}>
|
|
30
|
-
{props.children}
|
|
31
|
-
</NextIntlClientProvider>
|
|
32
|
-
</body>
|
|
33
|
-
</html>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { FailureScreen } from "@/components/composites/FailureScreen"
|
|
2
|
-
|
|
3
|
-
/** Props for {@link ErrorPageBase}. */
|
|
4
|
-
export type ErrorPageBaseProps = {
|
|
5
|
-
/** Whole-screen situations this surface settles; the error page only ever shows the failure. */
|
|
6
|
-
readonly state: "failed"
|
|
7
|
-
/** The words the failure shows. */
|
|
8
|
-
readonly props: {
|
|
9
|
-
readonly title: string
|
|
10
|
-
readonly retryLabel: string
|
|
11
|
-
}
|
|
12
|
-
/** What the surface reports upward. */
|
|
13
|
-
readonly on: {
|
|
14
|
-
readonly retry: () => void
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** Draw the failure of the locale segment with its one recovery action. */
|
|
19
|
-
export const ErrorPageBase = (props: ErrorPageBaseProps) => (
|
|
20
|
-
<FailureScreen title={props.props.title} retryLabel={props.props.retryLabel} onRetry={props.on.retry} />
|
|
21
|
-
)
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { useTranslations } from "next-intl"
|
|
2
|
-
import { ErrorPageBase } from "./component"
|
|
3
|
-
|
|
4
|
-
/** The public props of the locale error page: the boundary hands it the retry. */
|
|
5
|
-
type ErrorPageProps = { readonly onRetry: () => void }
|
|
6
|
-
|
|
7
|
-
/** A render failure of the locale segment shows this instead of a blank page; retry re-renders the segment. */
|
|
8
|
-
export const ErrorPage = (props: ErrorPageProps) => {
|
|
9
|
-
const t = useTranslations("errors.page")
|
|
10
|
-
return (
|
|
11
|
-
<ErrorPageBase
|
|
12
|
-
state="failed"
|
|
13
|
-
props={{ title: t("title"), retryLabel: t("retry") }}
|
|
14
|
-
on={{ retry: props.onRetry }}
|
|
15
|
-
/>
|
|
16
|
-
)
|
|
17
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { FailureScreen } from "@/components/composites/FailureScreen"
|
|
2
|
-
|
|
3
|
-
/** Props for {@link GlobalErrorPageBase}. */
|
|
4
|
-
export type GlobalErrorPageBaseProps = {
|
|
5
|
-
/** Whole-screen situations this surface settles; the global error page only ever shows the failure. */
|
|
6
|
-
readonly state: "failed"
|
|
7
|
-
/** The language of the document and the words the failure shows. */
|
|
8
|
-
readonly props: {
|
|
9
|
-
readonly lang: string
|
|
10
|
-
readonly title: string
|
|
11
|
-
readonly retryLabel: string
|
|
12
|
-
}
|
|
13
|
-
/** What the surface reports upward. */
|
|
14
|
-
readonly on: {
|
|
15
|
-
readonly retry: () => void
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Draw the whole document, because this boundary replaces the root layout that would have drawn it. */
|
|
20
|
-
export const GlobalErrorPageBase = (props: GlobalErrorPageBaseProps) => (
|
|
21
|
-
<html lang={props.props.lang}>
|
|
22
|
-
<body>
|
|
23
|
-
<FailureScreen title={props.props.title} retryLabel={props.props.retryLabel} onRetry={props.on.retry} />
|
|
24
|
-
</body>
|
|
25
|
-
</html>
|
|
26
|
-
)
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_LOCALE } from "@/modules/i18n"
|
|
2
|
-
import messages from "@/modules/i18n/messages/vi.json"
|
|
3
|
-
import { GlobalErrorPageBase } from "./component"
|
|
4
|
-
|
|
5
|
-
/** The public props of the last-resort error page: the boundary hands it the retry. */
|
|
6
|
-
type GlobalErrorPageProps = { readonly onRetry: () => void }
|
|
7
|
-
|
|
8
|
-
/** The last-resort boundary above the locale layout: it has no provider, so it reads the default catalog directly. */
|
|
9
|
-
export const GlobalErrorPage = (props: GlobalErrorPageProps) => (
|
|
10
|
-
<GlobalErrorPageBase
|
|
11
|
-
state="failed"
|
|
12
|
-
props={{ lang: DEFAULT_LOCALE, title: messages.errors.global.title, retryLabel: messages.errors.global.retry }}
|
|
13
|
-
on={{ retry: props.onRetry }}
|
|
14
|
-
/>
|
|
15
|
-
)
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { GrammarRoot, Heading, PageContainer, WorkspaceShell } from "@starci/grammar/common"
|
|
2
|
-
|
|
3
|
-
/** Props for {@link HomePageBase}. */
|
|
4
|
-
export type HomePageBaseProps = {
|
|
5
|
-
/** Whole-screen situations this surface settles; the home page only ever shows its title. */
|
|
6
|
-
readonly state: "ready"
|
|
7
|
-
/** The words the page shows. */
|
|
8
|
-
readonly props: { readonly title: string }
|
|
9
|
-
/** What the surface reports upward; the page reports nothing. */
|
|
10
|
-
readonly on: Record<never, never>
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Draw the front door of the app. */
|
|
14
|
-
export const HomePageBase = (props: HomePageBaseProps) => (
|
|
15
|
-
<GrammarRoot>
|
|
16
|
-
<WorkspaceShell
|
|
17
|
-
primaryLabel={props.props.title}
|
|
18
|
-
primary={
|
|
19
|
-
<PageContainer measure="reading">
|
|
20
|
-
<Heading level={1}>{props.props.title}</Heading>
|
|
21
|
-
</PageContainer>
|
|
22
|
-
}
|
|
23
|
-
/>
|
|
24
|
-
</GrammarRoot>
|
|
25
|
-
)
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { Metadata } from "next"
|
|
2
|
-
import { getTranslations } from "next-intl/server"
|
|
3
|
-
import { HomePageBase } from "./component"
|
|
4
|
-
|
|
5
|
-
/** The document title of the home page, from the catalog of the requested locale. */
|
|
6
|
-
export const homeMetadata = async (): Promise<Metadata> => {
|
|
7
|
-
const t = await getTranslations("home")
|
|
8
|
-
return { title: t("title") }
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/** The first page of the app: a server component, so no catalog is shipped for it. */
|
|
12
|
-
export const HomePage = async () => {
|
|
13
|
-
const t = await getTranslations("home")
|
|
14
|
-
return <HomePageBase state="ready" props={{ title: t("title") }} on={{}} />
|
|
15
|
-
}
|