@voltro/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/LICENSE +57 -0
- package/README.md +26 -0
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +20236 -0
- package/bin/voltro.mjs +43 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +9 -0
- package/dist/commands-CfPH2Wf4.js +18061 -0
- package/dist/frameworkInspectState-CX2250XB.js +86 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +5 -0
- package/dist/inspectState.d.ts +116 -0
- package/dist/inspectState.js +2 -0
- package/dist/startup.d.ts +26 -0
- package/dist/startup.js +2 -0
- package/dist/startupRunner-CRhuUl91.js +71 -0
- package/package.json +88 -0
- package/templates/AGENTS.core.md +258 -0
- package/templates/AGENTS.md +351 -0
- package/templates/agent-docs/_index.md +93 -0
- package/templates/agent-docs/_manifest.json +655 -0
- package/templates/agent-docs/ai.md +1845 -0
- package/templates/agent-docs/authentication.md +1788 -0
- package/templates/agent-docs/caching.md +624 -0
- package/templates/agent-docs/cli.md +1650 -0
- package/templates/agent-docs/configuration.md +295 -0
- package/templates/agent-docs/data.md +2172 -0
- package/templates/agent-docs/database/advancedqueries.md +1583 -0
- package/templates/agent-docs/database/columntypes.md +1200 -0
- package/templates/agent-docs/database/hosting.md +881 -0
- package/templates/agent-docs/database/migrations.md +2938 -0
- package/templates/agent-docs/database/misc.md +270 -0
- package/templates/agent-docs/database/overview.md +108 -0
- package/templates/agent-docs/database/querying.md +1622 -0
- package/templates/agent-docs/database/scaling.md +331 -0
- package/templates/agent-docs/database/schema.md +1458 -0
- package/templates/agent-docs/database/seedsdialects.md +1235 -0
- package/templates/agent-docs/database/transactions.md +285 -0
- package/templates/agent-docs/deployment.md +999 -0
- package/templates/agent-docs/internationalization.md +359 -0
- package/templates/agent-docs/introduction.md +438 -0
- package/templates/agent-docs/multi-tenancy.md +610 -0
- package/templates/agent-docs/observability.md +350 -0
- package/templates/agent-docs/plugins.md +1273 -0
- package/templates/agent-docs/reference.md +978 -0
- package/templates/agent-docs/routing.md +1553 -0
- package/templates/agent-docs/scheduling.md +666 -0
- package/templates/agent-docs/schema-driven-ui.md +607 -0
- package/templates/agent-docs/security.md +42 -0
- package/templates/agent-docs/templates/apibackends.md +3214 -0
- package/templates/agent-docs/templates/appshells.md +2062 -0
- package/templates/agent-docs/templates/custom.md +128 -0
- package/templates/agent-docs/templates/overview.md +122 -0
- package/templates/agent-docs/templates/serverless.md +315 -0
- package/templates/agent-docs/testing.md +376 -0
- package/templates/agent-docs/workflows.md +1351 -0
- package/templates/apps/api-ai/README.md +105 -0
- package/templates/apps/api-ai/actions/summarize.action.server.tsx +32 -0
- package/templates/apps/api-ai/actions/summarize.action.ts +33 -0
- package/templates/apps/api-ai/agents/support.agent.server.tsx +31 -0
- package/templates/apps/api-ai/agents/support.agent.tsx +27 -0
- package/templates/apps/api-ai/app.config.ts +57 -0
- package/templates/apps/api-ai/database/schema.ts +67 -0
- package/templates/apps/api-ai/package.json +27 -0
- package/templates/apps/api-ai/seeds/docs.seed.ts +67 -0
- package/templates/apps/api-ai/template.json +6 -0
- package/templates/apps/api-ai/tests/summarize.test.ts +50 -0
- package/templates/apps/api-ai/tools/searchDocs.tool.tsx +60 -0
- package/templates/apps/api-ai/tsconfig.json +5 -0
- package/templates/apps/api-auth/.env +17 -0
- package/templates/apps/api-auth/README.md +109 -0
- package/templates/apps/api-auth/actions/me.action.server.ts +20 -0
- package/templates/apps/api-auth/actions/me.action.ts +19 -0
- package/templates/apps/api-auth/app.config.ts +59 -0
- package/templates/apps/api-auth/database/schema.ts +26 -0
- package/templates/apps/api-auth/package.json +28 -0
- package/templates/apps/api-auth/template.json +6 -0
- package/templates/apps/api-auth/tests/me.test.ts +37 -0
- package/templates/apps/api-auth/tsconfig.json +5 -0
- package/templates/apps/api-backend/README.md +25 -0
- package/templates/apps/api-backend/app.config.ts +39 -0
- package/templates/apps/api-backend/database/schema.ts +57 -0
- package/templates/apps/api-backend/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend/package.json +28 -0
- package/templates/apps/api-backend/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend/template.json +6 -0
- package/templates/apps/api-backend/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend/tsconfig.json +5 -0
- package/templates/apps/api-backend-deactivation/README.md +49 -0
- package/templates/apps/api-backend-deactivation/actions/users.get.action.server.ts +17 -0
- package/templates/apps/api-backend-deactivation/actions/users.get.action.ts +18 -0
- package/templates/apps/api-backend-deactivation/app.config.ts +20 -0
- package/templates/apps/api-backend-deactivation/database/schema.ts +41 -0
- package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.server.ts +6 -0
- package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.ts +16 -0
- package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.server.ts +10 -0
- package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.ts +19 -0
- package/templates/apps/api-backend-deactivation/package.json +28 -0
- package/templates/apps/api-backend-deactivation/template.json +6 -0
- package/templates/apps/api-backend-deactivation/tests/users.deactivate.test.ts +47 -0
- package/templates/apps/api-backend-deactivation/tsconfig.json +5 -0
- package/templates/apps/api-backend-mail/README.md +39 -0
- package/templates/apps/api-backend-mail/actions/sendWelcome.action.server.ts +15 -0
- package/templates/apps/api-backend-mail/actions/sendWelcome.action.ts +21 -0
- package/templates/apps/api-backend-mail/app.config.ts +34 -0
- package/templates/apps/api-backend-mail/database/schema.ts +57 -0
- package/templates/apps/api-backend-mail/emails/welcome.email.tsx +106 -0
- package/templates/apps/api-backend-mail/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-mail/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-mail/package.json +30 -0
- package/templates/apps/api-backend-mail/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-mail/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-mail/template.json +6 -0
- package/templates/apps/api-backend-mail/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-mail/tsconfig.json +5 -0
- package/templates/apps/api-backend-mariadb/.env.example +35 -0
- package/templates/apps/api-backend-mariadb/README.md +31 -0
- package/templates/apps/api-backend-mariadb/app.config.ts +68 -0
- package/templates/apps/api-backend-mariadb/database/schema.ts +57 -0
- package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-mariadb/package.json +30 -0
- package/templates/apps/api-backend-mariadb/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-mariadb/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-mariadb/template.json +6 -0
- package/templates/apps/api-backend-mariadb/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-mariadb/tsconfig.json +5 -0
- package/templates/apps/api-backend-storage/README.md +86 -0
- package/templates/apps/api-backend-storage/actions/uploadAvatar.action.server.ts +21 -0
- package/templates/apps/api-backend-storage/actions/uploadAvatar.action.ts +21 -0
- package/templates/apps/api-backend-storage/actions/uploadDocument.action.server.ts +21 -0
- package/templates/apps/api-backend-storage/actions/uploadDocument.action.ts +20 -0
- package/templates/apps/api-backend-storage/app.config.ts +38 -0
- package/templates/apps/api-backend-storage/database/schema.ts +57 -0
- package/templates/apps/api-backend-storage/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-storage/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-storage/package.json +27 -0
- package/templates/apps/api-backend-storage/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-storage/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-storage/template.json +6 -0
- package/templates/apps/api-backend-storage/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-storage/tsconfig.json +5 -0
- package/templates/apps/api-data-advanced/.env +17 -0
- package/templates/apps/api-data-advanced/README.md +88 -0
- package/templates/apps/api-data-advanced/app.config.ts +56 -0
- package/templates/apps/api-data-advanced/database/actors.entity.ts +14 -0
- package/templates/apps/api-data-advanced/database/authors.entity.ts +30 -0
- package/templates/apps/api-data-advanced/database/authors.relations.ts +15 -0
- package/templates/apps/api-data-advanced/database/books.entity.ts +59 -0
- package/templates/apps/api-data-advanced/database/books.relations.ts +10 -0
- package/templates/apps/api-data-advanced/database/index.ts +23 -0
- package/templates/apps/api-data-advanced/database/tenants.entity.ts +11 -0
- package/templates/apps/api-data-advanced/package.json +28 -0
- package/templates/apps/api-data-advanced/queries/authors.withBooks.query.server.ts +18 -0
- package/templates/apps/api-data-advanced/queries/authors.withBooks.query.ts +40 -0
- package/templates/apps/api-data-advanced/queries/books.search.query.server.ts +17 -0
- package/templates/apps/api-data-advanced/queries/books.search.query.ts +39 -0
- package/templates/apps/api-data-advanced/seeds/catalog.seed.ts +101 -0
- package/templates/apps/api-data-advanced/template.json +6 -0
- package/templates/apps/api-data-advanced/tests/queries.test.ts +124 -0
- package/templates/apps/api-data-advanced/tsconfig.json +5 -0
- package/templates/apps/api-durable/README.md +81 -0
- package/templates/apps/api-durable/aggregates/orderStats.aggregate.ts +49 -0
- package/templates/apps/api-durable/app.config.ts +41 -0
- package/templates/apps/api-durable/database/schema.ts +65 -0
- package/templates/apps/api-durable/mutations/orders.approve.mutation.server.ts +43 -0
- package/templates/apps/api-durable/mutations/orders.approve.mutation.ts +23 -0
- package/templates/apps/api-durable/mutations/orders.place.mutation.server.ts +45 -0
- package/templates/apps/api-durable/mutations/orders.place.mutation.ts +40 -0
- package/templates/apps/api-durable/package.json +28 -0
- package/templates/apps/api-durable/schedules/nightlyReport.cron.tsx +37 -0
- package/templates/apps/api-durable/startup/warm.startup.tsx +33 -0
- package/templates/apps/api-durable/subscribers/orderChanges.subscribe.ts +27 -0
- package/templates/apps/api-durable/template.json +6 -0
- package/templates/apps/api-durable/tests/orders.place.test.ts +91 -0
- package/templates/apps/api-durable/triggers/order.placed.trigger.tsx +21 -0
- package/templates/apps/api-durable/tsconfig.json +5 -0
- package/templates/apps/api-durable/workflows/order.fulfill.workflow.server.tsx +103 -0
- package/templates/apps/api-durable/workflows/order.fulfill.workflow.tsx +25 -0
- package/templates/apps/api-feature-flags/README.md +63 -0
- package/templates/apps/api-feature-flags/actions/notes.export.action.server.ts +11 -0
- package/templates/apps/api-feature-flags/actions/notes.export.action.ts +18 -0
- package/templates/apps/api-feature-flags/app.config.ts +41 -0
- package/templates/apps/api-feature-flags/database/schema.ts +37 -0
- package/templates/apps/api-feature-flags/mutations/notes.create.mutation.server.ts +29 -0
- package/templates/apps/api-feature-flags/mutations/notes.create.mutation.ts +28 -0
- package/templates/apps/api-feature-flags/package.json +29 -0
- package/templates/apps/api-feature-flags/template.json +6 -0
- package/templates/apps/api-feature-flags/tests/notes.create.test.ts +67 -0
- package/templates/apps/api-feature-flags/tsconfig.json +5 -0
- package/templates/apps/api-governance/.env +4 -0
- package/templates/apps/api-governance/README.md +61 -0
- package/templates/apps/api-governance/actions/profiles.get.action.server.ts +17 -0
- package/templates/apps/api-governance/actions/profiles.get.action.ts +17 -0
- package/templates/apps/api-governance/app.config.ts +35 -0
- package/templates/apps/api-governance/database/schema.ts +41 -0
- package/templates/apps/api-governance/mutations/profiles.create.mutation.server.ts +17 -0
- package/templates/apps/api-governance/mutations/profiles.create.mutation.ts +21 -0
- package/templates/apps/api-governance/package.json +29 -0
- package/templates/apps/api-governance/template.json +6 -0
- package/templates/apps/api-governance/tests/profiles.create.test.ts +59 -0
- package/templates/apps/api-governance/tsconfig.json +5 -0
- package/templates/apps/api-kv/README.md +100 -0
- package/templates/apps/api-kv/actions/sync.pull.action.server.ts +75 -0
- package/templates/apps/api-kv/actions/sync.pull.action.ts +25 -0
- package/templates/apps/api-kv/actions/sync.reset.action.server.ts +25 -0
- package/templates/apps/api-kv/actions/sync.reset.action.ts +22 -0
- package/templates/apps/api-kv/actions/sync.status.action.server.ts +28 -0
- package/templates/apps/api-kv/actions/sync.status.action.ts +17 -0
- package/templates/apps/api-kv/app.config.ts +48 -0
- package/templates/apps/api-kv/database/schema.ts +62 -0
- package/templates/apps/api-kv/package.json +28 -0
- package/templates/apps/api-kv/queries/events.list.query.server.ts +14 -0
- package/templates/apps/api-kv/queries/events.list.query.ts +23 -0
- package/templates/apps/api-kv/template.json +6 -0
- package/templates/apps/api-kv/tests/sync.test.ts +103 -0
- package/templates/apps/api-kv/tsconfig.json +5 -0
- package/templates/apps/api-moderation/README.md +46 -0
- package/templates/apps/api-moderation/app.config.ts +33 -0
- package/templates/apps/api-moderation/database/schema.ts +45 -0
- package/templates/apps/api-moderation/mutations/comments.create.mutation.server.ts +8 -0
- package/templates/apps/api-moderation/mutations/comments.create.mutation.ts +20 -0
- package/templates/apps/api-moderation/mutations/posts.create.mutation.server.ts +9 -0
- package/templates/apps/api-moderation/mutations/posts.create.mutation.ts +25 -0
- package/templates/apps/api-moderation/package.json +29 -0
- package/templates/apps/api-moderation/template.json +6 -0
- package/templates/apps/api-moderation/tests/posts.create.test.ts +79 -0
- package/templates/apps/api-moderation/tsconfig.json +5 -0
- package/templates/apps/api-observability/README.md +106 -0
- package/templates/apps/api-observability/app.config.ts +32 -0
- package/templates/apps/api-observability/database/schema.ts +38 -0
- package/templates/apps/api-observability/mutations/notes.create.mutation.server.ts +33 -0
- package/templates/apps/api-observability/mutations/notes.create.mutation.ts +15 -0
- package/templates/apps/api-observability/package.json +29 -0
- package/templates/apps/api-observability/queries/notes.list.query.server.ts +7 -0
- package/templates/apps/api-observability/queries/notes.list.query.ts +14 -0
- package/templates/apps/api-observability/template.json +6 -0
- package/templates/apps/api-observability/tests/notes.create.test.ts +36 -0
- package/templates/apps/api-observability/tsconfig.json +5 -0
- package/templates/apps/api-ratelimit/README.md +49 -0
- package/templates/apps/api-ratelimit/app.config.ts +42 -0
- package/templates/apps/api-ratelimit/database/schema.ts +37 -0
- package/templates/apps/api-ratelimit/mutations/notes.create.mutation.server.ts +16 -0
- package/templates/apps/api-ratelimit/mutations/notes.create.mutation.ts +34 -0
- package/templates/apps/api-ratelimit/package.json +29 -0
- package/templates/apps/api-ratelimit/template.json +6 -0
- package/templates/apps/api-ratelimit/tests/notes.create.test.ts +69 -0
- package/templates/apps/api-ratelimit/tsconfig.json +5 -0
- package/templates/apps/api-rbac/README.md +59 -0
- package/templates/apps/api-rbac/app.config.ts +48 -0
- package/templates/apps/api-rbac/database/schema.ts +37 -0
- package/templates/apps/api-rbac/mutations/notes.create.mutation.server.ts +22 -0
- package/templates/apps/api-rbac/mutations/notes.create.mutation.ts +26 -0
- package/templates/apps/api-rbac/package.json +29 -0
- package/templates/apps/api-rbac/template.json +6 -0
- package/templates/apps/api-rbac/tests/notes.create.test.ts +80 -0
- package/templates/apps/api-rbac/tsconfig.json +5 -0
- package/templates/apps/api-rest/README.md +85 -0
- package/templates/apps/api-rest/app.config.ts +70 -0
- package/templates/apps/api-rest/database/schema.ts +52 -0
- package/templates/apps/api-rest/lib/product.ts +30 -0
- package/templates/apps/api-rest/package.json +27 -0
- package/templates/apps/api-rest/routes/v1/products.create.route.tsx +45 -0
- package/templates/apps/api-rest/routes/v1/products.delete.route.tsx +25 -0
- package/templates/apps/api-rest/routes/v1/products.get.route.tsx +29 -0
- package/templates/apps/api-rest/routes/v1/products.list.route.tsx +43 -0
- package/templates/apps/api-rest/template.json +6 -0
- package/templates/apps/api-rest/tests/products.create.test.ts +53 -0
- package/templates/apps/api-rest/tsconfig.json +5 -0
- package/templates/apps/api-saas/README.md +106 -0
- package/templates/apps/api-saas/app.config.ts +51 -0
- package/templates/apps/api-saas/database/schema.ts +50 -0
- package/templates/apps/api-saas/mutations/projects.create.mutation.server.ts +52 -0
- package/templates/apps/api-saas/mutations/projects.create.mutation.ts +18 -0
- package/templates/apps/api-saas/package.json +31 -0
- package/templates/apps/api-saas/queries/projects.list.query.server.ts +7 -0
- package/templates/apps/api-saas/queries/projects.list.query.ts +18 -0
- package/templates/apps/api-saas/template.json +6 -0
- package/templates/apps/api-saas/tests/projects.create.test.ts +47 -0
- package/templates/apps/api-saas/tsconfig.json +5 -0
- package/templates/apps/api-search/README.md +68 -0
- package/templates/apps/api-search/app.config.ts +31 -0
- package/templates/apps/api-search/database/schema.ts +47 -0
- package/templates/apps/api-search/lib/search.ts +27 -0
- package/templates/apps/api-search/mutations/articles.create.mutation.server.ts +19 -0
- package/templates/apps/api-search/mutations/articles.create.mutation.ts +35 -0
- package/templates/apps/api-search/package.json +29 -0
- package/templates/apps/api-search/queries/articles.list.query.server.ts +14 -0
- package/templates/apps/api-search/queries/articles.list.query.ts +19 -0
- package/templates/apps/api-search/seeds/articles.seed.ts +41 -0
- package/templates/apps/api-search/startup/searchBackfill.startup.tsx +24 -0
- package/templates/apps/api-search/template.json +6 -0
- package/templates/apps/api-search/tests/articles.create.test.ts +69 -0
- package/templates/apps/api-search/tsconfig.json +5 -0
- package/templates/apps/api-versioning/README.md +51 -0
- package/templates/apps/api-versioning/actions/documents.asOf.action.server.ts +13 -0
- package/templates/apps/api-versioning/actions/documents.asOf.action.ts +14 -0
- package/templates/apps/api-versioning/actions/documents.history.action.server.ts +15 -0
- package/templates/apps/api-versioning/actions/documents.history.action.ts +17 -0
- package/templates/apps/api-versioning/app.config.ts +24 -0
- package/templates/apps/api-versioning/database/schema.ts +38 -0
- package/templates/apps/api-versioning/mutations/documents.create.mutation.server.ts +8 -0
- package/templates/apps/api-versioning/mutations/documents.create.mutation.ts +21 -0
- package/templates/apps/api-versioning/mutations/documents.update.mutation.server.ts +9 -0
- package/templates/apps/api-versioning/mutations/documents.update.mutation.ts +21 -0
- package/templates/apps/api-versioning/package.json +29 -0
- package/templates/apps/api-versioning/template.json +6 -0
- package/templates/apps/api-versioning/tests/documents.create.test.ts +37 -0
- package/templates/apps/api-versioning/tsconfig.json +5 -0
- package/templates/apps/api-webhooks/.env +6 -0
- package/templates/apps/api-webhooks/README.md +106 -0
- package/templates/apps/api-webhooks/app.config.ts +16 -0
- package/templates/apps/api-webhooks/database/schema.ts +49 -0
- package/templates/apps/api-webhooks/events/order.completed.webhook.tsx +22 -0
- package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.server.ts +37 -0
- package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.ts +15 -0
- package/templates/apps/api-webhooks/package.json +28 -0
- package/templates/apps/api-webhooks/queries/orders.list.query.server.ts +7 -0
- package/templates/apps/api-webhooks/queries/orders.list.query.ts +15 -0
- package/templates/apps/api-webhooks/template.json +6 -0
- package/templates/apps/api-webhooks/tests/orders.fulfill.test.ts +51 -0
- package/templates/apps/api-webhooks/tsconfig.json +5 -0
- package/templates/apps/api-webhooks/webhooks/orders.webhook.tsx +35 -0
- package/templates/apps/changelog/README.md +77 -0
- package/templates/apps/changelog/app.config.ts +27 -0
- package/templates/apps/changelog/content/releases/0.1.0.mdx +19 -0
- package/templates/apps/changelog/content/releases/0.2.0.mdx +28 -0
- package/templates/apps/changelog/package.json +30 -0
- package/templates/apps/changelog/scripts/generate-rss.mjs +38 -0
- package/templates/apps/changelog/src/globals.css +66 -0
- package/templates/apps/changelog/src/globals.d.ts +16 -0
- package/templates/apps/changelog/src/lib/locale.test.ts +72 -0
- package/templates/apps/changelog/src/lib/locale.ts +55 -0
- package/templates/apps/changelog/src/lib/releases.ts +21 -0
- package/templates/apps/changelog/src/locales/de.ts +22 -0
- package/templates/apps/changelog/src/locales/en.ts +30 -0
- package/templates/apps/changelog/src/pages/[locale]/[slug].tsx +24 -0
- package/templates/apps/changelog/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +95 -0
- package/templates/apps/changelog/src/pages/[slug].test.tsx +127 -0
- package/templates/apps/changelog/src/pages/[slug].tsx +52 -0
- package/templates/apps/changelog/src/pages/index.test.tsx +112 -0
- package/templates/apps/changelog/src/pages/index.tsx +55 -0
- package/templates/apps/changelog/src/pages/layout.tsx +84 -0
- package/templates/apps/changelog/template.json +6 -0
- package/templates/apps/changelog/tsconfig.json +11 -0
- package/templates/apps/edge-functions/README.md +55 -0
- package/templates/apps/edge-functions/functions/aiComplete.serverless.ts +58 -0
- package/templates/apps/edge-functions/functions/currencyConvert.serverless.ts +47 -0
- package/templates/apps/edge-functions/functions/geoGreeting.serverless.ts +44 -0
- package/templates/apps/edge-functions/functions/health.serverless.ts +29 -0
- package/templates/apps/edge-functions/functions/resolveLink.serverless.ts +32 -0
- package/templates/apps/edge-functions/functions/shareLink.serverless.ts +30 -0
- package/templates/apps/edge-functions/functions/slackNotify.serverless.ts +40 -0
- package/templates/apps/edge-functions/functions/verifySignature.serverless.ts +52 -0
- package/templates/apps/edge-functions/package.json +21 -0
- package/templates/apps/edge-functions/template.json +6 -0
- package/templates/apps/edge-functions/tsconfig.json +10 -0
- package/templates/apps/frontend-admin/README.md +40 -0
- package/templates/apps/frontend-admin/app.config.ts +37 -0
- package/templates/apps/frontend-admin/package.json +30 -0
- package/templates/apps/frontend-admin/src/config.ts +8 -0
- package/templates/apps/frontend-admin/src/globals.css +76 -0
- package/templates/apps/frontend-admin/src/globals.d.ts +6 -0
- package/templates/apps/frontend-admin/src/lib/admin.ts +16 -0
- package/templates/apps/frontend-admin/src/lib/auth.ts +24 -0
- package/templates/apps/frontend-admin/src/locales/de.ts +67 -0
- package/templates/apps/frontend-admin/src/locales/en.ts +79 -0
- package/templates/apps/frontend-admin/src/locales/index.ts +15 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/index.test.tsx +55 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/index.tsx +32 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/layout.tsx +29 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/login.test.tsx +73 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/login.tsx +35 -0
- package/templates/apps/frontend-admin/src/pages/admin/[entity].tsx +121 -0
- package/templates/apps/frontend-admin/src/pages/admin/entity.test.tsx +119 -0
- package/templates/apps/frontend-admin/src/pages/admin/error.tsx +20 -0
- package/templates/apps/frontend-admin/src/pages/admin/fallbacks.test.tsx +68 -0
- package/templates/apps/frontend-admin/src/pages/admin/index.test.tsx +88 -0
- package/templates/apps/frontend-admin/src/pages/admin/index.tsx +65 -0
- package/templates/apps/frontend-admin/src/pages/admin/layout.test.tsx +114 -0
- package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +97 -0
- package/templates/apps/frontend-admin/src/pages/admin/loading.tsx +15 -0
- package/templates/apps/frontend-admin/src/pages/admin/not-found.tsx +15 -0
- package/templates/apps/frontend-admin/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-admin/src/pages/layouts.test.tsx +54 -0
- package/templates/apps/frontend-admin/template.json +6 -0
- package/templates/apps/frontend-admin/tsconfig.json +11 -0
- package/templates/apps/frontend-app/README.md +98 -0
- package/templates/apps/frontend-app/app.config.ts +43 -0
- package/templates/apps/frontend-app/package.json +30 -0
- package/templates/apps/frontend-app/src/locales/de.ts +36 -0
- package/templates/apps/frontend-app/src/locales/en.ts +43 -0
- package/templates/apps/frontend-app/src/locales/index.ts +15 -0
- package/templates/apps/frontend-app/src/pages/index.test.tsx +167 -0
- package/templates/apps/frontend-app/src/pages/index.tsx +136 -0
- package/templates/apps/frontend-app/src/pages/layout.tsx +41 -0
- package/templates/apps/frontend-app/src/pages/schema-ui.test.tsx +99 -0
- package/templates/apps/frontend-app/src/pages/schema-ui.tsx +74 -0
- package/templates/apps/frontend-app/template.json +6 -0
- package/templates/apps/frontend-app/tsconfig.json +11 -0
- package/templates/apps/frontend-blank/README.md +18 -0
- package/templates/apps/frontend-blank/app.config.ts +29 -0
- package/templates/apps/frontend-blank/package.json +29 -0
- package/templates/apps/frontend-blank/src/locales/de.ts +15 -0
- package/templates/apps/frontend-blank/src/locales/en.ts +22 -0
- package/templates/apps/frontend-blank/src/locales/index.ts +15 -0
- package/templates/apps/frontend-blank/src/pages/index.test.tsx +55 -0
- package/templates/apps/frontend-blank/src/pages/index.tsx +27 -0
- package/templates/apps/frontend-blank/src/pages/layout.test.tsx +54 -0
- package/templates/apps/frontend-blank/src/pages/layout.tsx +35 -0
- package/templates/apps/frontend-blank/template.json +6 -0
- package/templates/apps/frontend-blank/tsconfig.json +11 -0
- package/templates/apps/frontend-contact/README.md +65 -0
- package/templates/apps/frontend-contact/app.config.ts +25 -0
- package/templates/apps/frontend-contact/functions/sendMessage.serverless.ts +69 -0
- package/templates/apps/frontend-contact/package.json +33 -0
- package/templates/apps/frontend-contact/src/components/ContactForm.island.test.tsx +142 -0
- package/templates/apps/frontend-contact/src/components/ContactForm.island.tsx +104 -0
- package/templates/apps/frontend-contact/src/config.ts +12 -0
- package/templates/apps/frontend-contact/src/globals.css +84 -0
- package/templates/apps/frontend-contact/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-contact/src/locales/de.ts +26 -0
- package/templates/apps/frontend-contact/src/locales/en.ts +29 -0
- package/templates/apps/frontend-contact/src/pages/[locale]/index.tsx +14 -0
- package/templates/apps/frontend-contact/src/pages/index.test.tsx +70 -0
- package/templates/apps/frontend-contact/src/pages/index.tsx +63 -0
- package/templates/apps/frontend-contact/src/pages/layout.tsx +65 -0
- package/templates/apps/frontend-contact/template.json +6 -0
- package/templates/apps/frontend-contact/tsconfig.json +11 -0
- package/templates/apps/frontend-dashboard/README.md +54 -0
- package/templates/apps/frontend-dashboard/app.config.ts +39 -0
- package/templates/apps/frontend-dashboard/package.json +29 -0
- package/templates/apps/frontend-dashboard/src/config.ts +8 -0
- package/templates/apps/frontend-dashboard/src/globals.css +72 -0
- package/templates/apps/frontend-dashboard/src/globals.d.ts +6 -0
- package/templates/apps/frontend-dashboard/src/lib/auth.ts +27 -0
- package/templates/apps/frontend-dashboard/src/locales/de.ts +49 -0
- package/templates/apps/frontend-dashboard/src/locales/en.ts +60 -0
- package/templates/apps/frontend-dashboard/src/locales/index.ts +15 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/index.test.tsx +55 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/index.tsx +34 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/layout.tsx +32 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/login.test.tsx +74 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/login.tsx +40 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/error.tsx +21 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/fallbacks.test.tsx +68 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/index.test.tsx +55 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/index.tsx +38 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.test.tsx +88 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.tsx +69 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/loading.tsx +21 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/not-found.tsx +19 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/settings.test.tsx +69 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/settings.tsx +35 -0
- package/templates/apps/frontend-dashboard/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-dashboard/src/pages/layouts.test.tsx +54 -0
- package/templates/apps/frontend-dashboard/template.json +6 -0
- package/templates/apps/frontend-dashboard/tsconfig.json +11 -0
- package/templates/apps/frontend-docs/README.md +19 -0
- package/templates/apps/frontend-docs/app.config.ts +25 -0
- package/templates/apps/frontend-docs/package.json +29 -0
- package/templates/apps/frontend-docs/src/globals.css +32 -0
- package/templates/apps/frontend-docs/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-docs/src/lib/locale.ts +56 -0
- package/templates/apps/frontend-docs/src/locales/de.ts +24 -0
- package/templates/apps/frontend-docs/src/locales/en.ts +28 -0
- package/templates/apps/frontend-docs/src/pages/[locale]/docs/[...slug].tsx +18 -0
- package/templates/apps/frontend-docs/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +67 -0
- package/templates/apps/frontend-docs/src/pages/docs/[...slug].test.tsx +115 -0
- package/templates/apps/frontend-docs/src/pages/docs/[...slug].tsx +82 -0
- package/templates/apps/frontend-docs/src/pages/index.test.tsx +90 -0
- package/templates/apps/frontend-docs/src/pages/index.tsx +45 -0
- package/templates/apps/frontend-docs/src/pages/layout.test.tsx +84 -0
- package/templates/apps/frontend-docs/src/pages/layout.tsx +66 -0
- package/templates/apps/frontend-docs/template.json +6 -0
- package/templates/apps/frontend-docs/tsconfig.json +11 -0
- package/templates/apps/frontend-i18n/README.md +61 -0
- package/templates/apps/frontend-i18n/app.config.ts +33 -0
- package/templates/apps/frontend-i18n/package.json +28 -0
- package/templates/apps/frontend-i18n/src/globals.css +46 -0
- package/templates/apps/frontend-i18n/src/globals.d.ts +6 -0
- package/templates/apps/frontend-i18n/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-i18n/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-i18n/src/locales/de.ts +24 -0
- package/templates/apps/frontend-i18n/src/locales/en.ts +25 -0
- package/templates/apps/frontend-i18n/src/pages/[locale]/about.tsx +11 -0
- package/templates/apps/frontend-i18n/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-i18n/src/pages/[locale]/mirrors.test.tsx +50 -0
- package/templates/apps/frontend-i18n/src/pages/about.test.tsx +75 -0
- package/templates/apps/frontend-i18n/src/pages/about.tsx +31 -0
- package/templates/apps/frontend-i18n/src/pages/index.test.tsx +102 -0
- package/templates/apps/frontend-i18n/src/pages/index.tsx +43 -0
- package/templates/apps/frontend-i18n/src/pages/layout.test.tsx +86 -0
- package/templates/apps/frontend-i18n/src/pages/layout.tsx +70 -0
- package/templates/apps/frontend-i18n/template.json +6 -0
- package/templates/apps/frontend-i18n/tsconfig.json +11 -0
- package/templates/apps/frontend-landing/README.md +17 -0
- package/templates/apps/frontend-landing/app.config.ts +25 -0
- package/templates/apps/frontend-landing/package.json +29 -0
- package/templates/apps/frontend-landing/src/globals.css +23 -0
- package/templates/apps/frontend-landing/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-landing/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-landing/src/locales/de.ts +24 -0
- package/templates/apps/frontend-landing/src/locales/en.ts +26 -0
- package/templates/apps/frontend-landing/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-landing/src/pages/[locale]/mirrors.test.tsx +37 -0
- package/templates/apps/frontend-landing/src/pages/index.test.tsx +110 -0
- package/templates/apps/frontend-landing/src/pages/index.tsx +75 -0
- package/templates/apps/frontend-landing/src/pages/layout.test.tsx +84 -0
- package/templates/apps/frontend-landing/src/pages/layout.tsx +66 -0
- package/templates/apps/frontend-landing/template.json +6 -0
- package/templates/apps/frontend-landing/tsconfig.json +11 -0
- package/templates/apps/frontend-spa/README.md +45 -0
- package/templates/apps/frontend-spa/app.config.ts +27 -0
- package/templates/apps/frontend-spa/package.json +29 -0
- package/templates/apps/frontend-spa/src/globals.css +84 -0
- package/templates/apps/frontend-spa/src/locales/de.ts +22 -0
- package/templates/apps/frontend-spa/src/locales/en.ts +29 -0
- package/templates/apps/frontend-spa/src/locales/index.ts +15 -0
- package/templates/apps/frontend-spa/src/pages/index.test.tsx +137 -0
- package/templates/apps/frontend-spa/src/pages/index.tsx +123 -0
- package/templates/apps/frontend-spa/src/pages/layout.tsx +27 -0
- package/templates/apps/frontend-spa/template.json +6 -0
- package/templates/apps/frontend-spa/tsconfig.json +11 -0
- package/templates/apps/frontend-ssr/README.md +68 -0
- package/templates/apps/frontend-ssr/app.config.ts +32 -0
- package/templates/apps/frontend-ssr/package.json +29 -0
- package/templates/apps/frontend-ssr/src/globals.css +67 -0
- package/templates/apps/frontend-ssr/src/locales/de.ts +41 -0
- package/templates/apps/frontend-ssr/src/locales/en.ts +54 -0
- package/templates/apps/frontend-ssr/src/locales/index.ts +16 -0
- package/templates/apps/frontend-ssr/src/pages/feed-swr.test.tsx +69 -0
- package/templates/apps/frontend-ssr/src/pages/feed-swr.tsx +54 -0
- package/templates/apps/frontend-ssr/src/pages/feed.test.tsx +73 -0
- package/templates/apps/frontend-ssr/src/pages/feed.tsx +64 -0
- package/templates/apps/frontend-ssr/src/pages/index.test.tsx +89 -0
- package/templates/apps/frontend-ssr/src/pages/index.tsx +72 -0
- package/templates/apps/frontend-ssr/src/pages/layout.tsx +37 -0
- package/templates/apps/frontend-ssr/template.json +6 -0
- package/templates/apps/frontend-ssr/tsconfig.json +11 -0
- package/templates/apps/frontend-ssr-api/README.md +50 -0
- package/templates/apps/frontend-ssr-api/app.config.ts +43 -0
- package/templates/apps/frontend-ssr-api/package.json +30 -0
- package/templates/apps/frontend-ssr-api/src/globals.css +38 -0
- package/templates/apps/frontend-ssr-api/src/globals.d.ts +6 -0
- package/templates/apps/frontend-ssr-api/src/locales/de.ts +20 -0
- package/templates/apps/frontend-ssr-api/src/locales/en.ts +31 -0
- package/templates/apps/frontend-ssr-api/src/locales/index.ts +16 -0
- package/templates/apps/frontend-ssr-api/src/pages/index.test.tsx +105 -0
- package/templates/apps/frontend-ssr-api/src/pages/index.tsx +83 -0
- package/templates/apps/frontend-ssr-api/src/pages/layout.tsx +28 -0
- package/templates/apps/frontend-ssr-api/template.json +6 -0
- package/templates/apps/frontend-ssr-api/tsconfig.json +11 -0
- package/templates/apps/frontend-static-blog/README.md +49 -0
- package/templates/apps/frontend-static-blog/app.config.ts +34 -0
- package/templates/apps/frontend-static-blog/package.json +28 -0
- package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.test.tsx +65 -0
- package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.tsx +35 -0
- package/templates/apps/frontend-static-blog/src/content/posts.ts +64 -0
- package/templates/apps/frontend-static-blog/src/globals.css +75 -0
- package/templates/apps/frontend-static-blog/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-static-blog/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-static-blog/src/locales/de.ts +19 -0
- package/templates/apps/frontend-static-blog/src/locales/en.ts +26 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/[slug].tsx +20 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +62 -0
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug].test.tsx +116 -0
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug].tsx +69 -0
- package/templates/apps/frontend-static-blog/src/pages/index.test.tsx +100 -0
- package/templates/apps/frontend-static-blog/src/pages/index.tsx +58 -0
- package/templates/apps/frontend-static-blog/src/pages/layout.tsx +63 -0
- package/templates/apps/frontend-static-blog/template.json +6 -0
- package/templates/apps/frontend-static-blog/tsconfig.json +11 -0
- package/templates/baselines/bare/.env.example +37 -0
- package/templates/baselines/bare/README.md +47 -0
- package/templates/baselines/bare/baseline.json +31 -0
- package/templates/baselines/bare/deploy/README.md +43 -0
- package/templates/baselines/bare/deploy/voltro.service.example +36 -0
- package/templates/baselines/compose/.env.example +46 -0
- package/templates/baselines/compose/README.md +69 -0
- package/templates/baselines/compose/baseline.json +51 -0
- package/templates/baselines/compose/docker/.dockerignore +38 -0
- package/templates/baselines/compose/docker/api.Dockerfile +57 -0
- package/templates/baselines/compose/docker/dev.Dockerfile +35 -0
- package/templates/baselines/compose/docker/web.Dockerfile +59 -0
- package/templates/baselines/compose/docker-compose.dev.yml +89 -0
- package/templates/baselines/compose/docker-compose.prod.yml +87 -0
- package/templates/baselines/compose/docker-compose.yml +41 -0
- package/templates/baselines/compose-mariadb/.env.example +57 -0
- package/templates/baselines/compose-mariadb/README.md +78 -0
- package/templates/baselines/compose-mariadb/baseline.json +51 -0
- package/templates/baselines/compose-mariadb/docker/.dockerignore +38 -0
- package/templates/baselines/compose-mariadb/docker/api.Dockerfile +57 -0
- package/templates/baselines/compose-mariadb/docker/dev.Dockerfile +35 -0
- package/templates/baselines/compose-mariadb/docker/mariadb-init.sql +6 -0
- package/templates/baselines/compose-mariadb/docker/web.Dockerfile +59 -0
- package/templates/baselines/compose-mariadb/docker-compose.dev.yml +117 -0
- package/templates/baselines/compose-mariadb/docker-compose.prod.yml +114 -0
- package/templates/baselines/compose-mariadb/docker-compose.yml +79 -0
- package/templates/baselines/helm/.env.example +39 -0
- package/templates/baselines/helm/README.md +98 -0
- package/templates/baselines/helm/baseline.json +53 -0
- package/templates/baselines/helm/charts/voltro-app/.helmignore +10 -0
- package/templates/baselines/helm/charts/voltro-app/Chart.yaml +10 -0
- package/templates/baselines/helm/charts/voltro-app/templates/_helpers.tpl +36 -0
- package/templates/baselines/helm/charts/voltro-app/templates/configmap.yaml +13 -0
- package/templates/baselines/helm/charts/voltro-app/templates/deployment-api.yaml +120 -0
- package/templates/baselines/helm/charts/voltro-app/templates/deployment-web.yaml +45 -0
- package/templates/baselines/helm/charts/voltro-app/templates/ingress.yaml +37 -0
- package/templates/baselines/helm/charts/voltro-app/templates/postgres-service.yaml +19 -0
- package/templates/baselines/helm/charts/voltro-app/templates/postgres-statefulset.yaml +73 -0
- package/templates/baselines/helm/charts/voltro-app/templates/secret.yaml +33 -0
- package/templates/baselines/helm/charts/voltro-app/templates/service-api.yaml +19 -0
- package/templates/baselines/helm/charts/voltro-app/templates/service-web.yaml +19 -0
- package/templates/baselines/helm/charts/voltro-app/values-dev.yaml +21 -0
- package/templates/baselines/helm/charts/voltro-app/values-prod.yaml +58 -0
- package/templates/baselines/helm/charts/voltro-app/values-staging.yaml +25 -0
- package/templates/baselines/helm/charts/voltro-app/values.yaml +109 -0
- package/templates/baselines/helm/deploy/README.md +94 -0
- package/templates/patches/@effect__cluster@0.59.0.patch +262 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Base catalog — the source of truth for this app's UI strings. Every key here
|
|
2
|
+
// MUST be mirrored in `de.ts` (and any other locale); `defineLocale<typeof en>()`
|
|
3
|
+
// enforces that parity at build time, so a missing translation fails typecheck
|
|
4
|
+
// rather than silently falling back.
|
|
5
|
+
//
|
|
6
|
+
// Messages use ICU syntax: `{name}` placeholders, `{n, plural, …}`, and rich
|
|
7
|
+
// tags like `<code>…</code>` (rendered by passing a matching function to
|
|
8
|
+
// `<T values={{ code: (c) => <code>{c}</code> }} />`).
|
|
9
|
+
|
|
10
|
+
import { defineCatalog } from '@voltro/i18n'
|
|
11
|
+
|
|
12
|
+
export default defineCatalog({
|
|
13
|
+
// Browser tab titles + description — locale-aware via each page's `meta({ locale })`.
|
|
14
|
+
'meta.landing.title': 'Admin',
|
|
15
|
+
'meta.landing.description': 'An auto-admin back-office that discovers your api’s entities and renders a live, permission-gated, undoable UI per entity.',
|
|
16
|
+
'meta.login.title': 'Sign in',
|
|
17
|
+
'meta.overview.title': 'Overview',
|
|
18
|
+
'meta.entity.title': 'Entity',
|
|
19
|
+
|
|
20
|
+
// ---- Marketing header ((marketing)/layout.tsx) ----
|
|
21
|
+
'nav.admin': 'Admin',
|
|
22
|
+
'nav.signIn': 'Sign in',
|
|
23
|
+
'lang.label': 'Language',
|
|
24
|
+
|
|
25
|
+
// ---- Public landing ((marketing)/index.tsx) ----
|
|
26
|
+
'landing.heading': 'The back-office, done for you.',
|
|
27
|
+
'landing.body': 'This admin reads your api’s <strong>capability map</strong> at runtime and renders a live, reactive <code>DataTable</code> + <code>AutoForm</code> for every entity it exposes — permission-gated with <code>useCan</code>, with a per-row provenance drawer and universal undo/redo. Point it at any Voltro api and it adapts; the page code under <code>src/pages/admin/</code> is yours to edit.',
|
|
28
|
+
'landing.cta': 'Sign in to the admin →',
|
|
29
|
+
|
|
30
|
+
// ---- Public sign-in ((marketing)/login.tsx) ----
|
|
31
|
+
'login.heading': 'Sign in',
|
|
32
|
+
'login.body': 'No real auth here — this button sets a demo session cookie so you can see the admin gate let you through.',
|
|
33
|
+
'login.cta': 'Continue as Demo Admin →',
|
|
34
|
+
|
|
35
|
+
// ---- Admin shell (admin/layout.tsx) ----
|
|
36
|
+
'admin.undo': '↶ Undo',
|
|
37
|
+
'admin.redo': '↷ Redo',
|
|
38
|
+
'admin.nav.overview': 'Overview',
|
|
39
|
+
'admin.nav.discovering': 'discovering…',
|
|
40
|
+
'admin.nav.backToSite': '← Back to site',
|
|
41
|
+
|
|
42
|
+
// ---- Admin overview (admin/index.tsx) ----
|
|
43
|
+
'overview.heading': 'Entities',
|
|
44
|
+
'overview.intro': 'Discovered from <code>app</code>’s capability map. Each card is a live, permission-gated CRUD view — no per-entity code.',
|
|
45
|
+
'overview.error': 'Couldn’t reach the api’s capability map. Is the api running with inspect enabled?',
|
|
46
|
+
'overview.columns': '{count, plural, one {# column} other {# columns}}',
|
|
47
|
+
'overview.reactiveSuffix': ' · reactive',
|
|
48
|
+
'overview.readonly': 'read-only',
|
|
49
|
+
'overview.empty': 'No user entities exposed by this api.',
|
|
50
|
+
|
|
51
|
+
// ---- Per-entity CRUD (admin/[entity].tsx) ----
|
|
52
|
+
'entity.create': 'Create',
|
|
53
|
+
'entity.addLabel': 'Add {table}',
|
|
54
|
+
'entity.rows': 'Rows',
|
|
55
|
+
'entity.noList': 'This entity exposes no list query.',
|
|
56
|
+
'entity.empty': 'No {table} yet.',
|
|
57
|
+
'entity.why': 'Why?',
|
|
58
|
+
'entity.delete': 'Delete',
|
|
59
|
+
'entity.unknown': 'No entity <code>{entity}</code> in this api’s capability map.',
|
|
60
|
+
|
|
61
|
+
// ---- Provenance drawer (admin/[entity].tsx) ----
|
|
62
|
+
'prov.title': 'Provenance · <code>{table}</code> #{id}',
|
|
63
|
+
'prov.close': 'close',
|
|
64
|
+
'prov.loading': 'loading…',
|
|
65
|
+
'prov.none': 'No provenance (is audit() on this table + inspect enabled?).',
|
|
66
|
+
'prov.lastWrite': 'last write',
|
|
67
|
+
'prov.byActor': 'by actor',
|
|
68
|
+
'prov.attribution': 'attribution',
|
|
69
|
+
'prov.via': ' (via {via})',
|
|
70
|
+
'prov.derived': 'derived',
|
|
71
|
+
'prov.yes': 'yes',
|
|
72
|
+
|
|
73
|
+
// ---- Scoped fallbacks (admin/error.tsx, not-found.tsx) ----
|
|
74
|
+
'error.heading': 'Something went wrong',
|
|
75
|
+
'error.retry': 'Try again',
|
|
76
|
+
'notFound.heading': 'Not found',
|
|
77
|
+
'notFound.body': 'That admin page doesn’t exist.',
|
|
78
|
+
'notFound.back': '← Back to overview',
|
|
79
|
+
} as const)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Catalog lookup OUTSIDE React — for a page's `meta({ locale })` locale-aware
|
|
2
|
+
// <title>. This app is cookie-i18n (no `/<locale>` URL prefix), so @voltro/web
|
|
3
|
+
// hands `meta` the active locale from the `voltro:lang` cookie. Inside React,
|
|
4
|
+
// use `<T>` / `useT()` via the framework's auto-wired <I18nProvider> instead.
|
|
5
|
+
|
|
6
|
+
import en from './en'
|
|
7
|
+
import de from './de'
|
|
8
|
+
|
|
9
|
+
// Both catalogs share `en`'s keys (defineLocale enforces the mirror), so a
|
|
10
|
+
// known-key lookup returns `string` — not `string | undefined` — which is what
|
|
11
|
+
// `PageMeta.title` requires under exactOptionalPropertyTypes.
|
|
12
|
+
export type Messages = Record<keyof typeof en, string>
|
|
13
|
+
|
|
14
|
+
export const getCatalog = (locale?: string): Messages =>
|
|
15
|
+
(locale === 'de' ? de : en) as Messages
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// `/` public landing — a static page with fixed content. Assert its render-mode
|
|
4
|
+
// + meta config and the hero copy / sign-in link.
|
|
5
|
+
|
|
6
|
+
import { afterEach, describe, expect, test } from 'vitest'
|
|
7
|
+
import { act, createElement, type ReactNode } from 'react'
|
|
8
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
9
|
+
import { I18nProvider } from '@voltro/i18n'
|
|
10
|
+
import enCatalog from '../../locales/en'
|
|
11
|
+
|
|
12
|
+
const { default: Landing, meta, renderMode } = await import('./index')
|
|
13
|
+
|
|
14
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
15
|
+
|
|
16
|
+
let container: HTMLDivElement
|
|
17
|
+
let root: Root
|
|
18
|
+
|
|
19
|
+
// The page uses <T>, so it needs an <I18nProvider> (the framework auto-wires
|
|
20
|
+
// this in the running app). Wrap with the real English catalog.
|
|
21
|
+
const render = (node: ReactNode): void => {
|
|
22
|
+
container = document.createElement('div')
|
|
23
|
+
document.body.appendChild(container)
|
|
24
|
+
act(() => {
|
|
25
|
+
root = createRoot(container)
|
|
26
|
+
root.render(
|
|
27
|
+
createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }),
|
|
28
|
+
)
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
if (root) act(() => root.unmount())
|
|
34
|
+
container?.remove()
|
|
35
|
+
document.body.innerHTML = ''
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('landing — config', () => {
|
|
39
|
+
test('pre-renders as a static page with title + description meta', () => {
|
|
40
|
+
expect(renderMode).toBe('static')
|
|
41
|
+
expect(meta({ locale: 'en' }).title).toBe('Admin')
|
|
42
|
+
expect(meta({ locale: 'en' }).description).toContain('back-office')
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('landing — render', () => {
|
|
47
|
+
test('renders the hero heading and a sign-in call to action', () => {
|
|
48
|
+
render(createElement(Landing))
|
|
49
|
+
expect(container.querySelector('h1')!.textContent).toContain('back-office')
|
|
50
|
+
const cta = Array.from(container.querySelectorAll('a')).find(
|
|
51
|
+
(a) => a.getAttribute('href') === '/login' && (a.textContent ?? '').includes('Sign in'),
|
|
52
|
+
)
|
|
53
|
+
expect(cta).toBeTruthy()
|
|
54
|
+
})
|
|
55
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Public landing — URL `/`. Pure static content; pre-renders to flat HTML.
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import type { PageMeta } from '@voltro/web'
|
|
4
|
+
import { T } from '@voltro/i18n'
|
|
5
|
+
import { getCatalog } from '../../locales'
|
|
6
|
+
|
|
7
|
+
export const renderMode = 'static' as const
|
|
8
|
+
|
|
9
|
+
export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({
|
|
10
|
+
title: getCatalog(locale)['meta.landing.title'],
|
|
11
|
+
description: getCatalog(locale)['meta.landing.description'],
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export default function Landing(): ReactNode {
|
|
15
|
+
return (
|
|
16
|
+
<section className="hero">
|
|
17
|
+
<h1><T id="landing.heading" /></h1>
|
|
18
|
+
<p className="muted">
|
|
19
|
+
<T
|
|
20
|
+
id="landing.body"
|
|
21
|
+
values={{
|
|
22
|
+
strong: (c: ReactNode) => <strong>{c}</strong>,
|
|
23
|
+
code: (c: ReactNode) => <code>{c}</code>,
|
|
24
|
+
}}
|
|
25
|
+
/>
|
|
26
|
+
</p>
|
|
27
|
+
<p style={{ marginTop: '1.5rem' }}>
|
|
28
|
+
<a className="btn" href="/login"><T id="landing.cta" /></a>
|
|
29
|
+
</p>
|
|
30
|
+
</section>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Layout for the SILENT route group `(marketing)/`. The parens mean the
|
|
2
|
+
// directory name never appears in a URL — the pages inside are `/` and
|
|
3
|
+
// `/login`. Gives the public pages a header distinct from the admin sidebar.
|
|
4
|
+
import type { ReactNode } from 'react'
|
|
5
|
+
import { T, useLocale, useT } from '@voltro/i18n'
|
|
6
|
+
import { LocaleSwitcher } from '@voltro/ui-shadcn'
|
|
7
|
+
import { APP_NAME } from '../../config'
|
|
8
|
+
|
|
9
|
+
const LOCALES = [
|
|
10
|
+
{ code: 'en', label: 'English' },
|
|
11
|
+
{ code: 'de', label: 'Deutsch' },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
export default function MarketingLayout({ children }: { readonly children: ReactNode }): ReactNode {
|
|
15
|
+
const locale = useLocale()
|
|
16
|
+
const langLabel = useT('lang.label')
|
|
17
|
+
return (
|
|
18
|
+
<div className="marketing">
|
|
19
|
+
<header>
|
|
20
|
+
<a className="brand" href="/">{APP_NAME}</a>
|
|
21
|
+
<span className="spacer" />
|
|
22
|
+
<a href="/admin"><T id="nav.admin" /></a>
|
|
23
|
+
<a href="/login"><T id="nav.signIn" /></a>
|
|
24
|
+
<LocaleSwitcher locales={LOCALES} current={locale} ariaLabel={langLabel} />
|
|
25
|
+
</header>
|
|
26
|
+
{children}
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// `/login` demo sign-in. Clicking "Continue" sets the demo session cookie and
|
|
4
|
+
// navigates to `?from=` (defaulting to /admin). We mock useNavigate from
|
|
5
|
+
// @voltro/web and drive the click.
|
|
6
|
+
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
8
|
+
import { act, createElement, type ReactNode } from 'react'
|
|
9
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
10
|
+
|
|
11
|
+
import { I18nProvider } from '@voltro/i18n'
|
|
12
|
+
import enCatalog from '../../locales/en'
|
|
13
|
+
|
|
14
|
+
const navigate = vi.fn<(to: string) => void>()
|
|
15
|
+
vi.mock('@voltro/web', () => ({ useNavigate: () => navigate }))
|
|
16
|
+
|
|
17
|
+
const { default: Login, meta, renderMode } = await import('./login')
|
|
18
|
+
|
|
19
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
20
|
+
|
|
21
|
+
let container: HTMLDivElement
|
|
22
|
+
let root: Root
|
|
23
|
+
|
|
24
|
+
// The page uses <T>, so wrap the render in an <I18nProvider> (auto-wired in app).
|
|
25
|
+
const render = (node: ReactNode): void => {
|
|
26
|
+
container = document.createElement('div')
|
|
27
|
+
document.body.appendChild(container)
|
|
28
|
+
act(() => {
|
|
29
|
+
root = createRoot(container)
|
|
30
|
+
root.render(
|
|
31
|
+
createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }),
|
|
32
|
+
)
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const clickContinue = (): void => {
|
|
37
|
+
const btn = container.querySelector('button')!
|
|
38
|
+
act(() => { btn.dispatchEvent(new MouseEvent('click', { bubbles: true })) })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
navigate.mockReset()
|
|
43
|
+
document.cookie = 'demo_session=; path=/; max-age=0'
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
if (root) act(() => root.unmount())
|
|
48
|
+
container?.remove()
|
|
49
|
+
document.body.innerHTML = ''
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('login — config', () => {
|
|
53
|
+
test('is a static page titled Sign in', () => {
|
|
54
|
+
expect(renderMode).toBe('static')
|
|
55
|
+
expect(meta({ locale: 'en' }).title).toBe('Sign in')
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('login — sign in', () => {
|
|
60
|
+
test('sets the session cookie and navigates to /admin by default', () => {
|
|
61
|
+
render(createElement(Login))
|
|
62
|
+
clickContinue()
|
|
63
|
+
expect(document.cookie).toContain('demo_session=ok')
|
|
64
|
+
expect(navigate).toHaveBeenCalledWith('/admin')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('honours the ?from= redirect target', () => {
|
|
68
|
+
window.history.replaceState({}, '', '/login?from=/admin/todos')
|
|
69
|
+
render(createElement(Login))
|
|
70
|
+
clickContinue()
|
|
71
|
+
expect(navigate).toHaveBeenCalledWith('/admin/todos')
|
|
72
|
+
})
|
|
73
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Public sign-in — URL `/login`. DEMO login: it just writes the cookie the
|
|
2
|
+
// admin gate looks for. A real app POSTs credentials to an api that returns a
|
|
3
|
+
// signed, HttpOnly session cookie — see AGENTS.md › Auth.
|
|
4
|
+
import type { ReactNode } from 'react'
|
|
5
|
+
import type { PageMeta } from '@voltro/web'
|
|
6
|
+
import { useNavigate } from '@voltro/web'
|
|
7
|
+
import { T } from '@voltro/i18n'
|
|
8
|
+
import { getCatalog } from '../../locales'
|
|
9
|
+
import { SESSION_COOKIE } from '../../lib/auth'
|
|
10
|
+
|
|
11
|
+
export const renderMode = 'static' as const
|
|
12
|
+
|
|
13
|
+
export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({
|
|
14
|
+
title: getCatalog(locale)['meta.login.title'],
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export default function Login(): ReactNode {
|
|
18
|
+
const navigate = useNavigate()
|
|
19
|
+
|
|
20
|
+
const onSignIn = (): void => {
|
|
21
|
+
document.cookie = `${SESSION_COOKIE}=ok; path=/; max-age=86400; samesite=lax`
|
|
22
|
+
const from = new URLSearchParams(window.location.search).get('from') ?? '/admin'
|
|
23
|
+
navigate(from)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<section className="hero">
|
|
28
|
+
<h1><T id="login.heading" /></h1>
|
|
29
|
+
<p className="muted"><T id="login.body" /></p>
|
|
30
|
+
<p style={{ marginTop: '1.5rem' }}>
|
|
31
|
+
<button type="button" className="btn" onClick={onSignIn}><T id="login.cta" /></button>
|
|
32
|
+
</p>
|
|
33
|
+
</section>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Per-entity CRUD — URL `/admin/<entity>`. Everything here is DISCOVERED:
|
|
2
|
+
// useCapabilityManifest → deriveEntityAdmins gives this entity's list query +
|
|
3
|
+
// create/update/delete mutation tags + the columns. We bind the shipped
|
|
4
|
+
// schema-driven components to those tags:
|
|
5
|
+
// - <DataTable api="app" query={listTag}/> — live, reactive rows
|
|
6
|
+
// - <AutoForm api="app" mutation={createTag}/> — fields from the input schema
|
|
7
|
+
// Writes are gated by useCan on the conventional `<table>:create|delete` scopes;
|
|
8
|
+
// each row has a provenance ("why is this here?") drawer (08) + delete; the
|
|
9
|
+
// layout's undo bar reverts the last change (00/09). Edit this file freely — it's
|
|
10
|
+
// your template code, not a sealed runtime feature.
|
|
11
|
+
import { useState, type ReactNode } from 'react'
|
|
12
|
+
import type { PageMeta } from '@voltro/web'
|
|
13
|
+
import { useParams, AutoForm, DataTable } from '@voltro/web'
|
|
14
|
+
import {
|
|
15
|
+
useCapabilityManifest,
|
|
16
|
+
deriveEntityAdmins,
|
|
17
|
+
useCan,
|
|
18
|
+
useMutation,
|
|
19
|
+
useProvenance,
|
|
20
|
+
type EntityAdminSpec,
|
|
21
|
+
} from '@voltro/client'
|
|
22
|
+
import { T, useTFn } from '@voltro/i18n'
|
|
23
|
+
import { getCatalog } from '../../locales'
|
|
24
|
+
|
|
25
|
+
export const renderMode = 'ssr' as const
|
|
26
|
+
|
|
27
|
+
export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({
|
|
28
|
+
title: getCatalog(locale)['meta.entity.title'],
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
type Row = Record<string, unknown>
|
|
32
|
+
|
|
33
|
+
// "Why is this value here?" — a one-shot provenance lookup (innovation/08).
|
|
34
|
+
// Its own component so useProvenance only runs while the drawer is open.
|
|
35
|
+
const ProvenanceDrawer = ({ table, id, onClose }: { readonly table: string; readonly id: string; readonly onClose: () => void }): ReactNode => {
|
|
36
|
+
const { data, loading, error } = useProvenance('app', table, id)
|
|
37
|
+
return (
|
|
38
|
+
<div className="drawer">
|
|
39
|
+
<h3>
|
|
40
|
+
<T id="prov.title" values={{ table, id, code: (c: ReactNode) => <code>{c}</code> }} />
|
|
41
|
+
{' '}
|
|
42
|
+
<button type="button" className="btn ghost" style={{ float: 'right' }} onClick={onClose}><T id="prov.close" /></button>
|
|
43
|
+
</h3>
|
|
44
|
+
{loading ? <p className="muted"><T id="prov.loading" /></p> : null}
|
|
45
|
+
{error ? <p className="muted"><T id="prov.none" /></p> : null}
|
|
46
|
+
{data ? (
|
|
47
|
+
<dl>
|
|
48
|
+
<dt><T id="prov.lastWrite" /></dt><dd>{data.lastWrite?.at ?? '—'}</dd>
|
|
49
|
+
<dt><T id="prov.byActor" /></dt><dd>{data.lastWrite?.actor?.id ?? '—'}{data.lastWrite?.actor?.via ? <T id="prov.via" values={{ via: data.lastWrite.actor.via }} /> : ''}</dd>
|
|
50
|
+
<dt><T id="prov.attribution" /></dt><dd>{data.attribution}</dd>
|
|
51
|
+
{data.derived ? <><dt><T id="prov.derived" /></dt><dd><T id="prov.yes" /></dd></> : null}
|
|
52
|
+
</dl>
|
|
53
|
+
) : null}
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// The bound CRUD surface for one entity. A child component so its hooks
|
|
59
|
+
// (useCan/useMutation) get the real spec, never undefined.
|
|
60
|
+
const EntityAdmin = ({ spec }: { readonly spec: EntityAdminSpec }): ReactNode => {
|
|
61
|
+
const [sel, setSel] = useState<string | null>(null)
|
|
62
|
+
const t = useTFn()
|
|
63
|
+
const canCreate = useCan(spec.createScope)
|
|
64
|
+
const canDelete = useCan(spec.deleteScope)
|
|
65
|
+
// Always-called; the tag is only used when deleteTag exists (gated below).
|
|
66
|
+
const del = useMutation<{ id: string }>('app', spec.deleteTag ?? '__voltro.noop')
|
|
67
|
+
|
|
68
|
+
const rowActions = (row: Row): ReactNode => {
|
|
69
|
+
const id = String(row['id'] ?? '')
|
|
70
|
+
return (
|
|
71
|
+
<span className="row-actions">
|
|
72
|
+
<button type="button" className="btn ghost" onClick={() => setSel(id)}><T id="entity.why" /></button>
|
|
73
|
+
{spec.deleteTag && canDelete && id ? (
|
|
74
|
+
<button type="button" className="btn danger" onClick={() => void del.mutate({ id })}><T id="entity.delete" /></button>
|
|
75
|
+
) : null}
|
|
76
|
+
</span>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<section>
|
|
82
|
+
<h1>{spec.table}</h1>
|
|
83
|
+
|
|
84
|
+
{spec.createTag && canCreate ? (
|
|
85
|
+
<div className="section">
|
|
86
|
+
<h2><T id="entity.create" /></h2>
|
|
87
|
+
<AutoForm api="app" mutation={spec.createTag} submitLabel={t('entity.addLabel', { table: spec.table })} />
|
|
88
|
+
</div>
|
|
89
|
+
) : null}
|
|
90
|
+
|
|
91
|
+
<div className="section">
|
|
92
|
+
<h2><T id="entity.rows" /></h2>
|
|
93
|
+
{spec.listTag ? (
|
|
94
|
+
<DataTable api="app" query={spec.listTag} rowActions={rowActions} emptyText={t('entity.empty', { table: spec.table })} />
|
|
95
|
+
) : (
|
|
96
|
+
<p className="muted"><T id="entity.noList" /></p>
|
|
97
|
+
)}
|
|
98
|
+
{sel ? <ProvenanceDrawer table={spec.table} id={sel} onClose={() => setSel(null)} /> : null}
|
|
99
|
+
</div>
|
|
100
|
+
</section>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export default function EntityPage(): ReactNode {
|
|
105
|
+
const { entity } = useParams<{ entity: string }>()
|
|
106
|
+
const { manifest, loading } = useCapabilityManifest('app')
|
|
107
|
+
const spec = manifest ? deriveEntityAdmins(manifest).find((e) => e.table === entity) : undefined
|
|
108
|
+
|
|
109
|
+
if (loading && !manifest) {
|
|
110
|
+
return <section><div className="skeleton" style={{ height: '8rem' }} /></section>
|
|
111
|
+
}
|
|
112
|
+
if (!spec) {
|
|
113
|
+
return (
|
|
114
|
+
<section>
|
|
115
|
+
<h1>{entity}</h1>
|
|
116
|
+
<p className="banner"><T id="entity.unknown" values={{ entity, code: (c: ReactNode) => <code>{c}</code> }} /></p>
|
|
117
|
+
</section>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
return <EntityAdmin spec={spec} />
|
|
121
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// `/admin/<entity>` — per-entity CRUD. It resolves the entity spec from the
|
|
4
|
+
// capability map (useParams + useCapabilityManifest + deriveEntityAdmins) and
|
|
5
|
+
// renders one of three observable states: a loading skeleton, an "unknown
|
|
6
|
+
// entity" banner, or the bound CRUD surface (heading + AutoForm + DataTable).
|
|
7
|
+
// We mock @voltro/web (useParams + the AutoForm/DataTable components) and
|
|
8
|
+
// @voltro/client (the discovery + gating hooks).
|
|
9
|
+
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
11
|
+
import { act, createElement, type ReactNode } from 'react'
|
|
12
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
13
|
+
import { I18nProvider } from '@voltro/i18n'
|
|
14
|
+
import enCatalog from '../../locales/en'
|
|
15
|
+
|
|
16
|
+
const params = vi.fn<() => { entity?: string }>()
|
|
17
|
+
|
|
18
|
+
vi.mock('@voltro/web', () => ({
|
|
19
|
+
useParams: () => params(),
|
|
20
|
+
AutoForm: ({ mutation }: { mutation: string }) =>
|
|
21
|
+
createElement('div', { 'data-testid': 'auto-form' }, mutation),
|
|
22
|
+
DataTable: ({ query }: { query: string }) =>
|
|
23
|
+
createElement('div', { 'data-testid': 'data-table' }, query),
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
interface ManifestState { manifest: unknown; loading: boolean }
|
|
27
|
+
const capabilityManifest = vi.fn<() => ManifestState>()
|
|
28
|
+
const entityAdmins = vi.fn<() => Array<Record<string, unknown>>>()
|
|
29
|
+
const can = vi.fn<() => boolean>()
|
|
30
|
+
|
|
31
|
+
vi.mock('@voltro/client', () => ({
|
|
32
|
+
useCapabilityManifest: () => capabilityManifest(),
|
|
33
|
+
deriveEntityAdmins: () => entityAdmins(),
|
|
34
|
+
useCan: () => can(),
|
|
35
|
+
useMutation: () => ({ mutate: vi.fn() }),
|
|
36
|
+
useProvenance: () => ({ data: undefined, loading: false, error: undefined }),
|
|
37
|
+
}))
|
|
38
|
+
|
|
39
|
+
const { default: EntityPage, meta, renderMode } = await import('./[entity]')
|
|
40
|
+
|
|
41
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
42
|
+
|
|
43
|
+
let container: HTMLDivElement
|
|
44
|
+
let root: Root
|
|
45
|
+
|
|
46
|
+
// The page uses <T>/useTFn, so wrap renders in the framework's <I18nProvider>.
|
|
47
|
+
const render = (node: ReactNode): void => {
|
|
48
|
+
container = document.createElement('div')
|
|
49
|
+
document.body.appendChild(container)
|
|
50
|
+
act(() => {
|
|
51
|
+
root = createRoot(container)
|
|
52
|
+
root.render(
|
|
53
|
+
createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }),
|
|
54
|
+
)
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const cell = (id: string): string | null =>
|
|
59
|
+
container.querySelector(`[data-testid="${id}"]`)?.textContent ?? null
|
|
60
|
+
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
params.mockReset()
|
|
63
|
+
capabilityManifest.mockReset()
|
|
64
|
+
entityAdmins.mockReset()
|
|
65
|
+
can.mockReset()
|
|
66
|
+
can.mockReturnValue(true)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
afterEach(() => {
|
|
70
|
+
if (root) act(() => root.unmount())
|
|
71
|
+
container?.remove()
|
|
72
|
+
document.body.innerHTML = ''
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('entity page — config', () => {
|
|
76
|
+
test('is an SSR page', () => {
|
|
77
|
+
expect(renderMode).toBe('ssr')
|
|
78
|
+
expect(meta({ locale: 'en' }).title).toBe('Entity')
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('entity page — render states', () => {
|
|
83
|
+
test('shows a skeleton while the manifest is still loading', () => {
|
|
84
|
+
params.mockReturnValue({ entity: 'todos' })
|
|
85
|
+
capabilityManifest.mockReturnValue({ manifest: undefined, loading: true })
|
|
86
|
+
entityAdmins.mockReturnValue([])
|
|
87
|
+
render(createElement(EntityPage))
|
|
88
|
+
expect(container.querySelector('.skeleton')).toBeTruthy()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('shows an unknown-entity banner when the entity is not in the map', () => {
|
|
92
|
+
params.mockReturnValue({ entity: 'ghosts' })
|
|
93
|
+
capabilityManifest.mockReturnValue({ manifest: {}, loading: false })
|
|
94
|
+
entityAdmins.mockReturnValue([{ table: 'todos' }])
|
|
95
|
+
render(createElement(EntityPage))
|
|
96
|
+
expect(container.querySelector('.banner')!.textContent).toContain('No entity')
|
|
97
|
+
expect(container.textContent).toContain('ghosts')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('renders the bound CRUD surface (heading + AutoForm + DataTable) for a known entity', () => {
|
|
101
|
+
params.mockReturnValue({ entity: 'todos' })
|
|
102
|
+
capabilityManifest.mockReturnValue({ manifest: {}, loading: false })
|
|
103
|
+
entityAdmins.mockReturnValue([
|
|
104
|
+
{
|
|
105
|
+
table: 'todos',
|
|
106
|
+
columns: ['id', 'title'],
|
|
107
|
+
listTag: 'todos.list',
|
|
108
|
+
createTag: 'todos.create',
|
|
109
|
+
createScope: 'todos:create',
|
|
110
|
+
deleteScope: 'todos:delete',
|
|
111
|
+
deleteTag: 'todos.delete',
|
|
112
|
+
},
|
|
113
|
+
])
|
|
114
|
+
render(createElement(EntityPage))
|
|
115
|
+
expect(container.querySelector('h1')!.textContent).toBe('todos')
|
|
116
|
+
expect(cell('auto-form')).toBe('todos.create')
|
|
117
|
+
expect(cell('data-table')).toBe('todos.list')
|
|
118
|
+
})
|
|
119
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Error boundary scoped to the /admin subtree. A render error (or a loader
|
|
2
|
+
// rejection that ISN'T a RedirectError / NotFoundError) bubbles to here instead
|
|
3
|
+
// of the router default. `reset` re-runs the failed segment.
|
|
4
|
+
import type { ReactNode } from 'react'
|
|
5
|
+
import type { ErrorBoundaryProps } from '@voltro/web'
|
|
6
|
+
import { T } from '@voltro/i18n'
|
|
7
|
+
|
|
8
|
+
export default function AdminError({ error, reset }: ErrorBoundaryProps): ReactNode {
|
|
9
|
+
return (
|
|
10
|
+
<div className="admin">
|
|
11
|
+
<main>
|
|
12
|
+
<h1><T id="error.heading" /></h1>
|
|
13
|
+
<p className="muted">{String(error)}</p>
|
|
14
|
+
<p style={{ marginTop: '1rem' }}>
|
|
15
|
+
<button type="button" className="btn" onClick={reset}><T id="error.retry" /></button>
|
|
16
|
+
</p>
|
|
17
|
+
</main>
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// Render smokes for the /admin subtree's scoped fallbacks: error.tsx (message +
|
|
4
|
+
// reset), loading.tsx (skeleton), not-found.tsx (message + back link). Pure
|
|
5
|
+
// presentational components, no framework hooks.
|
|
6
|
+
|
|
7
|
+
import { afterEach, describe, expect, test, vi } from 'vitest'
|
|
8
|
+
import { act, createElement, type ReactNode } from 'react'
|
|
9
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
10
|
+
import { I18nProvider } from '@voltro/i18n'
|
|
11
|
+
import enCatalog from '../../locales/en'
|
|
12
|
+
|
|
13
|
+
const { default: AdminError } = await import('./error')
|
|
14
|
+
const { default: AdminLoading } = await import('./loading')
|
|
15
|
+
const { default: AdminNotFound } = await import('./not-found')
|
|
16
|
+
|
|
17
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
18
|
+
|
|
19
|
+
let container: HTMLDivElement
|
|
20
|
+
let root: Root
|
|
21
|
+
|
|
22
|
+
// error.tsx / not-found.tsx use <T>, so wrap renders in the framework's
|
|
23
|
+
// <I18nProvider> (auto-wired in the running app).
|
|
24
|
+
const render = (node: ReactNode): void => {
|
|
25
|
+
container = document.createElement('div')
|
|
26
|
+
document.body.appendChild(container)
|
|
27
|
+
act(() => {
|
|
28
|
+
root = createRoot(container)
|
|
29
|
+
root.render(
|
|
30
|
+
createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }),
|
|
31
|
+
)
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
if (root) act(() => root.unmount())
|
|
37
|
+
container?.remove()
|
|
38
|
+
document.body.innerHTML = ''
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('admin error boundary', () => {
|
|
42
|
+
test('renders the error text and calls reset when Try again is clicked', () => {
|
|
43
|
+
const reset = vi.fn()
|
|
44
|
+
render(createElement(AdminError, { error: new Error('kaboom'), reset }))
|
|
45
|
+
expect(container.textContent).toContain('kaboom')
|
|
46
|
+
const btn = container.querySelector('button')!
|
|
47
|
+
act(() => { btn.dispatchEvent(new MouseEvent('click', { bubbles: true })) })
|
|
48
|
+
expect(reset).toHaveBeenCalledTimes(1)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('admin loading', () => {
|
|
53
|
+
test('renders skeleton placeholders', () => {
|
|
54
|
+
render(createElement(AdminLoading))
|
|
55
|
+
expect(container.querySelectorAll('.skeleton').length).toBeGreaterThan(0)
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('admin not-found', () => {
|
|
60
|
+
test('renders the not-found message and a link back to the overview', () => {
|
|
61
|
+
render(createElement(AdminNotFound))
|
|
62
|
+
expect(container.textContent).toContain('Not found')
|
|
63
|
+
const back = Array.from(container.querySelectorAll('a')).find(
|
|
64
|
+
(a) => a.getAttribute('href') === '/admin',
|
|
65
|
+
)
|
|
66
|
+
expect(back).toBeTruthy()
|
|
67
|
+
})
|
|
68
|
+
})
|