@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,95 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// The [locale] mirror pages re-export the bare page and add getStaticPaths,
|
|
4
|
+
// which tells `voltro build` which locale-prefixed paths to emit. The default
|
|
5
|
+
// locale (en) is NOT listed — it lives at the bare path — so only /de is
|
|
6
|
+
// produced. The dynamic release mirror emits the CROSS-PRODUCT of non-default
|
|
7
|
+
// locales × release slugs (here: de × every release).
|
|
8
|
+
|
|
9
|
+
import { describe, expect, test, vi } from 'vitest'
|
|
10
|
+
|
|
11
|
+
interface Release {
|
|
12
|
+
version: string
|
|
13
|
+
releasedAt: string
|
|
14
|
+
slug: string
|
|
15
|
+
tags: ReadonlyArray<string>
|
|
16
|
+
title: string
|
|
17
|
+
summary: string
|
|
18
|
+
html: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const releases: ReadonlyArray<Release> = [
|
|
22
|
+
{
|
|
23
|
+
version: '0.2.0',
|
|
24
|
+
releasedAt: '2026-06-21',
|
|
25
|
+
slug: 'v0-2-0',
|
|
26
|
+
tags: ['feature'],
|
|
27
|
+
title: 'Typed env',
|
|
28
|
+
summary: 'Declare your environment once.',
|
|
29
|
+
html: '<p>body</p>',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
version: '0.1.0',
|
|
33
|
+
releasedAt: '2026-01-15',
|
|
34
|
+
slug: 'v0-1-0',
|
|
35
|
+
tags: ['feature'],
|
|
36
|
+
title: 'First release',
|
|
37
|
+
summary: 'The initial cut.',
|
|
38
|
+
html: '<p>body</p>',
|
|
39
|
+
},
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
// The real release source reads .mdx via `import.meta.glob` + node-gated shiki;
|
|
43
|
+
// mock it (used by the [slug] mirror's cross-product getStaticPaths and by the
|
|
44
|
+
// transitively-imported base pages).
|
|
45
|
+
const releaseBySlug = (slug: string): Release | undefined => releases.find((r) => r.slug === slug)
|
|
46
|
+
vi.mock('../../lib/releases', () => ({ releases, releaseBySlug }))
|
|
47
|
+
vi.mock('@voltro/changelog', () => ({
|
|
48
|
+
highlightRelease: async (r: Release) => r,
|
|
49
|
+
}))
|
|
50
|
+
vi.mock('@voltro/web', () => ({
|
|
51
|
+
useLoaderData: () => ({ release: releases[0] }),
|
|
52
|
+
notFound: (detail?: string) => {
|
|
53
|
+
throw new Error(detail)
|
|
54
|
+
},
|
|
55
|
+
useLocation: () => '/',
|
|
56
|
+
}))
|
|
57
|
+
vi.mock('@voltro/i18n', () => ({
|
|
58
|
+
defineCatalog: <T,>(c: T): T => c,
|
|
59
|
+
defineLocale:
|
|
60
|
+
() =>
|
|
61
|
+
<T,>(c: T): T =>
|
|
62
|
+
c,
|
|
63
|
+
useLocale: () => 'en',
|
|
64
|
+
useT: (id: string) => id,
|
|
65
|
+
T: ({ id }: { id: string }) => id,
|
|
66
|
+
}))
|
|
67
|
+
|
|
68
|
+
const indexMirror = await import('./index')
|
|
69
|
+
const releaseMirror = await import('./[slug]')
|
|
70
|
+
|
|
71
|
+
describe('index mirror — getStaticPaths', () => {
|
|
72
|
+
test('emits only non-default locales (just /de)', async () => {
|
|
73
|
+
const paths = await indexMirror.getStaticPaths()
|
|
74
|
+
expect(paths).toEqual([{ params: { locale: 'de' } }])
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('re-exports the base page component + its meta function', () => {
|
|
78
|
+
expect(typeof indexMirror.default).toBe('function')
|
|
79
|
+
expect(indexMirror.meta({ locale: 'de' }).title).toContain('Changelog')
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
describe('release mirror — getStaticPaths (locale × slug cross-product)', () => {
|
|
84
|
+
test('emits one path per non-default locale × release slug', async () => {
|
|
85
|
+
const paths = await releaseMirror.getStaticPaths()
|
|
86
|
+
expect(paths).toEqual(releases.map((r) => ({ params: { locale: 'de', slug: r.slug } })))
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('re-exports the base release page + its config exports', () => {
|
|
90
|
+
expect(typeof releaseMirror.default).toBe('function')
|
|
91
|
+
expect(releaseMirror.renderMode).toBe('static')
|
|
92
|
+
expect(releaseMirror.interactive).toBe('none')
|
|
93
|
+
expect(typeof releaseMirror.loader).toBe('function')
|
|
94
|
+
})
|
|
95
|
+
})
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// `/[slug]` per-release page — `getStaticPaths` (one path per release),
|
|
4
|
+
// `loader` (look up the release, shiki-highlight it, `notFound()` on a miss),
|
|
5
|
+
// `meta(loaderData)`, and the render (which injects the highlighted HTML). We
|
|
6
|
+
// mock `../lib/releases` (avoids the node-only glob) and `@voltro/changelog`
|
|
7
|
+
// (`highlightRelease` pulls in shiki) and `@voltro/web` (`notFound`).
|
|
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
|
+
// Type-only import — erased at runtime, so it does NOT pull @voltro/changelog's
|
|
13
|
+
// node-only glob (the value module stays mocked below). Using the real Release
|
|
14
|
+
// type keeps the mock in lockstep with the framework's shape (tags: ReleaseTag[],
|
|
15
|
+
// draft, body, …) so the test typechecks against what the page actually consumes.
|
|
16
|
+
import type { Release } from '@voltro/changelog'
|
|
17
|
+
|
|
18
|
+
const releases: ReadonlyArray<Release> = [
|
|
19
|
+
{
|
|
20
|
+
version: '0.1.0',
|
|
21
|
+
releasedAt: '2026-01-15',
|
|
22
|
+
slug: 'v0-1-0',
|
|
23
|
+
tags: ['feature'],
|
|
24
|
+
title: 'First release',
|
|
25
|
+
summary: 'The initial cut.',
|
|
26
|
+
draft: false,
|
|
27
|
+
body: 'The initial cut.',
|
|
28
|
+
html: '<p>plain</p>',
|
|
29
|
+
},
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
const releaseBySlug = (slug: string): Release | undefined => releases.find((r) => r.slug === slug)
|
|
33
|
+
|
|
34
|
+
vi.mock('../lib/releases', () => ({ releases, releaseBySlug }))
|
|
35
|
+
// `highlightRelease` runs shiki in node; the page only needs it to return a
|
|
36
|
+
// (possibly re-rendered) release — the identity, tagged so we can assert it ran.
|
|
37
|
+
vi.mock('@voltro/changelog', () => ({
|
|
38
|
+
highlightRelease: async (r: Release) => ({ ...r, html: `<pre>${r.title}</pre>` }),
|
|
39
|
+
}))
|
|
40
|
+
|
|
41
|
+
const loaderData = vi.fn<() => { release: Release }>()
|
|
42
|
+
class NotFoundError extends Error {}
|
|
43
|
+
vi.mock('@voltro/web', () => ({
|
|
44
|
+
useLoaderData: () => loaderData(),
|
|
45
|
+
notFound: (detail?: string) => {
|
|
46
|
+
throw new NotFoundError(detail)
|
|
47
|
+
},
|
|
48
|
+
// `withLocalePrefix` (lib/locale) reads useLocation; the page pins the locale
|
|
49
|
+
// via useLocale, so any bare path is fine here.
|
|
50
|
+
useLocation: () => '/',
|
|
51
|
+
}))
|
|
52
|
+
// The back-link chrome comes from @voltro/i18n; stub it (useLocale → en,
|
|
53
|
+
// <T> passes children through).
|
|
54
|
+
vi.mock('@voltro/i18n', () => ({
|
|
55
|
+
defineCatalog: <T,>(c: T): T => c,
|
|
56
|
+
defineLocale:
|
|
57
|
+
() =>
|
|
58
|
+
<T,>(c: T): T =>
|
|
59
|
+
c,
|
|
60
|
+
useLocale: () => 'en',
|
|
61
|
+
T: ({ id }: { id: string }) => id,
|
|
62
|
+
}))
|
|
63
|
+
|
|
64
|
+
const { default: ReleasePage, getStaticPaths, loader, meta } = await import('./[slug]')
|
|
65
|
+
|
|
66
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
67
|
+
|
|
68
|
+
let container: HTMLDivElement
|
|
69
|
+
let root: Root
|
|
70
|
+
|
|
71
|
+
const render = (node: ReactNode): void => {
|
|
72
|
+
container = document.createElement('div')
|
|
73
|
+
document.body.appendChild(container)
|
|
74
|
+
act(() => {
|
|
75
|
+
root = createRoot(container)
|
|
76
|
+
root.render(node)
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
loaderData.mockReset()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
afterEach(() => {
|
|
85
|
+
if (root) act(() => root.unmount())
|
|
86
|
+
container?.remove()
|
|
87
|
+
document.body.innerHTML = ''
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
describe('changelog [slug] — getStaticPaths', () => {
|
|
91
|
+
test('enumerates one path per release slug', async () => {
|
|
92
|
+
const paths = await getStaticPaths()
|
|
93
|
+
expect(paths.map((p) => p.params.slug)).toEqual(['v0-1-0'])
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe('changelog [slug] — loader', () => {
|
|
98
|
+
test('resolves the release and runs it through highlightRelease', async () => {
|
|
99
|
+
const { release } = await loader({ params: { slug: 'v0-1-0' } } as never)
|
|
100
|
+
expect(release.slug).toBe('v0-1-0')
|
|
101
|
+
expect(release.html).toBe('<pre>First release</pre>')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('throws notFound for an unknown slug', async () => {
|
|
105
|
+
await expect(loader({ params: { slug: 'nope' } } as never)).rejects.toBeInstanceOf(
|
|
106
|
+
NotFoundError,
|
|
107
|
+
)
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
describe('changelog [slug] — meta', () => {
|
|
112
|
+
test('builds the per-release title + description from the loader data', () => {
|
|
113
|
+
const m = meta({ loaderData: { release: releases[0]! } })
|
|
114
|
+
expect(m.title).toContain('First release')
|
|
115
|
+
expect(m.description).toBe('The initial cut.')
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe('changelog [slug] — render', () => {
|
|
120
|
+
test('renders the title, version, and injects the highlighted HTML body', () => {
|
|
121
|
+
loaderData.mockReturnValue({ release: { ...releases[0]!, html: '<p>the body html</p>' } })
|
|
122
|
+
render(createElement(ReleasePage))
|
|
123
|
+
expect(container.querySelector('h1')?.textContent).toBe('First release')
|
|
124
|
+
expect(container.textContent).toContain('v0.1.0')
|
|
125
|
+
expect(container.innerHTML).toContain('the body html')
|
|
126
|
+
})
|
|
127
|
+
})
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Per-release page. `getStaticPaths` enumerates every release slug so
|
|
2
|
+
// `voltro build` pre-renders one HTML file per release. The loader runs at
|
|
3
|
+
// build time (Node) where `highlightRelease` shiki-highlights the code
|
|
4
|
+
// fences; in the browser it's a no-op and the plain marked HTML is used.
|
|
5
|
+
|
|
6
|
+
import type { ReactNode } from 'react'
|
|
7
|
+
import { useLoaderData, notFound, type LoaderFn } from '@voltro/web'
|
|
8
|
+
import { highlightRelease } from '@voltro/changelog'
|
|
9
|
+
import { T, useLocale } from '@voltro/i18n'
|
|
10
|
+
import { releases, releaseBySlug, type Release } from '../lib/releases'
|
|
11
|
+
import { withLocalePrefix } from '../lib/locale'
|
|
12
|
+
|
|
13
|
+
export const renderMode = 'static' as const
|
|
14
|
+
export const interactive = 'none' as const
|
|
15
|
+
|
|
16
|
+
export const getStaticPaths = async (): Promise<Array<{ params: { slug: string } }>> =>
|
|
17
|
+
releases.map((release) => ({ params: { slug: release.slug } }))
|
|
18
|
+
|
|
19
|
+
export const loader: LoaderFn<{ release: Release }> = async ({ params }) => {
|
|
20
|
+
const release = params.slug ? releaseBySlug(params.slug) : undefined
|
|
21
|
+
if (!release) return notFound(`release ${params.slug ?? ''}`)
|
|
22
|
+
return { release: await highlightRelease(release) }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const meta = ({ loaderData }: { loaderData: { release: Release } }) => ({
|
|
26
|
+
title: `${loaderData.release.title} — Changelog`,
|
|
27
|
+
description: loaderData.release.summary,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export default function ReleasePage(): ReactNode {
|
|
31
|
+
const { release } = useLoaderData<{ release: Release }>()
|
|
32
|
+
// The active locale (from the layout's URL provider) keeps the back-link on
|
|
33
|
+
// the current language.
|
|
34
|
+
const locale = useLocale()
|
|
35
|
+
return (
|
|
36
|
+
<article className="changelog-prose">
|
|
37
|
+
<p className="changelog-meta">
|
|
38
|
+
<a href={withLocalePrefix('/', locale)}>
|
|
39
|
+
<T id="post.backToList" />
|
|
40
|
+
</a>
|
|
41
|
+
</p>
|
|
42
|
+
<h1>{release.title}</h1>
|
|
43
|
+
<p className="changelog-meta">
|
|
44
|
+
<time dateTime={release.releasedAt}>{release.releasedAt}</time> · v{release.version}
|
|
45
|
+
{release.tags.map((tag) => (
|
|
46
|
+
<span key={tag} className="changelog-tag">{tag}</span>
|
|
47
|
+
))}
|
|
48
|
+
</p>
|
|
49
|
+
<div dangerouslySetInnerHTML={{ __html: release.html }} />
|
|
50
|
+
</article>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// `/` changelog index — lists every release, newest first. The real release
|
|
4
|
+
// source (`../lib/releases`) reads .mdx via `import.meta.glob` + node-gated
|
|
5
|
+
// shiki, so we mock it with canned releases and assert the observable render:
|
|
6
|
+
// config exports, meta, one entry per release, and its tags + link.
|
|
7
|
+
|
|
8
|
+
import { afterEach, describe, expect, test, vi } from 'vitest'
|
|
9
|
+
import { act, createElement, type ReactNode } from 'react'
|
|
10
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
11
|
+
|
|
12
|
+
interface Release {
|
|
13
|
+
version: string
|
|
14
|
+
releasedAt: string
|
|
15
|
+
slug: string
|
|
16
|
+
tags: ReadonlyArray<string>
|
|
17
|
+
title: string
|
|
18
|
+
summary: string
|
|
19
|
+
html: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const releases: ReadonlyArray<Release> = [
|
|
23
|
+
{
|
|
24
|
+
version: '0.2.0',
|
|
25
|
+
releasedAt: '2026-06-21',
|
|
26
|
+
slug: 'v0-2-0',
|
|
27
|
+
tags: ['feature', 'performance'],
|
|
28
|
+
title: 'Typed env + faster boot',
|
|
29
|
+
summary: 'Declare your environment once.',
|
|
30
|
+
html: '<p>body</p>',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
version: '0.1.0',
|
|
34
|
+
releasedAt: '2026-01-15',
|
|
35
|
+
slug: 'v0-1-0',
|
|
36
|
+
tags: ['feature'],
|
|
37
|
+
title: 'First release',
|
|
38
|
+
summary: 'The initial cut.',
|
|
39
|
+
html: '<p>body</p>',
|
|
40
|
+
},
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
vi.mock('../lib/releases', () => ({ releases }))
|
|
44
|
+
// `withLocalePrefix` (lib/locale) reads useLocation; the page pins the locale
|
|
45
|
+
// via useLocale, so any bare path is fine here.
|
|
46
|
+
vi.mock('@voltro/web', () => ({ useLocation: () => '/' }))
|
|
47
|
+
// Chrome (meta via getCatalog + the empty state) comes from @voltro/i18n;
|
|
48
|
+
// stub it (identity catalog defs, useLocale → en, <T> passes children through).
|
|
49
|
+
vi.mock('@voltro/i18n', () => ({
|
|
50
|
+
defineCatalog: <T,>(c: T): T => c,
|
|
51
|
+
defineLocale:
|
|
52
|
+
() =>
|
|
53
|
+
<T,>(c: T): T =>
|
|
54
|
+
c,
|
|
55
|
+
useLocale: () => 'en',
|
|
56
|
+
useT: (id: string) => id,
|
|
57
|
+
T: ({ id }: { id: string }) => id,
|
|
58
|
+
}))
|
|
59
|
+
|
|
60
|
+
const { default: ChangelogIndex, meta, renderMode, interactive } = await import('./index')
|
|
61
|
+
|
|
62
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
63
|
+
|
|
64
|
+
let container: HTMLDivElement
|
|
65
|
+
let root: Root
|
|
66
|
+
|
|
67
|
+
const render = (node: ReactNode): void => {
|
|
68
|
+
container = document.createElement('div')
|
|
69
|
+
document.body.appendChild(container)
|
|
70
|
+
act(() => {
|
|
71
|
+
root = createRoot(container)
|
|
72
|
+
root.render(node)
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
afterEach(() => {
|
|
77
|
+
if (root) act(() => root.unmount())
|
|
78
|
+
container?.remove()
|
|
79
|
+
document.body.innerHTML = ''
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('changelog index — config + localized meta', () => {
|
|
83
|
+
test('is a static page with zero client JS', () => {
|
|
84
|
+
expect(renderMode).toBe('static')
|
|
85
|
+
expect(interactive).toBe('none')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('meta() returns a localized title + description per locale', () => {
|
|
89
|
+
expect(meta({ locale: 'en' }).title).toContain('Changelog')
|
|
90
|
+
expect(meta({ locale: 'en' }).description).toContain('release notes')
|
|
91
|
+
expect(meta({ locale: 'de' }).description).toContain('Release-Notes')
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('changelog index — render', () => {
|
|
96
|
+
test('renders one entry per release, each linking to /<slug> with its tags', () => {
|
|
97
|
+
render(createElement(ChangelogIndex))
|
|
98
|
+
const entries = Array.from(container.querySelectorAll('.changelog-entry'))
|
|
99
|
+
expect(entries).toHaveLength(2)
|
|
100
|
+
|
|
101
|
+
const links = Array.from(container.querySelectorAll('.changelog-entry h2 a'))
|
|
102
|
+
expect(links.map((a) => a.getAttribute('href'))).toEqual(['/v0-2-0', '/v0-1-0'])
|
|
103
|
+
expect(links[0]!.textContent).toBe('Typed env + faster boot')
|
|
104
|
+
|
|
105
|
+
// The first entry surfaces both of its tags.
|
|
106
|
+
const firstTags = Array.from(entries[0]!.querySelectorAll('.changelog-tag')).map(
|
|
107
|
+
(t) => t.textContent,
|
|
108
|
+
)
|
|
109
|
+
expect(firstTags).toEqual(['feature', 'performance'])
|
|
110
|
+
expect(container.textContent).toContain('v0.2.0')
|
|
111
|
+
})
|
|
112
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Changelog index — every release, newest first, mounted at `/` (default
|
|
2
|
+
// locale) and mirrored at `/de` by src/pages/[locale]/index.tsx. Pure static
|
|
3
|
+
// render with no client JS (`interactive: 'none'`).
|
|
4
|
+
//
|
|
5
|
+
// `meta` is a FUNCTION of `{ locale }` so each variant's <title>/<description>
|
|
6
|
+
// is localised in the pre-rendered HTML. The release list itself is CONTENT
|
|
7
|
+
// (the .mdx files) and is NOT translated — only the chrome (page meta, the
|
|
8
|
+
// empty state) comes from the catalogs. Entry links are locale-prefixed via
|
|
9
|
+
// `withLocalePrefix` so /de stays on /de.
|
|
10
|
+
|
|
11
|
+
import type { ReactNode } from 'react'
|
|
12
|
+
import type { PageMeta } from '@voltro/web'
|
|
13
|
+
import { T, useLocale } from '@voltro/i18n'
|
|
14
|
+
import { releases } from '../lib/releases'
|
|
15
|
+
import { getCatalog, withLocalePrefix } from '../lib/locale'
|
|
16
|
+
|
|
17
|
+
export const renderMode = 'static' as const
|
|
18
|
+
export const interactive = 'none' as const
|
|
19
|
+
|
|
20
|
+
export const meta = ({ locale }: { locale: string }): PageMeta => {
|
|
21
|
+
const c = getCatalog(locale)
|
|
22
|
+
return {
|
|
23
|
+
title: c['meta.home.title'],
|
|
24
|
+
description: c['meta.home.description'],
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default function ChangelogIndex(): ReactNode {
|
|
29
|
+
const locale = useLocale()
|
|
30
|
+
if (releases.length === 0) {
|
|
31
|
+
return (
|
|
32
|
+
<p>
|
|
33
|
+
<T id="empty.body" values={{ code: (chunks: ReactNode) => <code>{chunks}</code> }} />
|
|
34
|
+
</p>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
return (
|
|
38
|
+
<div>
|
|
39
|
+
{releases.map((release) => (
|
|
40
|
+
<article key={release.slug} className="changelog-entry">
|
|
41
|
+
<p className="changelog-meta">
|
|
42
|
+
<time dateTime={release.releasedAt}>{release.releasedAt}</time> · v{release.version}
|
|
43
|
+
{release.tags.map((tag) => (
|
|
44
|
+
<span key={tag} className="changelog-tag">{tag}</span>
|
|
45
|
+
))}
|
|
46
|
+
</p>
|
|
47
|
+
<h2>
|
|
48
|
+
<a href={withLocalePrefix(`/${release.slug}`, locale)}>{release.title}</a>
|
|
49
|
+
</h2>
|
|
50
|
+
<p>{release.summary}</p>
|
|
51
|
+
</article>
|
|
52
|
+
))}
|
|
53
|
+
</div>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Root layout — wraps every page in the framework's BlogLayout shell inside a
|
|
2
|
+
// URL-driven <I18nProvider> (Strategy B: en at the bare path, de under /de).
|
|
3
|
+
// `voltro build` pre-renders each page once per locale with the right catalog
|
|
4
|
+
// baked in. The language switcher goes in BlogLayout's `headerRight` slot as a
|
|
5
|
+
// plain <a> per locale — a real navigation to that locale's crawlable URL,
|
|
6
|
+
// which is correct for a zero-JS static content site.
|
|
7
|
+
//
|
|
8
|
+
// BlogLayout carries its own inline styles; globals.css only styles the
|
|
9
|
+
// markdown body + the list.
|
|
10
|
+
|
|
11
|
+
import type { ReactNode } from 'react'
|
|
12
|
+
import { BlogLayout, useLocation } from '@voltro/web'
|
|
13
|
+
import { I18nProvider, T, useLocale, useT } from '@voltro/i18n'
|
|
14
|
+
import {
|
|
15
|
+
CATALOGS,
|
|
16
|
+
DEFAULT_LOCALE,
|
|
17
|
+
SUPPORTED_LOCALES,
|
|
18
|
+
stripLocalePrefix,
|
|
19
|
+
useUrlLocale,
|
|
20
|
+
withLocalePrefix,
|
|
21
|
+
} from '../lib/locale'
|
|
22
|
+
import '../globals.css'
|
|
23
|
+
|
|
24
|
+
interface LayoutProps {
|
|
25
|
+
readonly children: ReactNode
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Rendered INSIDE the provider so <T> / useT resolve against the active
|
|
29
|
+
// catalog. topNav labels must be plain strings, so they come from useT.
|
|
30
|
+
function ChangelogShell({ children }: LayoutProps): ReactNode {
|
|
31
|
+
const locale = useLocale()
|
|
32
|
+
const pathname = useLocation()
|
|
33
|
+
const barePath = stripLocalePrefix(pathname)
|
|
34
|
+
const releasesLabel = useT('nav.releases')
|
|
35
|
+
const rssLabel = useT('nav.rss')
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<BlogLayout
|
|
39
|
+
brand={
|
|
40
|
+
<>
|
|
41
|
+
{{capProjectName}} <T id="nav.changelog" />
|
|
42
|
+
</>
|
|
43
|
+
}
|
|
44
|
+
pathname={pathname}
|
|
45
|
+
topNav={[
|
|
46
|
+
{ label: releasesLabel, href: withLocalePrefix('/', locale) },
|
|
47
|
+
// One shared feed for every locale — never locale-prefixed.
|
|
48
|
+
{ label: rssLabel, href: '/rss.xml' },
|
|
49
|
+
]}
|
|
50
|
+
headerRight={
|
|
51
|
+
<span className="cl-lang">
|
|
52
|
+
<span className="cl-lang-label">
|
|
53
|
+
<T id="nav.language" />:
|
|
54
|
+
</span>
|
|
55
|
+
{SUPPORTED_LOCALES.map((code) => (
|
|
56
|
+
<a
|
|
57
|
+
key={code}
|
|
58
|
+
href={withLocalePrefix(barePath, code)}
|
|
59
|
+
aria-current={code === locale ? 'true' : undefined}
|
|
60
|
+
>
|
|
61
|
+
{code.toUpperCase()}
|
|
62
|
+
</a>
|
|
63
|
+
))}
|
|
64
|
+
</span>
|
|
65
|
+
}
|
|
66
|
+
footer={
|
|
67
|
+
<span>
|
|
68
|
+
<T id="footer.builtWith" />
|
|
69
|
+
</span>
|
|
70
|
+
}
|
|
71
|
+
>
|
|
72
|
+
{children}
|
|
73
|
+
</BlogLayout>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default function Layout({ children }: LayoutProps): ReactNode {
|
|
78
|
+
const locale = useUrlLocale()
|
|
79
|
+
return (
|
|
80
|
+
<I18nProvider locale={locale} messages={CATALOGS[locale]} defaultLocale={DEFAULT_LOCALE}>
|
|
81
|
+
<ChangelogShell>{children}</ChangelogShell>
|
|
82
|
+
</I18nProvider>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "changelog",
|
|
3
|
+
"kind": "web",
|
|
4
|
+
"summary": "Changelog / release-notes site: MDX releases in content/releases/, a rendered list + per-release pages (shiki-highlighted code), and an RSS feed — powered by @voltro/changelog.",
|
|
5
|
+
"tags": ["web", "frontend", "changelog", "releases", "rss", "static"]
|
|
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,55 @@
|
|
|
1
|
+
# {{projectName}} / {{appName}}
|
|
2
|
+
|
|
3
|
+
A library of standalone serverless functions (template: **edge-functions**).
|
|
4
|
+
Each `functions/*.serverless.ts` is bundled + deployed on its own — there is
|
|
5
|
+
no long-running server here and no dev port.
|
|
6
|
+
|
|
7
|
+
## The functions
|
|
8
|
+
|
|
9
|
+
| File | Method | Demonstrates |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `health.serverless.ts` | GET | simplest diagnostic; `ctx.env`; pure `Effect.sync` |
|
|
12
|
+
| `shareLink.serverless.ts` | POST | pure compute; runtime hints (memory/timeout/region) |
|
|
13
|
+
| `geoGreeting.serverless.ts` | GET | reading `ctx.request.headers` (edge geo) |
|
|
14
|
+
| `slackNotify.serverless.ts` | POST | `ctx.waitUntil` — background work after the response |
|
|
15
|
+
| `currencyConvert.serverless.ts` | POST | outbound HTTP via `HttpClient`; read + transform JSON |
|
|
16
|
+
| `verifySignature.serverless.ts` | POST | Web Crypto HMAC; `ServerlessHttpError` 401 |
|
|
17
|
+
| `aiComplete.serverless.ts` | POST | a third-party API needing a secret (`ctx.env`); 502 mapping |
|
|
18
|
+
| `resolveLink.serverless.ts` | GET | a lookup; 404 via `ServerlessHttpError` |
|
|
19
|
+
|
|
20
|
+
Every function is **edge-safe** (no `node:*`), so the same code runs on
|
|
21
|
+
Cloudflare Workers, Scaleway Functions, and your own node box.
|
|
22
|
+
|
|
23
|
+
## Run one locally
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm install
|
|
27
|
+
voltro serverless list . # what's here
|
|
28
|
+
voltro serverless dev functions/health.serverless.ts # one function on :8910
|
|
29
|
+
curl http://localhost:8910/
|
|
30
|
+
|
|
31
|
+
# functions that need config read it from the env:
|
|
32
|
+
SLACK_WEBHOOK_URL=https://hooks.slack.com/... \
|
|
33
|
+
voltro serverless dev functions/slackNotify.serverless.ts
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`voltro serverless serve .` runs ALL of them at once (each mounted at
|
|
37
|
+
`/<name>`) — handy as a single self-hosted sidecar.
|
|
38
|
+
|
|
39
|
+
## Deploy
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
voltro serverless build . # bundle all → inspect
|
|
43
|
+
voltro serverless deploy --target node # self-host kit (Dockerfile + README)
|
|
44
|
+
voltro serverless deploy --target cloudflare # Cloudflare Workers
|
|
45
|
+
voltro serverless deploy --target scaleway --namespace-id <id> # Scaleway Functions
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Set each function's secrets at deploy time (the host's env / secret store):
|
|
49
|
+
`SLACK_WEBHOOK_URL`, `SIGNING_SECRET`, `OPENAI_API_KEY`, `SHARE_BASE_URL`, …
|
|
50
|
+
|
|
51
|
+
## Add your own
|
|
52
|
+
|
|
53
|
+
Drop a new `functions/<name>.serverless.ts` exporting
|
|
54
|
+
`export default defineServerless({ … })`. It's discovered automatically — no
|
|
55
|
+
registry, no config.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// aiComplete — a one-shot LLM completion at the edge.
|
|
2
|
+
//
|
|
3
|
+
// Demonstrates: calling a third-party API that needs a SECRET (the key lives in
|
|
4
|
+
// `ctx.env`, never in the bundle), an optional input field, reading the JSON
|
|
5
|
+
// response, and mapping an upstream failure to a 502. Uses the OpenAI
|
|
6
|
+
// chat-completions shape; point it at any compatible endpoint via OPENAI_BASE_URL.
|
|
7
|
+
//
|
|
8
|
+
// OPENAI_API_KEY=sk-... voltro serverless dev functions/aiComplete.serverless.ts
|
|
9
|
+
// curl -X POST http://localhost:8910/ -d '{"prompt":"Name three primary colors."}'
|
|
10
|
+
|
|
11
|
+
import { Effect, Schema } from 'effect'
|
|
12
|
+
import { HttpClient, HttpClientRequest } from '@effect/platform'
|
|
13
|
+
import { defineServerless, ServerlessHttpError } from '@voltro/serverless'
|
|
14
|
+
|
|
15
|
+
export default defineServerless({
|
|
16
|
+
name: 'ai-complete',
|
|
17
|
+
method: 'POST',
|
|
18
|
+
input: Schema.Struct({
|
|
19
|
+
prompt: Schema.String.pipe(Schema.minLength(1), Schema.maxLength(4000)),
|
|
20
|
+
system: Schema.optional(Schema.String),
|
|
21
|
+
}),
|
|
22
|
+
output: Schema.Struct({ text: Schema.String }),
|
|
23
|
+
runtime: { memoryMb: 256, timeoutSeconds: 30 },
|
|
24
|
+
handler: ({ prompt, system }, ctx) =>
|
|
25
|
+
Effect.gen(function* () {
|
|
26
|
+
const apiKey = ctx.env.OPENAI_API_KEY
|
|
27
|
+
if (!apiKey) {
|
|
28
|
+
return yield* Effect.fail(
|
|
29
|
+
new ServerlessHttpError({ status: 503, message: 'ai-not-configured', detail: 'Set OPENAI_API_KEY.' }),
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
const baseUrl = ctx.env.OPENAI_BASE_URL ?? 'https://api.openai.com'
|
|
33
|
+
const model = ctx.env.OPENAI_MODEL ?? 'gpt-4o-mini'
|
|
34
|
+
|
|
35
|
+
const http = yield* HttpClient.HttpClient
|
|
36
|
+
const request = HttpClientRequest.post(`${baseUrl}/v1/chat/completions`).pipe(
|
|
37
|
+
HttpClientRequest.setHeader('authorization', `Bearer ${apiKey}`),
|
|
38
|
+
HttpClientRequest.bodyUnsafeJson({
|
|
39
|
+
model,
|
|
40
|
+
max_tokens: 400,
|
|
41
|
+
messages: [
|
|
42
|
+
...(system ? [{ role: 'system', content: system }] : []),
|
|
43
|
+
{ role: 'user', content: prompt },
|
|
44
|
+
],
|
|
45
|
+
}),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const response = yield* http.execute(request)
|
|
49
|
+
if (response.status >= 400) {
|
|
50
|
+
const detail = yield* response.text
|
|
51
|
+
return yield* Effect.fail(new ServerlessHttpError({ status: 502, message: 'ai-call-failed', detail }))
|
|
52
|
+
}
|
|
53
|
+
const body = (yield* response.json) as {
|
|
54
|
+
choices?: ReadonlyArray<{ message?: { content?: string } }>
|
|
55
|
+
}
|
|
56
|
+
return { text: body.choices?.[0]?.message?.content ?? '' }
|
|
57
|
+
}),
|
|
58
|
+
})
|