@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,88 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// `/admin` overview — renders one card per discovered entity (from the api
|
|
4
|
+
// capability map), an error banner if the map can't be reached, and a skeleton
|
|
5
|
+
// while loading. We mock @voltro/client so the three states are driveable.
|
|
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
|
+
import { I18nProvider } from '@voltro/i18n'
|
|
11
|
+
import enCatalog from '../../locales/en'
|
|
12
|
+
|
|
13
|
+
interface ManifestState { manifest: unknown; loading: boolean; error?: unknown }
|
|
14
|
+
const capabilityManifest = vi.fn<() => ManifestState>()
|
|
15
|
+
const entityAdmins = vi.fn<() => Array<Record<string, unknown>>>()
|
|
16
|
+
|
|
17
|
+
vi.mock('@voltro/client', () => ({
|
|
18
|
+
useCapabilityManifest: () => capabilityManifest(),
|
|
19
|
+
deriveEntityAdmins: () => entityAdmins(),
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
const { default: AdminOverview, meta, renderMode } = await import('./index')
|
|
23
|
+
|
|
24
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
25
|
+
|
|
26
|
+
let container: HTMLDivElement
|
|
27
|
+
let root: Root
|
|
28
|
+
|
|
29
|
+
// AdminOverview uses <T>, so wrap renders in the framework's <I18nProvider>.
|
|
30
|
+
const render = (node: ReactNode): void => {
|
|
31
|
+
container = document.createElement('div')
|
|
32
|
+
document.body.appendChild(container)
|
|
33
|
+
act(() => {
|
|
34
|
+
root = createRoot(container)
|
|
35
|
+
root.render(
|
|
36
|
+
createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }),
|
|
37
|
+
)
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
capabilityManifest.mockReset()
|
|
43
|
+
entityAdmins.mockReset()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
if (root) act(() => root.unmount())
|
|
48
|
+
container?.remove()
|
|
49
|
+
document.body.innerHTML = ''
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('admin overview — config', () => {
|
|
53
|
+
test('is an SSR page titled Overview', () => {
|
|
54
|
+
expect(renderMode).toBe('ssr')
|
|
55
|
+
expect(meta({ locale: 'en' }).title).toBe('Overview')
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('admin overview — render', () => {
|
|
60
|
+
test('renders a linked card per entity with its column count and ops', () => {
|
|
61
|
+
capabilityManifest.mockReturnValue({ manifest: {}, loading: false })
|
|
62
|
+
entityAdmins.mockReturnValue([
|
|
63
|
+
{ table: 'todos', columns: ['id', 'title'], reactive: true, listTag: 'todos.list', createTag: 'todos.create' },
|
|
64
|
+
{ table: 'notes', columns: ['id'], reactive: false, listTag: 'notes.list' },
|
|
65
|
+
])
|
|
66
|
+
|
|
67
|
+
render(createElement(AdminOverview))
|
|
68
|
+
const cards = Array.from(container.querySelectorAll('a.card'))
|
|
69
|
+
expect(cards.map((c) => c.getAttribute('href'))).toEqual(['/admin/todos', '/admin/notes'])
|
|
70
|
+
expect(container.textContent).toContain('2 columns · reactive')
|
|
71
|
+
expect(container.textContent).toContain('1 column')
|
|
72
|
+
expect(container.textContent).toContain('list · create')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('shows the error banner when the capability map is unreachable', () => {
|
|
76
|
+
capabilityManifest.mockReturnValue({ manifest: undefined, loading: false, error: 'boom' })
|
|
77
|
+
entityAdmins.mockReturnValue([])
|
|
78
|
+
render(createElement(AdminOverview))
|
|
79
|
+
expect(container.querySelector('.banner')!.textContent).toContain('Couldn’t reach')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('shows skeleton cards while the manifest is loading', () => {
|
|
83
|
+
capabilityManifest.mockReturnValue({ manifest: undefined, loading: true })
|
|
84
|
+
entityAdmins.mockReturnValue([])
|
|
85
|
+
render(createElement(AdminOverview))
|
|
86
|
+
expect(container.querySelectorAll('.skeleton').length).toBeGreaterThan(0)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Admin overview — URL `/admin`. `renderMode: 'ssr'` so the parent layout's
|
|
2
|
+
// auth gate runs per request. Renders one card per discovered entity, each
|
|
3
|
+
// linking to its CRUD page. The whole list comes from the api's capability map
|
|
4
|
+
// — there's no hard-coded entity list.
|
|
5
|
+
import type { ReactNode } from 'react'
|
|
6
|
+
import type { PageMeta } from '@voltro/web'
|
|
7
|
+
import { useCapabilityManifest, deriveEntityAdmins } from '@voltro/client'
|
|
8
|
+
import { T } from '@voltro/i18n'
|
|
9
|
+
import { getCatalog } from '../../locales'
|
|
10
|
+
|
|
11
|
+
export const renderMode = 'ssr' as const
|
|
12
|
+
|
|
13
|
+
export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({
|
|
14
|
+
title: getCatalog(locale)['meta.overview.title'],
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export default function AdminOverview(): ReactNode {
|
|
18
|
+
const { manifest, loading, error } = useCapabilityManifest('app')
|
|
19
|
+
const entities = manifest ? deriveEntityAdmins(manifest) : []
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<section>
|
|
23
|
+
<h1><T id="overview.heading" /></h1>
|
|
24
|
+
<p className="muted">
|
|
25
|
+
<T id="overview.intro" values={{ code: (c: ReactNode) => <code>{c}</code> }} />
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
{error ? (
|
|
29
|
+
<p className="banner" style={{ marginTop: '1rem' }}>
|
|
30
|
+
<T id="overview.error" />
|
|
31
|
+
</p>
|
|
32
|
+
) : null}
|
|
33
|
+
|
|
34
|
+
{loading && entities.length === 0 ? (
|
|
35
|
+
<div className="cards" style={{ marginTop: '1.25rem' }} aria-hidden="true">
|
|
36
|
+
<div className="skeleton" /><div className="skeleton" /><div className="skeleton" />
|
|
37
|
+
</div>
|
|
38
|
+
) : (
|
|
39
|
+
<div className="cards" style={{ marginTop: '1.25rem' }}>
|
|
40
|
+
{entities.map((e) => {
|
|
41
|
+
const ops = [
|
|
42
|
+
e.listTag && 'list',
|
|
43
|
+
e.createTag && 'create',
|
|
44
|
+
e.updateTag && 'edit',
|
|
45
|
+
e.deleteTag && 'delete',
|
|
46
|
+
].filter(Boolean)
|
|
47
|
+
return (
|
|
48
|
+
<a className="card" key={e.table} href={`/admin/${e.table}`}>
|
|
49
|
+
<div className="value">{e.table}</div>
|
|
50
|
+
<div className="label">
|
|
51
|
+
<T id="overview.columns" values={{ count: e.columns.length }} />
|
|
52
|
+
{e.reactive ? <T id="overview.reactiveSuffix" /> : null}
|
|
53
|
+
</div>
|
|
54
|
+
<div className="meta">{ops.length > 0 ? ops.join(' · ') : <T id="overview.readonly" />}</div>
|
|
55
|
+
</a>
|
|
56
|
+
)
|
|
57
|
+
})}
|
|
58
|
+
{entities.length === 0 && !loading ? (
|
|
59
|
+
<p className="muted"><T id="overview.empty" /></p>
|
|
60
|
+
) : null}
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
63
|
+
</section>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// The admin shell + AUTH GATE. The loader redirects an unauthenticated request
|
|
4
|
+
// and otherwise returns the shared user. The rendered shell derives its nav
|
|
5
|
+
// from the api capability map (useCapabilityManifest + deriveEntityAdmins) and
|
|
6
|
+
// wraps an undo/redo bar (useUndoLog). We mock @voltro/client so those hooks are
|
|
7
|
+
// deterministic, mock the framework hooks, and import the REAL RedirectError.
|
|
8
|
+
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
10
|
+
import { act, createElement, type ReactNode } from 'react'
|
|
11
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
12
|
+
import { RedirectError } from '@voltro/web'
|
|
13
|
+
import { I18nProvider } from '@voltro/i18n'
|
|
14
|
+
import enCatalog from '../../locales/en'
|
|
15
|
+
|
|
16
|
+
const loaderData = vi.fn<() => unknown>()
|
|
17
|
+
const location = vi.fn<() => string>()
|
|
18
|
+
|
|
19
|
+
vi.mock('@voltro/web', async () => {
|
|
20
|
+
const actual = await vi.importActual<typeof import('@voltro/web')>('@voltro/web')
|
|
21
|
+
return { ...actual, useLoaderData: () => loaderData(), useLocation: () => location() }
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
interface ManifestState { manifest: unknown; loading: boolean }
|
|
25
|
+
const capabilityManifest = vi.fn<() => ManifestState>()
|
|
26
|
+
const entityAdmins = vi.fn<() => Array<{ table: string }>>()
|
|
27
|
+
const undoLog = vi.fn(() => ({
|
|
28
|
+
canUndo: false,
|
|
29
|
+
canRedo: false,
|
|
30
|
+
undoLast: vi.fn(),
|
|
31
|
+
redoLast: vi.fn(),
|
|
32
|
+
}))
|
|
33
|
+
|
|
34
|
+
vi.mock('@voltro/client', () => ({
|
|
35
|
+
useCapabilityManifest: () => capabilityManifest(),
|
|
36
|
+
deriveEntityAdmins: () => entityAdmins(),
|
|
37
|
+
useUndoLog: () => undoLog(),
|
|
38
|
+
PermissionProvider: ({ children }: { children?: ReactNode }) => children,
|
|
39
|
+
}))
|
|
40
|
+
|
|
41
|
+
const { default: AdminLayout, loader } = await import('./layout')
|
|
42
|
+
|
|
43
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
44
|
+
|
|
45
|
+
let container: HTMLDivElement
|
|
46
|
+
let root: Root
|
|
47
|
+
|
|
48
|
+
// AdminLayout uses <T>/useLocale + <LocaleSwitcher>, so wrap renders in the
|
|
49
|
+
// framework's <I18nProvider> (auto-wired in the running app).
|
|
50
|
+
const render = (node: ReactNode): void => {
|
|
51
|
+
container = document.createElement('div')
|
|
52
|
+
document.body.appendChild(container)
|
|
53
|
+
act(() => {
|
|
54
|
+
root = createRoot(container)
|
|
55
|
+
root.render(
|
|
56
|
+
createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }),
|
|
57
|
+
)
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
loaderData.mockReset()
|
|
63
|
+
location.mockReset()
|
|
64
|
+
capabilityManifest.mockReset()
|
|
65
|
+
entityAdmins.mockReset()
|
|
66
|
+
undoLog.mockClear()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
afterEach(() => {
|
|
70
|
+
if (root) act(() => root.unmount())
|
|
71
|
+
container?.remove()
|
|
72
|
+
document.body.innerHTML = ''
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('admin layout — loader (auth gate)', () => {
|
|
76
|
+
test('redirects to /login (remembering the destination) when unauthenticated', async () => {
|
|
77
|
+
const err = await Promise.resolve(loader({ headers: {} } as never)).catch((e: unknown) => e)
|
|
78
|
+
expect(err).toBeInstanceOf(RedirectError)
|
|
79
|
+
expect((err as RedirectError).location).toBe('/login?from=/admin')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('returns the shared user for a signed-in request', async () => {
|
|
83
|
+
const data = await loader({ headers: { cookie: 'demo_session=ok' } } as never)
|
|
84
|
+
expect(data.user.name).toBe('Demo Admin')
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe('admin layout — render', () => {
|
|
89
|
+
test('renders a nav item per discovered entity plus the Overview link', () => {
|
|
90
|
+
loaderData.mockReturnValue({ user: { name: 'Root Admin' } })
|
|
91
|
+
location.mockReturnValue('/admin')
|
|
92
|
+
capabilityManifest.mockReturnValue({ manifest: {}, loading: false })
|
|
93
|
+
entityAdmins.mockReturnValue([{ table: 'todos' }, { table: 'projects' }])
|
|
94
|
+
|
|
95
|
+
render(createElement(AdminLayout, { children: createElement('p', null, 'admin body') }))
|
|
96
|
+
|
|
97
|
+
expect(container.textContent).toContain('Root Admin')
|
|
98
|
+
const hrefs = Array.from(container.querySelectorAll('a')).map((a) => a.getAttribute('href'))
|
|
99
|
+
expect(hrefs).toContain('/admin')
|
|
100
|
+
expect(hrefs).toContain('/admin/todos')
|
|
101
|
+
expect(hrefs).toContain('/admin/projects')
|
|
102
|
+
expect(container.querySelector('main')!.textContent).toContain('admin body')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('shows the discovering hint before the manifest arrives', () => {
|
|
106
|
+
loaderData.mockReturnValue({ user: { name: 'Root Admin' } })
|
|
107
|
+
location.mockReturnValue('/admin')
|
|
108
|
+
capabilityManifest.mockReturnValue({ manifest: undefined, loading: true })
|
|
109
|
+
entityAdmins.mockReturnValue([])
|
|
110
|
+
|
|
111
|
+
render(createElement(AdminLayout, { children: null }))
|
|
112
|
+
expect(container.textContent).toContain('discovering…')
|
|
113
|
+
})
|
|
114
|
+
})
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// The admin shell — and the AUTH GATE. This nested layout wraps every page
|
|
2
|
+
// under `/admin`, and its `loader` runs server-side BEFORE the page renders, so
|
|
3
|
+
// it's the right seam to validate the session. Throwing RedirectError
|
|
4
|
+
// short-circuits the subtree (303 + Location on SSR; replace-nav on the client).
|
|
5
|
+
// Pages under here are `renderMode: 'ssr'` so the gate runs per request.
|
|
6
|
+
//
|
|
7
|
+
// The sidebar is DRIVEN BY THE API'S CAPABILITY MAP: useCapabilityManifest +
|
|
8
|
+
// deriveEntityAdmins discover every entity at runtime, so point the admin at any
|
|
9
|
+
// api and the nav adapts — no per-entity code. The undo bar wraps the
|
|
10
|
+
// __voltro.undo.* built-ins (requires undo capture on; the dev default).
|
|
11
|
+
import type { ReactNode } from 'react'
|
|
12
|
+
import { RedirectError, useLoaderData, useLocation, type LoaderFn } from '@voltro/web'
|
|
13
|
+
import {
|
|
14
|
+
useCapabilityManifest,
|
|
15
|
+
deriveEntityAdmins,
|
|
16
|
+
useUndoLog,
|
|
17
|
+
PermissionProvider,
|
|
18
|
+
} from '@voltro/client'
|
|
19
|
+
import { T, useLocale, useT } from '@voltro/i18n'
|
|
20
|
+
import { LocaleSwitcher } from '@voltro/ui-shadcn'
|
|
21
|
+
import { APP_NAME } from '../../config'
|
|
22
|
+
import { isSignedIn } from '../../lib/auth'
|
|
23
|
+
import { DEMO_SCOPES } from '../../lib/admin'
|
|
24
|
+
|
|
25
|
+
const LOCALES = [
|
|
26
|
+
{ code: 'en', label: 'English' },
|
|
27
|
+
{ code: 'de', label: 'Deutsch' },
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
export const renderMode = 'ssr' as const
|
|
31
|
+
|
|
32
|
+
interface AdminData {
|
|
33
|
+
readonly user: { readonly name: string }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const loader: LoaderFn<AdminData> = async ({ headers }) => {
|
|
37
|
+
if (!isSignedIn(headers)) {
|
|
38
|
+
throw new RedirectError('/login?from=/admin')
|
|
39
|
+
}
|
|
40
|
+
return { user: { name: 'Demo Admin' } }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// The undo bar — split out so a deployment running with VOLTRO_UNDO=off (no
|
|
44
|
+
// __voltro.undo.* built-ins) can delete just these lines.
|
|
45
|
+
const UndoBar = (): ReactNode => {
|
|
46
|
+
const undo = useUndoLog('app')
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
<button type="button" className="btn ghost" disabled={!undo.canUndo} onClick={() => void undo.undoLast()}>
|
|
50
|
+
<T id="admin.undo" />
|
|
51
|
+
</button>
|
|
52
|
+
<button type="button" className="btn ghost" disabled={!undo.canRedo} onClick={() => void undo.redoLast()}>
|
|
53
|
+
<T id="admin.redo" />
|
|
54
|
+
</button>
|
|
55
|
+
</>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default function AdminLayout({ children }: { readonly children: ReactNode }): ReactNode {
|
|
60
|
+
const { user } = useLoaderData<AdminData>()
|
|
61
|
+
const pathname = useLocation()
|
|
62
|
+
const locale = useLocale()
|
|
63
|
+
const langLabel = useT('lang.label')
|
|
64
|
+
const { manifest, loading } = useCapabilityManifest('app')
|
|
65
|
+
const entities = manifest ? deriveEntityAdmins(manifest) : []
|
|
66
|
+
|
|
67
|
+
const navItem = (href: string, label: ReactNode): ReactNode => (
|
|
68
|
+
<a key={href} href={href} aria-current={pathname === href ? 'page' : undefined}>{label}</a>
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
// PermissionProvider feeds the current subject's scopes to useCan across the
|
|
73
|
+
// admin. DEMO scopes here — swap for your session's scopes (see lib/admin.ts).
|
|
74
|
+
<PermissionProvider scopes={DEMO_SCOPES}>
|
|
75
|
+
<div className="admin">
|
|
76
|
+
<aside>
|
|
77
|
+
<div className="brand">{APP_NAME}</div>
|
|
78
|
+
<nav>
|
|
79
|
+
{navItem('/admin', <T id="admin.nav.overview" />)}
|
|
80
|
+
{loading && entities.length === 0 ? <span className="muted" style={{ padding: '0.35rem 0.6rem', fontSize: '0.85rem' }}><T id="admin.nav.discovering" /></span> : null}
|
|
81
|
+
{entities.map((e) => navItem(`/admin/${e.table}`, e.table))}
|
|
82
|
+
<a href="/"><T id="admin.nav.backToSite" /></a>
|
|
83
|
+
</nav>
|
|
84
|
+
</aside>
|
|
85
|
+
<main>
|
|
86
|
+
<div className="topbar">
|
|
87
|
+
<strong>{user.name}</strong>
|
|
88
|
+
<span className="spacer" />
|
|
89
|
+
<UndoBar />
|
|
90
|
+
<LocaleSwitcher locales={LOCALES} current={locale} ariaLabel={langLabel} />
|
|
91
|
+
</div>
|
|
92
|
+
{children}
|
|
93
|
+
</main>
|
|
94
|
+
</div>
|
|
95
|
+
</PermissionProvider>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// OPT-IN loading skeleton for /admin pages. Suits a data-backed admin where the
|
|
2
|
+
// loader (auth gate) + the first capability-map fetch take a beat.
|
|
3
|
+
import type { ReactNode } from 'react'
|
|
4
|
+
|
|
5
|
+
export default function AdminLoading(): ReactNode {
|
|
6
|
+
return (
|
|
7
|
+
<div className="admin">
|
|
8
|
+
<main>
|
|
9
|
+
<div className="cards" aria-hidden="true">
|
|
10
|
+
<div className="skeleton" /><div className="skeleton" /><div className="skeleton" />
|
|
11
|
+
</div>
|
|
12
|
+
</main>
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Scoped 404 for the /admin subtree (e.g. /admin/nope when no page matches).
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import { T } from '@voltro/i18n'
|
|
4
|
+
|
|
5
|
+
export default function AdminNotFound(): ReactNode {
|
|
6
|
+
return (
|
|
7
|
+
<div className="admin">
|
|
8
|
+
<main>
|
|
9
|
+
<h1><T id="notFound.heading" /></h1>
|
|
10
|
+
<p className="muted"><T id="notFound.body" /></p>
|
|
11
|
+
<p style={{ marginTop: '1rem' }}><a href="/admin"><T id="notFound.back" /></a></p>
|
|
12
|
+
</main>
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Root layout — wraps EVERY page (marketing + admin). Intentionally thin: it
|
|
2
|
+
// loads the stylesheet and lets each AREA layout ((marketing)/layout.tsx,
|
|
3
|
+
// admin/layout.tsx) supply its own chrome. Layouts nest, so an admin page is
|
|
4
|
+
// wrapped root → admin.
|
|
5
|
+
//
|
|
6
|
+
// Don't render <html>/<head>/<body> here — the framework shell owns them.
|
|
7
|
+
import type { ReactNode } from 'react'
|
|
8
|
+
import '../globals.css'
|
|
9
|
+
|
|
10
|
+
export default function RootLayout({ children }: { readonly children: ReactNode }): ReactNode {
|
|
11
|
+
return <>{children}</>
|
|
12
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// Render smokes for the root layout (passes children through) and the
|
|
4
|
+
// (marketing) group layout (brand header + nav links wrapping the children).
|
|
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: RootLayout } = await import('./layout')
|
|
13
|
+
const { default: MarketingLayout } = await import('./(marketing)/layout')
|
|
14
|
+
|
|
15
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
16
|
+
|
|
17
|
+
let container: HTMLDivElement
|
|
18
|
+
let root: Root
|
|
19
|
+
|
|
20
|
+
// MarketingLayout uses <T>/useLocale + <LocaleSwitcher>, so wrap renders in the
|
|
21
|
+
// framework's <I18nProvider> (auto-wired in the running app).
|
|
22
|
+
const render = (node: ReactNode): void => {
|
|
23
|
+
container = document.createElement('div')
|
|
24
|
+
document.body.appendChild(container)
|
|
25
|
+
act(() => {
|
|
26
|
+
root = createRoot(container)
|
|
27
|
+
root.render(
|
|
28
|
+
createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }),
|
|
29
|
+
)
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
if (root) act(() => root.unmount())
|
|
35
|
+
container?.remove()
|
|
36
|
+
document.body.innerHTML = ''
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('root layout', () => {
|
|
40
|
+
test('renders its children unchanged', () => {
|
|
41
|
+
render(createElement(RootLayout, { children: createElement('p', null, 'hello') }))
|
|
42
|
+
expect(container.textContent).toContain('hello')
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('marketing layout', () => {
|
|
47
|
+
test('renders the header nav (Admin + Sign in) around the children', () => {
|
|
48
|
+
render(createElement(MarketingLayout, { children: createElement('p', null, 'body') }))
|
|
49
|
+
const hrefs = Array.from(container.querySelectorAll('a')).map((a) => a.getAttribute('href'))
|
|
50
|
+
expect(hrefs).toContain('/admin')
|
|
51
|
+
expect(hrefs).toContain('/login')
|
|
52
|
+
expect(container.textContent).toContain('body')
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "frontend-admin",
|
|
3
|
+
"kind": "web",
|
|
4
|
+
"summary": "An auto-admin back-office that DISCOVERS your api's entities at runtime (capability map) and renders a live, reactive <DataTable> + <AutoForm> per entity — permission-gated with useCan, with a per-row provenance drawer and universal undo/redo. Editable template code, not a sealed generated cage. Pairs with any api: scaffold both with `create-project --api=api-backend --web=frontend-admin`.",
|
|
5
|
+
"tags": ["web", "frontend", "admin", "back-office", "schema-driven", "datatable", "autoform", "rbac", "undo", "provenance", "reactive"]
|
|
6
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"jsx": "react-jsx",
|
|
5
|
+
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "Bundler"
|
|
8
|
+
},
|
|
9
|
+
"include": ["src/**/*"],
|
|
10
|
+
"exclude": ["node_modules", "dist", ".framework"]
|
|
11
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# {{projectName}} / {{appName}}
|
|
2
|
+
|
|
3
|
+
Voltro frontend scaffold (template: **frontend-app**).
|
|
4
|
+
|
|
5
|
+
The **reactive end-to-end loop** — the framework's headline feature, in one
|
|
6
|
+
page. A live notes list (`useSubscription`) plus a create form
|
|
7
|
+
(`useMutation`) with **zero-boilerplate auto-optimistic** updates. This is the
|
|
8
|
+
only template that wires a web frontend to an api; every other `frontend-*`
|
|
9
|
+
template is backend-less.
|
|
10
|
+
|
|
11
|
+
## Scaffold the pair
|
|
12
|
+
|
|
13
|
+
`frontend-app` consumes a sibling api — it is meant to be scaffolded **together
|
|
14
|
+
with [`api-backend`](../api-backend)** (which exposes the `notes.list` query +
|
|
15
|
+
`notes.create` mutation this page subscribes to / calls):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
voltro create-project demo --api=api-backend --web=frontend-app
|
|
19
|
+
cd demo
|
|
20
|
+
pnpm install
|
|
21
|
+
pnpm dev # boots BOTH apps (api on :4000, web on its portRange port)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Open the web app, add a note, and watch it appear in the list **instantly** —
|
|
25
|
+
no refetch, no polling. Open a second browser tab: a note added in one tab
|
|
26
|
+
shows up live in the other.
|
|
27
|
+
|
|
28
|
+
## How the loop works
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Browser api (api-backend)
|
|
32
|
+
─────── ─────────────────
|
|
33
|
+
useSubscription('app', notes.list (defineQuery, source: 'notes')
|
|
34
|
+
'notes.list') ───WS────► └─ streams a fresh snapshot/delta on every
|
|
35
|
+
▲ write to the tenant's `notes` rows
|
|
36
|
+
│ delta (WebSocket)
|
|
37
|
+
│
|
|
38
|
+
useMutation('app', notes.create (defineMutation,
|
|
39
|
+
'notes.create') ──rpc──► target: { table:'notes', op:'insert' })
|
|
40
|
+
.mutate({...}) └─ one atomic DB write → commit → delta
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
1. **`useSubscription('app', 'notes.list')`** opens ONE live WebSocket
|
|
44
|
+
subscription. `data` is the current rows; it re-renders whenever the
|
|
45
|
+
`notes` table changes — from THIS tab, another tab, a workflow, or an
|
|
46
|
+
out-of-band DB write.
|
|
47
|
+
2. **`useMutation('app', 'notes.create').mutate({...})`** invokes the
|
|
48
|
+
mutation. Because the descriptor declares `target: { table:'notes',
|
|
49
|
+
op:'insert' }`, the framework **auto-prepends an optimistic row** to the
|
|
50
|
+
subscription the instant you submit — then replaces it with the real row
|
|
51
|
+
(or reverts it) when the rpc resolves. No `.withOptimistic`, no
|
|
52
|
+
`useOptimistic`, no `startTransition`.
|
|
53
|
+
|
|
54
|
+
Optimistic rows carry `optimistic: true`; the page renders them faintly until
|
|
55
|
+
the server delta supersedes them (see `src/pages/index.tsx`).
|
|
56
|
+
|
|
57
|
+
## The `apis` wiring
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// app.config.ts
|
|
61
|
+
apis: {
|
|
62
|
+
app: { package: '@{{projectName}}/api' }, // the sibling api in this project
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The map key (`'app'`) is the **lookup name** every hook takes as its first
|
|
67
|
+
argument — `useSubscription('app', …)`, `useMutation('app', …)`. The
|
|
68
|
+
`package` resolves the sibling api workspace package; its port is
|
|
69
|
+
auto-discovered from its own `app.config.ts`, and codegen pulls its typed rpc
|
|
70
|
+
surface. Consume more apis by adding more entries (each gets its own
|
|
71
|
+
WebSocket + reconnect cycle).
|
|
72
|
+
|
|
73
|
+
## Files
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
app.config.ts type:web + the `apis: { app }` wiring
|
|
77
|
+
package.json depends on @{{projectName}}/api (the sibling) + @voltro/client
|
|
78
|
+
src/pages/
|
|
79
|
+
layout.tsx root shell (header + <main>)
|
|
80
|
+
index.tsx the reactive notes page — useSubscription + useMutation
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Pairing with a different api
|
|
84
|
+
|
|
85
|
+
This page is hard-wired to api-backend's `notes` domain. To point it at your
|
|
86
|
+
own api, change `app.config.ts`'s `apis.app.package`, then swap the rpc tags +
|
|
87
|
+
the `Note` type in `src/pages/index.tsx` to match your query/mutation
|
|
88
|
+
descriptors. The hook shapes (`useSubscription` / `useMutation`) are identical
|
|
89
|
+
for any api.
|
|
90
|
+
|
|
91
|
+
## Multi-tenancy note
|
|
92
|
+
|
|
93
|
+
The dev `AuthMiddleware` resolves the tenant from the `x-tenant` header,
|
|
94
|
+
defaulting to `'acme'` (the `TENANT` constant in `index.tsx`). The `notes`
|
|
95
|
+
table carries the `tenant()` mixin, so reads are auto-scoped and
|
|
96
|
+
`notes.create` guards that the submitted `tenantId` matches the caller's —
|
|
97
|
+
a typed `TenantMismatch` otherwise. Wire real auth before production (see the
|
|
98
|
+
authentication docs).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Web app for the {{projectName}} project. Read by `voltro dev` / `voltro start`.
|
|
2
|
+
//
|
|
3
|
+
// This is the FULLSTACK template — a web frontend wired to an api so you can
|
|
4
|
+
// see the framework's headline feature: the reactive end-to-end loop. The web
|
|
5
|
+
// client subscribes to a server-side query; when a mutation writes the table,
|
|
6
|
+
// the server pushes a delta over WebSocket and the React tree re-renders. No
|
|
7
|
+
// `refetch`, no polling.
|
|
8
|
+
//
|
|
9
|
+
// `port` is allocated by `voltro create-project` / `voltro add-app` from the
|
|
10
|
+
// project's portRange (in project.json), hard-pinned per app.
|
|
11
|
+
import { defineEnv, envVar } from '@voltro/env'
|
|
12
|
+
|
|
13
|
+
// Typed PUBLIC environment — public vars are validated + baked into the browser
|
|
14
|
+
// bundle at build time and read via `publicEnv` from '@voltro/env/public'.
|
|
15
|
+
// NEVER put a secret here (it ships to every visitor) — declare secrets on the
|
|
16
|
+
// api instead. Run `voltro env` for the manifest.
|
|
17
|
+
export const env = defineEnv({
|
|
18
|
+
VOLTRO_PUBLIC_APP_NAME: envVar.string({ access: 'public', default: '{{capProjectName}}' }),
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
type: 'web' as const,
|
|
23
|
+
name: '{{capProjectName}}{{capAppName}}',
|
|
24
|
+
port: {{port}},
|
|
25
|
+
env,
|
|
26
|
+
// Bilingual by default. The framework auto-wires a cookie-driven
|
|
27
|
+
// <I18nProvider> from these codes and loads the matching
|
|
28
|
+
// `src/locales/<code>.ts` catalog — so `<T id="…" />` / `useT()` /
|
|
29
|
+
// `useLocale()` work app-wide with no manual setup. The <LocaleSwitcher>
|
|
30
|
+
// in the layout writes the `voltro:lang` cookie + reloads. Grow the app to
|
|
31
|
+
// more languages by adding a code here + a `src/locales/<code>.ts`.
|
|
32
|
+
locales: ['en', 'de'] as const,
|
|
33
|
+
defaultLocale: 'en' as const,
|
|
34
|
+
// The api this frontend consumes. The NAME ('app') is the lookup key the
|
|
35
|
+
// hooks use — `useSubscription('app', 'notes.list')`, `useMutation('app',
|
|
36
|
+
// 'notes.create')`. The `package` resolves the sibling api in this project
|
|
37
|
+
// (scaffold it with `--api=api-backend`); its port is auto-discovered from
|
|
38
|
+
// its own app.config.ts, and codegen pulls its typed rpc surface so the
|
|
39
|
+
// hooks are end-to-end typed. Add more entries to consume more apis.
|
|
40
|
+
apis: {
|
|
41
|
+
app: { package: '@{{projectName}}/api' },
|
|
42
|
+
},
|
|
43
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@{{projectName}}/{{appName}}",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "voltro dev .",
|
|
8
|
+
"build": "voltro build .",
|
|
9
|
+
"start": "voltro start .",
|
|
10
|
+
"test": "voltro test",
|
|
11
|
+
"typecheck": "tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@{{projectName}}/api": "0.1.0",
|
|
15
|
+
"@voltro/cli": "0.1.0",
|
|
16
|
+
"@voltro/client": "0.1.0",
|
|
17
|
+
"@voltro/env": "0.1.0",
|
|
18
|
+
"@voltro/i18n": "0.1.0",
|
|
19
|
+
"@voltro/ui-shadcn": "0.1.0",
|
|
20
|
+
"@voltro/web": "0.1.0",
|
|
21
|
+
"react": "^19.0.0",
|
|
22
|
+
"react-dom": "^19.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/react": "^19.0.0",
|
|
26
|
+
"@types/react-dom": "^19.0.0",
|
|
27
|
+
"typescript": "^5.7.0",
|
|
28
|
+
"vitest": "^3.0.0"
|
|
29
|
+
}
|
|
30
|
+
}
|