@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.
Files changed (626) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/LICENSE +57 -0
  3. package/README.md +26 -0
  4. package/SECURITY.md +56 -0
  5. package/THIRD-PARTY-NOTICES.md +20236 -0
  6. package/bin/voltro.mjs +43 -0
  7. package/dist/bin.d.ts +1 -0
  8. package/dist/bin.js +9 -0
  9. package/dist/commands-CfPH2Wf4.js +18061 -0
  10. package/dist/frameworkInspectState-CX2250XB.js +86 -0
  11. package/dist/index.d.ts +48 -0
  12. package/dist/index.js +5 -0
  13. package/dist/inspectState.d.ts +116 -0
  14. package/dist/inspectState.js +2 -0
  15. package/dist/startup.d.ts +26 -0
  16. package/dist/startup.js +2 -0
  17. package/dist/startupRunner-CRhuUl91.js +71 -0
  18. package/package.json +88 -0
  19. package/templates/AGENTS.core.md +258 -0
  20. package/templates/AGENTS.md +351 -0
  21. package/templates/agent-docs/_index.md +93 -0
  22. package/templates/agent-docs/_manifest.json +655 -0
  23. package/templates/agent-docs/ai.md +1845 -0
  24. package/templates/agent-docs/authentication.md +1788 -0
  25. package/templates/agent-docs/caching.md +624 -0
  26. package/templates/agent-docs/cli.md +1650 -0
  27. package/templates/agent-docs/configuration.md +295 -0
  28. package/templates/agent-docs/data.md +2172 -0
  29. package/templates/agent-docs/database/advancedqueries.md +1583 -0
  30. package/templates/agent-docs/database/columntypes.md +1200 -0
  31. package/templates/agent-docs/database/hosting.md +881 -0
  32. package/templates/agent-docs/database/migrations.md +2938 -0
  33. package/templates/agent-docs/database/misc.md +270 -0
  34. package/templates/agent-docs/database/overview.md +108 -0
  35. package/templates/agent-docs/database/querying.md +1622 -0
  36. package/templates/agent-docs/database/scaling.md +331 -0
  37. package/templates/agent-docs/database/schema.md +1458 -0
  38. package/templates/agent-docs/database/seedsdialects.md +1235 -0
  39. package/templates/agent-docs/database/transactions.md +285 -0
  40. package/templates/agent-docs/deployment.md +999 -0
  41. package/templates/agent-docs/internationalization.md +359 -0
  42. package/templates/agent-docs/introduction.md +438 -0
  43. package/templates/agent-docs/multi-tenancy.md +610 -0
  44. package/templates/agent-docs/observability.md +350 -0
  45. package/templates/agent-docs/plugins.md +1273 -0
  46. package/templates/agent-docs/reference.md +978 -0
  47. package/templates/agent-docs/routing.md +1553 -0
  48. package/templates/agent-docs/scheduling.md +666 -0
  49. package/templates/agent-docs/schema-driven-ui.md +607 -0
  50. package/templates/agent-docs/security.md +42 -0
  51. package/templates/agent-docs/templates/apibackends.md +3214 -0
  52. package/templates/agent-docs/templates/appshells.md +2062 -0
  53. package/templates/agent-docs/templates/custom.md +128 -0
  54. package/templates/agent-docs/templates/overview.md +122 -0
  55. package/templates/agent-docs/templates/serverless.md +315 -0
  56. package/templates/agent-docs/testing.md +376 -0
  57. package/templates/agent-docs/workflows.md +1351 -0
  58. package/templates/apps/api-ai/README.md +105 -0
  59. package/templates/apps/api-ai/actions/summarize.action.server.tsx +32 -0
  60. package/templates/apps/api-ai/actions/summarize.action.ts +33 -0
  61. package/templates/apps/api-ai/agents/support.agent.server.tsx +31 -0
  62. package/templates/apps/api-ai/agents/support.agent.tsx +27 -0
  63. package/templates/apps/api-ai/app.config.ts +57 -0
  64. package/templates/apps/api-ai/database/schema.ts +67 -0
  65. package/templates/apps/api-ai/package.json +27 -0
  66. package/templates/apps/api-ai/seeds/docs.seed.ts +67 -0
  67. package/templates/apps/api-ai/template.json +6 -0
  68. package/templates/apps/api-ai/tests/summarize.test.ts +50 -0
  69. package/templates/apps/api-ai/tools/searchDocs.tool.tsx +60 -0
  70. package/templates/apps/api-ai/tsconfig.json +5 -0
  71. package/templates/apps/api-auth/.env +17 -0
  72. package/templates/apps/api-auth/README.md +109 -0
  73. package/templates/apps/api-auth/actions/me.action.server.ts +20 -0
  74. package/templates/apps/api-auth/actions/me.action.ts +19 -0
  75. package/templates/apps/api-auth/app.config.ts +59 -0
  76. package/templates/apps/api-auth/database/schema.ts +26 -0
  77. package/templates/apps/api-auth/package.json +28 -0
  78. package/templates/apps/api-auth/template.json +6 -0
  79. package/templates/apps/api-auth/tests/me.test.ts +37 -0
  80. package/templates/apps/api-auth/tsconfig.json +5 -0
  81. package/templates/apps/api-backend/README.md +25 -0
  82. package/templates/apps/api-backend/app.config.ts +39 -0
  83. package/templates/apps/api-backend/database/schema.ts +57 -0
  84. package/templates/apps/api-backend/mutations/notes.create.mutation.server.ts +19 -0
  85. package/templates/apps/api-backend/mutations/notes.create.mutation.ts +37 -0
  86. package/templates/apps/api-backend/package.json +28 -0
  87. package/templates/apps/api-backend/queries/notes.query.server.ts +14 -0
  88. package/templates/apps/api-backend/queries/notes.query.ts +20 -0
  89. package/templates/apps/api-backend/template.json +6 -0
  90. package/templates/apps/api-backend/tests/notes.create.test.ts +50 -0
  91. package/templates/apps/api-backend/tsconfig.json +5 -0
  92. package/templates/apps/api-backend-deactivation/README.md +49 -0
  93. package/templates/apps/api-backend-deactivation/actions/users.get.action.server.ts +17 -0
  94. package/templates/apps/api-backend-deactivation/actions/users.get.action.ts +18 -0
  95. package/templates/apps/api-backend-deactivation/app.config.ts +20 -0
  96. package/templates/apps/api-backend-deactivation/database/schema.ts +41 -0
  97. package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.server.ts +6 -0
  98. package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.ts +16 -0
  99. package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.server.ts +10 -0
  100. package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.ts +19 -0
  101. package/templates/apps/api-backend-deactivation/package.json +28 -0
  102. package/templates/apps/api-backend-deactivation/template.json +6 -0
  103. package/templates/apps/api-backend-deactivation/tests/users.deactivate.test.ts +47 -0
  104. package/templates/apps/api-backend-deactivation/tsconfig.json +5 -0
  105. package/templates/apps/api-backend-mail/README.md +39 -0
  106. package/templates/apps/api-backend-mail/actions/sendWelcome.action.server.ts +15 -0
  107. package/templates/apps/api-backend-mail/actions/sendWelcome.action.ts +21 -0
  108. package/templates/apps/api-backend-mail/app.config.ts +34 -0
  109. package/templates/apps/api-backend-mail/database/schema.ts +57 -0
  110. package/templates/apps/api-backend-mail/emails/welcome.email.tsx +106 -0
  111. package/templates/apps/api-backend-mail/mutations/notes.create.mutation.server.ts +19 -0
  112. package/templates/apps/api-backend-mail/mutations/notes.create.mutation.ts +37 -0
  113. package/templates/apps/api-backend-mail/package.json +30 -0
  114. package/templates/apps/api-backend-mail/queries/notes.query.server.ts +14 -0
  115. package/templates/apps/api-backend-mail/queries/notes.query.ts +20 -0
  116. package/templates/apps/api-backend-mail/template.json +6 -0
  117. package/templates/apps/api-backend-mail/tests/notes.create.test.ts +50 -0
  118. package/templates/apps/api-backend-mail/tsconfig.json +5 -0
  119. package/templates/apps/api-backend-mariadb/.env.example +35 -0
  120. package/templates/apps/api-backend-mariadb/README.md +31 -0
  121. package/templates/apps/api-backend-mariadb/app.config.ts +68 -0
  122. package/templates/apps/api-backend-mariadb/database/schema.ts +57 -0
  123. package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.server.ts +19 -0
  124. package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.ts +37 -0
  125. package/templates/apps/api-backend-mariadb/package.json +30 -0
  126. package/templates/apps/api-backend-mariadb/queries/notes.query.server.ts +14 -0
  127. package/templates/apps/api-backend-mariadb/queries/notes.query.ts +20 -0
  128. package/templates/apps/api-backend-mariadb/template.json +6 -0
  129. package/templates/apps/api-backend-mariadb/tests/notes.create.test.ts +50 -0
  130. package/templates/apps/api-backend-mariadb/tsconfig.json +5 -0
  131. package/templates/apps/api-backend-storage/README.md +86 -0
  132. package/templates/apps/api-backend-storage/actions/uploadAvatar.action.server.ts +21 -0
  133. package/templates/apps/api-backend-storage/actions/uploadAvatar.action.ts +21 -0
  134. package/templates/apps/api-backend-storage/actions/uploadDocument.action.server.ts +21 -0
  135. package/templates/apps/api-backend-storage/actions/uploadDocument.action.ts +20 -0
  136. package/templates/apps/api-backend-storage/app.config.ts +38 -0
  137. package/templates/apps/api-backend-storage/database/schema.ts +57 -0
  138. package/templates/apps/api-backend-storage/mutations/notes.create.mutation.server.ts +19 -0
  139. package/templates/apps/api-backend-storage/mutations/notes.create.mutation.ts +37 -0
  140. package/templates/apps/api-backend-storage/package.json +27 -0
  141. package/templates/apps/api-backend-storage/queries/notes.query.server.ts +14 -0
  142. package/templates/apps/api-backend-storage/queries/notes.query.ts +20 -0
  143. package/templates/apps/api-backend-storage/template.json +6 -0
  144. package/templates/apps/api-backend-storage/tests/notes.create.test.ts +50 -0
  145. package/templates/apps/api-backend-storage/tsconfig.json +5 -0
  146. package/templates/apps/api-data-advanced/.env +17 -0
  147. package/templates/apps/api-data-advanced/README.md +88 -0
  148. package/templates/apps/api-data-advanced/app.config.ts +56 -0
  149. package/templates/apps/api-data-advanced/database/actors.entity.ts +14 -0
  150. package/templates/apps/api-data-advanced/database/authors.entity.ts +30 -0
  151. package/templates/apps/api-data-advanced/database/authors.relations.ts +15 -0
  152. package/templates/apps/api-data-advanced/database/books.entity.ts +59 -0
  153. package/templates/apps/api-data-advanced/database/books.relations.ts +10 -0
  154. package/templates/apps/api-data-advanced/database/index.ts +23 -0
  155. package/templates/apps/api-data-advanced/database/tenants.entity.ts +11 -0
  156. package/templates/apps/api-data-advanced/package.json +28 -0
  157. package/templates/apps/api-data-advanced/queries/authors.withBooks.query.server.ts +18 -0
  158. package/templates/apps/api-data-advanced/queries/authors.withBooks.query.ts +40 -0
  159. package/templates/apps/api-data-advanced/queries/books.search.query.server.ts +17 -0
  160. package/templates/apps/api-data-advanced/queries/books.search.query.ts +39 -0
  161. package/templates/apps/api-data-advanced/seeds/catalog.seed.ts +101 -0
  162. package/templates/apps/api-data-advanced/template.json +6 -0
  163. package/templates/apps/api-data-advanced/tests/queries.test.ts +124 -0
  164. package/templates/apps/api-data-advanced/tsconfig.json +5 -0
  165. package/templates/apps/api-durable/README.md +81 -0
  166. package/templates/apps/api-durable/aggregates/orderStats.aggregate.ts +49 -0
  167. package/templates/apps/api-durable/app.config.ts +41 -0
  168. package/templates/apps/api-durable/database/schema.ts +65 -0
  169. package/templates/apps/api-durable/mutations/orders.approve.mutation.server.ts +43 -0
  170. package/templates/apps/api-durable/mutations/orders.approve.mutation.ts +23 -0
  171. package/templates/apps/api-durable/mutations/orders.place.mutation.server.ts +45 -0
  172. package/templates/apps/api-durable/mutations/orders.place.mutation.ts +40 -0
  173. package/templates/apps/api-durable/package.json +28 -0
  174. package/templates/apps/api-durable/schedules/nightlyReport.cron.tsx +37 -0
  175. package/templates/apps/api-durable/startup/warm.startup.tsx +33 -0
  176. package/templates/apps/api-durable/subscribers/orderChanges.subscribe.ts +27 -0
  177. package/templates/apps/api-durable/template.json +6 -0
  178. package/templates/apps/api-durable/tests/orders.place.test.ts +91 -0
  179. package/templates/apps/api-durable/triggers/order.placed.trigger.tsx +21 -0
  180. package/templates/apps/api-durable/tsconfig.json +5 -0
  181. package/templates/apps/api-durable/workflows/order.fulfill.workflow.server.tsx +103 -0
  182. package/templates/apps/api-durable/workflows/order.fulfill.workflow.tsx +25 -0
  183. package/templates/apps/api-feature-flags/README.md +63 -0
  184. package/templates/apps/api-feature-flags/actions/notes.export.action.server.ts +11 -0
  185. package/templates/apps/api-feature-flags/actions/notes.export.action.ts +18 -0
  186. package/templates/apps/api-feature-flags/app.config.ts +41 -0
  187. package/templates/apps/api-feature-flags/database/schema.ts +37 -0
  188. package/templates/apps/api-feature-flags/mutations/notes.create.mutation.server.ts +29 -0
  189. package/templates/apps/api-feature-flags/mutations/notes.create.mutation.ts +28 -0
  190. package/templates/apps/api-feature-flags/package.json +29 -0
  191. package/templates/apps/api-feature-flags/template.json +6 -0
  192. package/templates/apps/api-feature-flags/tests/notes.create.test.ts +67 -0
  193. package/templates/apps/api-feature-flags/tsconfig.json +5 -0
  194. package/templates/apps/api-governance/.env +4 -0
  195. package/templates/apps/api-governance/README.md +61 -0
  196. package/templates/apps/api-governance/actions/profiles.get.action.server.ts +17 -0
  197. package/templates/apps/api-governance/actions/profiles.get.action.ts +17 -0
  198. package/templates/apps/api-governance/app.config.ts +35 -0
  199. package/templates/apps/api-governance/database/schema.ts +41 -0
  200. package/templates/apps/api-governance/mutations/profiles.create.mutation.server.ts +17 -0
  201. package/templates/apps/api-governance/mutations/profiles.create.mutation.ts +21 -0
  202. package/templates/apps/api-governance/package.json +29 -0
  203. package/templates/apps/api-governance/template.json +6 -0
  204. package/templates/apps/api-governance/tests/profiles.create.test.ts +59 -0
  205. package/templates/apps/api-governance/tsconfig.json +5 -0
  206. package/templates/apps/api-kv/README.md +100 -0
  207. package/templates/apps/api-kv/actions/sync.pull.action.server.ts +75 -0
  208. package/templates/apps/api-kv/actions/sync.pull.action.ts +25 -0
  209. package/templates/apps/api-kv/actions/sync.reset.action.server.ts +25 -0
  210. package/templates/apps/api-kv/actions/sync.reset.action.ts +22 -0
  211. package/templates/apps/api-kv/actions/sync.status.action.server.ts +28 -0
  212. package/templates/apps/api-kv/actions/sync.status.action.ts +17 -0
  213. package/templates/apps/api-kv/app.config.ts +48 -0
  214. package/templates/apps/api-kv/database/schema.ts +62 -0
  215. package/templates/apps/api-kv/package.json +28 -0
  216. package/templates/apps/api-kv/queries/events.list.query.server.ts +14 -0
  217. package/templates/apps/api-kv/queries/events.list.query.ts +23 -0
  218. package/templates/apps/api-kv/template.json +6 -0
  219. package/templates/apps/api-kv/tests/sync.test.ts +103 -0
  220. package/templates/apps/api-kv/tsconfig.json +5 -0
  221. package/templates/apps/api-moderation/README.md +46 -0
  222. package/templates/apps/api-moderation/app.config.ts +33 -0
  223. package/templates/apps/api-moderation/database/schema.ts +45 -0
  224. package/templates/apps/api-moderation/mutations/comments.create.mutation.server.ts +8 -0
  225. package/templates/apps/api-moderation/mutations/comments.create.mutation.ts +20 -0
  226. package/templates/apps/api-moderation/mutations/posts.create.mutation.server.ts +9 -0
  227. package/templates/apps/api-moderation/mutations/posts.create.mutation.ts +25 -0
  228. package/templates/apps/api-moderation/package.json +29 -0
  229. package/templates/apps/api-moderation/template.json +6 -0
  230. package/templates/apps/api-moderation/tests/posts.create.test.ts +79 -0
  231. package/templates/apps/api-moderation/tsconfig.json +5 -0
  232. package/templates/apps/api-observability/README.md +106 -0
  233. package/templates/apps/api-observability/app.config.ts +32 -0
  234. package/templates/apps/api-observability/database/schema.ts +38 -0
  235. package/templates/apps/api-observability/mutations/notes.create.mutation.server.ts +33 -0
  236. package/templates/apps/api-observability/mutations/notes.create.mutation.ts +15 -0
  237. package/templates/apps/api-observability/package.json +29 -0
  238. package/templates/apps/api-observability/queries/notes.list.query.server.ts +7 -0
  239. package/templates/apps/api-observability/queries/notes.list.query.ts +14 -0
  240. package/templates/apps/api-observability/template.json +6 -0
  241. package/templates/apps/api-observability/tests/notes.create.test.ts +36 -0
  242. package/templates/apps/api-observability/tsconfig.json +5 -0
  243. package/templates/apps/api-ratelimit/README.md +49 -0
  244. package/templates/apps/api-ratelimit/app.config.ts +42 -0
  245. package/templates/apps/api-ratelimit/database/schema.ts +37 -0
  246. package/templates/apps/api-ratelimit/mutations/notes.create.mutation.server.ts +16 -0
  247. package/templates/apps/api-ratelimit/mutations/notes.create.mutation.ts +34 -0
  248. package/templates/apps/api-ratelimit/package.json +29 -0
  249. package/templates/apps/api-ratelimit/template.json +6 -0
  250. package/templates/apps/api-ratelimit/tests/notes.create.test.ts +69 -0
  251. package/templates/apps/api-ratelimit/tsconfig.json +5 -0
  252. package/templates/apps/api-rbac/README.md +59 -0
  253. package/templates/apps/api-rbac/app.config.ts +48 -0
  254. package/templates/apps/api-rbac/database/schema.ts +37 -0
  255. package/templates/apps/api-rbac/mutations/notes.create.mutation.server.ts +22 -0
  256. package/templates/apps/api-rbac/mutations/notes.create.mutation.ts +26 -0
  257. package/templates/apps/api-rbac/package.json +29 -0
  258. package/templates/apps/api-rbac/template.json +6 -0
  259. package/templates/apps/api-rbac/tests/notes.create.test.ts +80 -0
  260. package/templates/apps/api-rbac/tsconfig.json +5 -0
  261. package/templates/apps/api-rest/README.md +85 -0
  262. package/templates/apps/api-rest/app.config.ts +70 -0
  263. package/templates/apps/api-rest/database/schema.ts +52 -0
  264. package/templates/apps/api-rest/lib/product.ts +30 -0
  265. package/templates/apps/api-rest/package.json +27 -0
  266. package/templates/apps/api-rest/routes/v1/products.create.route.tsx +45 -0
  267. package/templates/apps/api-rest/routes/v1/products.delete.route.tsx +25 -0
  268. package/templates/apps/api-rest/routes/v1/products.get.route.tsx +29 -0
  269. package/templates/apps/api-rest/routes/v1/products.list.route.tsx +43 -0
  270. package/templates/apps/api-rest/template.json +6 -0
  271. package/templates/apps/api-rest/tests/products.create.test.ts +53 -0
  272. package/templates/apps/api-rest/tsconfig.json +5 -0
  273. package/templates/apps/api-saas/README.md +106 -0
  274. package/templates/apps/api-saas/app.config.ts +51 -0
  275. package/templates/apps/api-saas/database/schema.ts +50 -0
  276. package/templates/apps/api-saas/mutations/projects.create.mutation.server.ts +52 -0
  277. package/templates/apps/api-saas/mutations/projects.create.mutation.ts +18 -0
  278. package/templates/apps/api-saas/package.json +31 -0
  279. package/templates/apps/api-saas/queries/projects.list.query.server.ts +7 -0
  280. package/templates/apps/api-saas/queries/projects.list.query.ts +18 -0
  281. package/templates/apps/api-saas/template.json +6 -0
  282. package/templates/apps/api-saas/tests/projects.create.test.ts +47 -0
  283. package/templates/apps/api-saas/tsconfig.json +5 -0
  284. package/templates/apps/api-search/README.md +68 -0
  285. package/templates/apps/api-search/app.config.ts +31 -0
  286. package/templates/apps/api-search/database/schema.ts +47 -0
  287. package/templates/apps/api-search/lib/search.ts +27 -0
  288. package/templates/apps/api-search/mutations/articles.create.mutation.server.ts +19 -0
  289. package/templates/apps/api-search/mutations/articles.create.mutation.ts +35 -0
  290. package/templates/apps/api-search/package.json +29 -0
  291. package/templates/apps/api-search/queries/articles.list.query.server.ts +14 -0
  292. package/templates/apps/api-search/queries/articles.list.query.ts +19 -0
  293. package/templates/apps/api-search/seeds/articles.seed.ts +41 -0
  294. package/templates/apps/api-search/startup/searchBackfill.startup.tsx +24 -0
  295. package/templates/apps/api-search/template.json +6 -0
  296. package/templates/apps/api-search/tests/articles.create.test.ts +69 -0
  297. package/templates/apps/api-search/tsconfig.json +5 -0
  298. package/templates/apps/api-versioning/README.md +51 -0
  299. package/templates/apps/api-versioning/actions/documents.asOf.action.server.ts +13 -0
  300. package/templates/apps/api-versioning/actions/documents.asOf.action.ts +14 -0
  301. package/templates/apps/api-versioning/actions/documents.history.action.server.ts +15 -0
  302. package/templates/apps/api-versioning/actions/documents.history.action.ts +17 -0
  303. package/templates/apps/api-versioning/app.config.ts +24 -0
  304. package/templates/apps/api-versioning/database/schema.ts +38 -0
  305. package/templates/apps/api-versioning/mutations/documents.create.mutation.server.ts +8 -0
  306. package/templates/apps/api-versioning/mutations/documents.create.mutation.ts +21 -0
  307. package/templates/apps/api-versioning/mutations/documents.update.mutation.server.ts +9 -0
  308. package/templates/apps/api-versioning/mutations/documents.update.mutation.ts +21 -0
  309. package/templates/apps/api-versioning/package.json +29 -0
  310. package/templates/apps/api-versioning/template.json +6 -0
  311. package/templates/apps/api-versioning/tests/documents.create.test.ts +37 -0
  312. package/templates/apps/api-versioning/tsconfig.json +5 -0
  313. package/templates/apps/api-webhooks/.env +6 -0
  314. package/templates/apps/api-webhooks/README.md +106 -0
  315. package/templates/apps/api-webhooks/app.config.ts +16 -0
  316. package/templates/apps/api-webhooks/database/schema.ts +49 -0
  317. package/templates/apps/api-webhooks/events/order.completed.webhook.tsx +22 -0
  318. package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.server.ts +37 -0
  319. package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.ts +15 -0
  320. package/templates/apps/api-webhooks/package.json +28 -0
  321. package/templates/apps/api-webhooks/queries/orders.list.query.server.ts +7 -0
  322. package/templates/apps/api-webhooks/queries/orders.list.query.ts +15 -0
  323. package/templates/apps/api-webhooks/template.json +6 -0
  324. package/templates/apps/api-webhooks/tests/orders.fulfill.test.ts +51 -0
  325. package/templates/apps/api-webhooks/tsconfig.json +5 -0
  326. package/templates/apps/api-webhooks/webhooks/orders.webhook.tsx +35 -0
  327. package/templates/apps/changelog/README.md +77 -0
  328. package/templates/apps/changelog/app.config.ts +27 -0
  329. package/templates/apps/changelog/content/releases/0.1.0.mdx +19 -0
  330. package/templates/apps/changelog/content/releases/0.2.0.mdx +28 -0
  331. package/templates/apps/changelog/package.json +30 -0
  332. package/templates/apps/changelog/scripts/generate-rss.mjs +38 -0
  333. package/templates/apps/changelog/src/globals.css +66 -0
  334. package/templates/apps/changelog/src/globals.d.ts +16 -0
  335. package/templates/apps/changelog/src/lib/locale.test.ts +72 -0
  336. package/templates/apps/changelog/src/lib/locale.ts +55 -0
  337. package/templates/apps/changelog/src/lib/releases.ts +21 -0
  338. package/templates/apps/changelog/src/locales/de.ts +22 -0
  339. package/templates/apps/changelog/src/locales/en.ts +30 -0
  340. package/templates/apps/changelog/src/pages/[locale]/[slug].tsx +24 -0
  341. package/templates/apps/changelog/src/pages/[locale]/index.tsx +13 -0
  342. package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +95 -0
  343. package/templates/apps/changelog/src/pages/[slug].test.tsx +127 -0
  344. package/templates/apps/changelog/src/pages/[slug].tsx +52 -0
  345. package/templates/apps/changelog/src/pages/index.test.tsx +112 -0
  346. package/templates/apps/changelog/src/pages/index.tsx +55 -0
  347. package/templates/apps/changelog/src/pages/layout.tsx +84 -0
  348. package/templates/apps/changelog/template.json +6 -0
  349. package/templates/apps/changelog/tsconfig.json +11 -0
  350. package/templates/apps/edge-functions/README.md +55 -0
  351. package/templates/apps/edge-functions/functions/aiComplete.serverless.ts +58 -0
  352. package/templates/apps/edge-functions/functions/currencyConvert.serverless.ts +47 -0
  353. package/templates/apps/edge-functions/functions/geoGreeting.serverless.ts +44 -0
  354. package/templates/apps/edge-functions/functions/health.serverless.ts +29 -0
  355. package/templates/apps/edge-functions/functions/resolveLink.serverless.ts +32 -0
  356. package/templates/apps/edge-functions/functions/shareLink.serverless.ts +30 -0
  357. package/templates/apps/edge-functions/functions/slackNotify.serverless.ts +40 -0
  358. package/templates/apps/edge-functions/functions/verifySignature.serverless.ts +52 -0
  359. package/templates/apps/edge-functions/package.json +21 -0
  360. package/templates/apps/edge-functions/template.json +6 -0
  361. package/templates/apps/edge-functions/tsconfig.json +10 -0
  362. package/templates/apps/frontend-admin/README.md +40 -0
  363. package/templates/apps/frontend-admin/app.config.ts +37 -0
  364. package/templates/apps/frontend-admin/package.json +30 -0
  365. package/templates/apps/frontend-admin/src/config.ts +8 -0
  366. package/templates/apps/frontend-admin/src/globals.css +76 -0
  367. package/templates/apps/frontend-admin/src/globals.d.ts +6 -0
  368. package/templates/apps/frontend-admin/src/lib/admin.ts +16 -0
  369. package/templates/apps/frontend-admin/src/lib/auth.ts +24 -0
  370. package/templates/apps/frontend-admin/src/locales/de.ts +67 -0
  371. package/templates/apps/frontend-admin/src/locales/en.ts +79 -0
  372. package/templates/apps/frontend-admin/src/locales/index.ts +15 -0
  373. package/templates/apps/frontend-admin/src/pages/(marketing)/index.test.tsx +55 -0
  374. package/templates/apps/frontend-admin/src/pages/(marketing)/index.tsx +32 -0
  375. package/templates/apps/frontend-admin/src/pages/(marketing)/layout.tsx +29 -0
  376. package/templates/apps/frontend-admin/src/pages/(marketing)/login.test.tsx +73 -0
  377. package/templates/apps/frontend-admin/src/pages/(marketing)/login.tsx +35 -0
  378. package/templates/apps/frontend-admin/src/pages/admin/[entity].tsx +121 -0
  379. package/templates/apps/frontend-admin/src/pages/admin/entity.test.tsx +119 -0
  380. package/templates/apps/frontend-admin/src/pages/admin/error.tsx +20 -0
  381. package/templates/apps/frontend-admin/src/pages/admin/fallbacks.test.tsx +68 -0
  382. package/templates/apps/frontend-admin/src/pages/admin/index.test.tsx +88 -0
  383. package/templates/apps/frontend-admin/src/pages/admin/index.tsx +65 -0
  384. package/templates/apps/frontend-admin/src/pages/admin/layout.test.tsx +114 -0
  385. package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +97 -0
  386. package/templates/apps/frontend-admin/src/pages/admin/loading.tsx +15 -0
  387. package/templates/apps/frontend-admin/src/pages/admin/not-found.tsx +15 -0
  388. package/templates/apps/frontend-admin/src/pages/layout.tsx +12 -0
  389. package/templates/apps/frontend-admin/src/pages/layouts.test.tsx +54 -0
  390. package/templates/apps/frontend-admin/template.json +6 -0
  391. package/templates/apps/frontend-admin/tsconfig.json +11 -0
  392. package/templates/apps/frontend-app/README.md +98 -0
  393. package/templates/apps/frontend-app/app.config.ts +43 -0
  394. package/templates/apps/frontend-app/package.json +30 -0
  395. package/templates/apps/frontend-app/src/locales/de.ts +36 -0
  396. package/templates/apps/frontend-app/src/locales/en.ts +43 -0
  397. package/templates/apps/frontend-app/src/locales/index.ts +15 -0
  398. package/templates/apps/frontend-app/src/pages/index.test.tsx +167 -0
  399. package/templates/apps/frontend-app/src/pages/index.tsx +136 -0
  400. package/templates/apps/frontend-app/src/pages/layout.tsx +41 -0
  401. package/templates/apps/frontend-app/src/pages/schema-ui.test.tsx +99 -0
  402. package/templates/apps/frontend-app/src/pages/schema-ui.tsx +74 -0
  403. package/templates/apps/frontend-app/template.json +6 -0
  404. package/templates/apps/frontend-app/tsconfig.json +11 -0
  405. package/templates/apps/frontend-blank/README.md +18 -0
  406. package/templates/apps/frontend-blank/app.config.ts +29 -0
  407. package/templates/apps/frontend-blank/package.json +29 -0
  408. package/templates/apps/frontend-blank/src/locales/de.ts +15 -0
  409. package/templates/apps/frontend-blank/src/locales/en.ts +22 -0
  410. package/templates/apps/frontend-blank/src/locales/index.ts +15 -0
  411. package/templates/apps/frontend-blank/src/pages/index.test.tsx +55 -0
  412. package/templates/apps/frontend-blank/src/pages/index.tsx +27 -0
  413. package/templates/apps/frontend-blank/src/pages/layout.test.tsx +54 -0
  414. package/templates/apps/frontend-blank/src/pages/layout.tsx +35 -0
  415. package/templates/apps/frontend-blank/template.json +6 -0
  416. package/templates/apps/frontend-blank/tsconfig.json +11 -0
  417. package/templates/apps/frontend-contact/README.md +65 -0
  418. package/templates/apps/frontend-contact/app.config.ts +25 -0
  419. package/templates/apps/frontend-contact/functions/sendMessage.serverless.ts +69 -0
  420. package/templates/apps/frontend-contact/package.json +33 -0
  421. package/templates/apps/frontend-contact/src/components/ContactForm.island.test.tsx +142 -0
  422. package/templates/apps/frontend-contact/src/components/ContactForm.island.tsx +104 -0
  423. package/templates/apps/frontend-contact/src/config.ts +12 -0
  424. package/templates/apps/frontend-contact/src/globals.css +84 -0
  425. package/templates/apps/frontend-contact/src/lib/locale.ts +55 -0
  426. package/templates/apps/frontend-contact/src/locales/de.ts +26 -0
  427. package/templates/apps/frontend-contact/src/locales/en.ts +29 -0
  428. package/templates/apps/frontend-contact/src/pages/[locale]/index.tsx +14 -0
  429. package/templates/apps/frontend-contact/src/pages/index.test.tsx +70 -0
  430. package/templates/apps/frontend-contact/src/pages/index.tsx +63 -0
  431. package/templates/apps/frontend-contact/src/pages/layout.tsx +65 -0
  432. package/templates/apps/frontend-contact/template.json +6 -0
  433. package/templates/apps/frontend-contact/tsconfig.json +11 -0
  434. package/templates/apps/frontend-dashboard/README.md +54 -0
  435. package/templates/apps/frontend-dashboard/app.config.ts +39 -0
  436. package/templates/apps/frontend-dashboard/package.json +29 -0
  437. package/templates/apps/frontend-dashboard/src/config.ts +8 -0
  438. package/templates/apps/frontend-dashboard/src/globals.css +72 -0
  439. package/templates/apps/frontend-dashboard/src/globals.d.ts +6 -0
  440. package/templates/apps/frontend-dashboard/src/lib/auth.ts +27 -0
  441. package/templates/apps/frontend-dashboard/src/locales/de.ts +49 -0
  442. package/templates/apps/frontend-dashboard/src/locales/en.ts +60 -0
  443. package/templates/apps/frontend-dashboard/src/locales/index.ts +15 -0
  444. package/templates/apps/frontend-dashboard/src/pages/(marketing)/index.test.tsx +55 -0
  445. package/templates/apps/frontend-dashboard/src/pages/(marketing)/index.tsx +34 -0
  446. package/templates/apps/frontend-dashboard/src/pages/(marketing)/layout.tsx +32 -0
  447. package/templates/apps/frontend-dashboard/src/pages/(marketing)/login.test.tsx +74 -0
  448. package/templates/apps/frontend-dashboard/src/pages/(marketing)/login.tsx +40 -0
  449. package/templates/apps/frontend-dashboard/src/pages/dashboard/error.tsx +21 -0
  450. package/templates/apps/frontend-dashboard/src/pages/dashboard/fallbacks.test.tsx +68 -0
  451. package/templates/apps/frontend-dashboard/src/pages/dashboard/index.test.tsx +55 -0
  452. package/templates/apps/frontend-dashboard/src/pages/dashboard/index.tsx +38 -0
  453. package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.test.tsx +88 -0
  454. package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.tsx +69 -0
  455. package/templates/apps/frontend-dashboard/src/pages/dashboard/loading.tsx +21 -0
  456. package/templates/apps/frontend-dashboard/src/pages/dashboard/not-found.tsx +19 -0
  457. package/templates/apps/frontend-dashboard/src/pages/dashboard/settings.test.tsx +69 -0
  458. package/templates/apps/frontend-dashboard/src/pages/dashboard/settings.tsx +35 -0
  459. package/templates/apps/frontend-dashboard/src/pages/layout.tsx +12 -0
  460. package/templates/apps/frontend-dashboard/src/pages/layouts.test.tsx +54 -0
  461. package/templates/apps/frontend-dashboard/template.json +6 -0
  462. package/templates/apps/frontend-dashboard/tsconfig.json +11 -0
  463. package/templates/apps/frontend-docs/README.md +19 -0
  464. package/templates/apps/frontend-docs/app.config.ts +25 -0
  465. package/templates/apps/frontend-docs/package.json +29 -0
  466. package/templates/apps/frontend-docs/src/globals.css +32 -0
  467. package/templates/apps/frontend-docs/src/lib/locale.test.ts +72 -0
  468. package/templates/apps/frontend-docs/src/lib/locale.ts +56 -0
  469. package/templates/apps/frontend-docs/src/locales/de.ts +24 -0
  470. package/templates/apps/frontend-docs/src/locales/en.ts +28 -0
  471. package/templates/apps/frontend-docs/src/pages/[locale]/docs/[...slug].tsx +18 -0
  472. package/templates/apps/frontend-docs/src/pages/[locale]/index.tsx +13 -0
  473. package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +67 -0
  474. package/templates/apps/frontend-docs/src/pages/docs/[...slug].test.tsx +115 -0
  475. package/templates/apps/frontend-docs/src/pages/docs/[...slug].tsx +82 -0
  476. package/templates/apps/frontend-docs/src/pages/index.test.tsx +90 -0
  477. package/templates/apps/frontend-docs/src/pages/index.tsx +45 -0
  478. package/templates/apps/frontend-docs/src/pages/layout.test.tsx +84 -0
  479. package/templates/apps/frontend-docs/src/pages/layout.tsx +66 -0
  480. package/templates/apps/frontend-docs/template.json +6 -0
  481. package/templates/apps/frontend-docs/tsconfig.json +11 -0
  482. package/templates/apps/frontend-i18n/README.md +61 -0
  483. package/templates/apps/frontend-i18n/app.config.ts +33 -0
  484. package/templates/apps/frontend-i18n/package.json +28 -0
  485. package/templates/apps/frontend-i18n/src/globals.css +46 -0
  486. package/templates/apps/frontend-i18n/src/globals.d.ts +6 -0
  487. package/templates/apps/frontend-i18n/src/lib/locale.test.ts +72 -0
  488. package/templates/apps/frontend-i18n/src/lib/locale.ts +55 -0
  489. package/templates/apps/frontend-i18n/src/locales/de.ts +24 -0
  490. package/templates/apps/frontend-i18n/src/locales/en.ts +25 -0
  491. package/templates/apps/frontend-i18n/src/pages/[locale]/about.tsx +11 -0
  492. package/templates/apps/frontend-i18n/src/pages/[locale]/index.tsx +13 -0
  493. package/templates/apps/frontend-i18n/src/pages/[locale]/mirrors.test.tsx +50 -0
  494. package/templates/apps/frontend-i18n/src/pages/about.test.tsx +75 -0
  495. package/templates/apps/frontend-i18n/src/pages/about.tsx +31 -0
  496. package/templates/apps/frontend-i18n/src/pages/index.test.tsx +102 -0
  497. package/templates/apps/frontend-i18n/src/pages/index.tsx +43 -0
  498. package/templates/apps/frontend-i18n/src/pages/layout.test.tsx +86 -0
  499. package/templates/apps/frontend-i18n/src/pages/layout.tsx +70 -0
  500. package/templates/apps/frontend-i18n/template.json +6 -0
  501. package/templates/apps/frontend-i18n/tsconfig.json +11 -0
  502. package/templates/apps/frontend-landing/README.md +17 -0
  503. package/templates/apps/frontend-landing/app.config.ts +25 -0
  504. package/templates/apps/frontend-landing/package.json +29 -0
  505. package/templates/apps/frontend-landing/src/globals.css +23 -0
  506. package/templates/apps/frontend-landing/src/lib/locale.test.ts +72 -0
  507. package/templates/apps/frontend-landing/src/lib/locale.ts +55 -0
  508. package/templates/apps/frontend-landing/src/locales/de.ts +24 -0
  509. package/templates/apps/frontend-landing/src/locales/en.ts +26 -0
  510. package/templates/apps/frontend-landing/src/pages/[locale]/index.tsx +13 -0
  511. package/templates/apps/frontend-landing/src/pages/[locale]/mirrors.test.tsx +37 -0
  512. package/templates/apps/frontend-landing/src/pages/index.test.tsx +110 -0
  513. package/templates/apps/frontend-landing/src/pages/index.tsx +75 -0
  514. package/templates/apps/frontend-landing/src/pages/layout.test.tsx +84 -0
  515. package/templates/apps/frontend-landing/src/pages/layout.tsx +66 -0
  516. package/templates/apps/frontend-landing/template.json +6 -0
  517. package/templates/apps/frontend-landing/tsconfig.json +11 -0
  518. package/templates/apps/frontend-spa/README.md +45 -0
  519. package/templates/apps/frontend-spa/app.config.ts +27 -0
  520. package/templates/apps/frontend-spa/package.json +29 -0
  521. package/templates/apps/frontend-spa/src/globals.css +84 -0
  522. package/templates/apps/frontend-spa/src/locales/de.ts +22 -0
  523. package/templates/apps/frontend-spa/src/locales/en.ts +29 -0
  524. package/templates/apps/frontend-spa/src/locales/index.ts +15 -0
  525. package/templates/apps/frontend-spa/src/pages/index.test.tsx +137 -0
  526. package/templates/apps/frontend-spa/src/pages/index.tsx +123 -0
  527. package/templates/apps/frontend-spa/src/pages/layout.tsx +27 -0
  528. package/templates/apps/frontend-spa/template.json +6 -0
  529. package/templates/apps/frontend-spa/tsconfig.json +11 -0
  530. package/templates/apps/frontend-ssr/README.md +68 -0
  531. package/templates/apps/frontend-ssr/app.config.ts +32 -0
  532. package/templates/apps/frontend-ssr/package.json +29 -0
  533. package/templates/apps/frontend-ssr/src/globals.css +67 -0
  534. package/templates/apps/frontend-ssr/src/locales/de.ts +41 -0
  535. package/templates/apps/frontend-ssr/src/locales/en.ts +54 -0
  536. package/templates/apps/frontend-ssr/src/locales/index.ts +16 -0
  537. package/templates/apps/frontend-ssr/src/pages/feed-swr.test.tsx +69 -0
  538. package/templates/apps/frontend-ssr/src/pages/feed-swr.tsx +54 -0
  539. package/templates/apps/frontend-ssr/src/pages/feed.test.tsx +73 -0
  540. package/templates/apps/frontend-ssr/src/pages/feed.tsx +64 -0
  541. package/templates/apps/frontend-ssr/src/pages/index.test.tsx +89 -0
  542. package/templates/apps/frontend-ssr/src/pages/index.tsx +72 -0
  543. package/templates/apps/frontend-ssr/src/pages/layout.tsx +37 -0
  544. package/templates/apps/frontend-ssr/template.json +6 -0
  545. package/templates/apps/frontend-ssr/tsconfig.json +11 -0
  546. package/templates/apps/frontend-ssr-api/README.md +50 -0
  547. package/templates/apps/frontend-ssr-api/app.config.ts +43 -0
  548. package/templates/apps/frontend-ssr-api/package.json +30 -0
  549. package/templates/apps/frontend-ssr-api/src/globals.css +38 -0
  550. package/templates/apps/frontend-ssr-api/src/globals.d.ts +6 -0
  551. package/templates/apps/frontend-ssr-api/src/locales/de.ts +20 -0
  552. package/templates/apps/frontend-ssr-api/src/locales/en.ts +31 -0
  553. package/templates/apps/frontend-ssr-api/src/locales/index.ts +16 -0
  554. package/templates/apps/frontend-ssr-api/src/pages/index.test.tsx +105 -0
  555. package/templates/apps/frontend-ssr-api/src/pages/index.tsx +83 -0
  556. package/templates/apps/frontend-ssr-api/src/pages/layout.tsx +28 -0
  557. package/templates/apps/frontend-ssr-api/template.json +6 -0
  558. package/templates/apps/frontend-ssr-api/tsconfig.json +11 -0
  559. package/templates/apps/frontend-static-blog/README.md +49 -0
  560. package/templates/apps/frontend-static-blog/app.config.ts +34 -0
  561. package/templates/apps/frontend-static-blog/package.json +28 -0
  562. package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.test.tsx +65 -0
  563. package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.tsx +35 -0
  564. package/templates/apps/frontend-static-blog/src/content/posts.ts +64 -0
  565. package/templates/apps/frontend-static-blog/src/globals.css +75 -0
  566. package/templates/apps/frontend-static-blog/src/lib/locale.test.ts +72 -0
  567. package/templates/apps/frontend-static-blog/src/lib/locale.ts +55 -0
  568. package/templates/apps/frontend-static-blog/src/locales/de.ts +19 -0
  569. package/templates/apps/frontend-static-blog/src/locales/en.ts +26 -0
  570. package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/[slug].tsx +20 -0
  571. package/templates/apps/frontend-static-blog/src/pages/[locale]/index.tsx +13 -0
  572. package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +62 -0
  573. package/templates/apps/frontend-static-blog/src/pages/blog/[slug].test.tsx +116 -0
  574. package/templates/apps/frontend-static-blog/src/pages/blog/[slug].tsx +69 -0
  575. package/templates/apps/frontend-static-blog/src/pages/index.test.tsx +100 -0
  576. package/templates/apps/frontend-static-blog/src/pages/index.tsx +58 -0
  577. package/templates/apps/frontend-static-blog/src/pages/layout.tsx +63 -0
  578. package/templates/apps/frontend-static-blog/template.json +6 -0
  579. package/templates/apps/frontend-static-blog/tsconfig.json +11 -0
  580. package/templates/baselines/bare/.env.example +37 -0
  581. package/templates/baselines/bare/README.md +47 -0
  582. package/templates/baselines/bare/baseline.json +31 -0
  583. package/templates/baselines/bare/deploy/README.md +43 -0
  584. package/templates/baselines/bare/deploy/voltro.service.example +36 -0
  585. package/templates/baselines/compose/.env.example +46 -0
  586. package/templates/baselines/compose/README.md +69 -0
  587. package/templates/baselines/compose/baseline.json +51 -0
  588. package/templates/baselines/compose/docker/.dockerignore +38 -0
  589. package/templates/baselines/compose/docker/api.Dockerfile +57 -0
  590. package/templates/baselines/compose/docker/dev.Dockerfile +35 -0
  591. package/templates/baselines/compose/docker/web.Dockerfile +59 -0
  592. package/templates/baselines/compose/docker-compose.dev.yml +89 -0
  593. package/templates/baselines/compose/docker-compose.prod.yml +87 -0
  594. package/templates/baselines/compose/docker-compose.yml +41 -0
  595. package/templates/baselines/compose-mariadb/.env.example +57 -0
  596. package/templates/baselines/compose-mariadb/README.md +78 -0
  597. package/templates/baselines/compose-mariadb/baseline.json +51 -0
  598. package/templates/baselines/compose-mariadb/docker/.dockerignore +38 -0
  599. package/templates/baselines/compose-mariadb/docker/api.Dockerfile +57 -0
  600. package/templates/baselines/compose-mariadb/docker/dev.Dockerfile +35 -0
  601. package/templates/baselines/compose-mariadb/docker/mariadb-init.sql +6 -0
  602. package/templates/baselines/compose-mariadb/docker/web.Dockerfile +59 -0
  603. package/templates/baselines/compose-mariadb/docker-compose.dev.yml +117 -0
  604. package/templates/baselines/compose-mariadb/docker-compose.prod.yml +114 -0
  605. package/templates/baselines/compose-mariadb/docker-compose.yml +79 -0
  606. package/templates/baselines/helm/.env.example +39 -0
  607. package/templates/baselines/helm/README.md +98 -0
  608. package/templates/baselines/helm/baseline.json +53 -0
  609. package/templates/baselines/helm/charts/voltro-app/.helmignore +10 -0
  610. package/templates/baselines/helm/charts/voltro-app/Chart.yaml +10 -0
  611. package/templates/baselines/helm/charts/voltro-app/templates/_helpers.tpl +36 -0
  612. package/templates/baselines/helm/charts/voltro-app/templates/configmap.yaml +13 -0
  613. package/templates/baselines/helm/charts/voltro-app/templates/deployment-api.yaml +120 -0
  614. package/templates/baselines/helm/charts/voltro-app/templates/deployment-web.yaml +45 -0
  615. package/templates/baselines/helm/charts/voltro-app/templates/ingress.yaml +37 -0
  616. package/templates/baselines/helm/charts/voltro-app/templates/postgres-service.yaml +19 -0
  617. package/templates/baselines/helm/charts/voltro-app/templates/postgres-statefulset.yaml +73 -0
  618. package/templates/baselines/helm/charts/voltro-app/templates/secret.yaml +33 -0
  619. package/templates/baselines/helm/charts/voltro-app/templates/service-api.yaml +19 -0
  620. package/templates/baselines/helm/charts/voltro-app/templates/service-web.yaml +19 -0
  621. package/templates/baselines/helm/charts/voltro-app/values-dev.yaml +21 -0
  622. package/templates/baselines/helm/charts/voltro-app/values-prod.yaml +58 -0
  623. package/templates/baselines/helm/charts/voltro-app/values-staging.yaml +25 -0
  624. package/templates/baselines/helm/charts/voltro-app/values.yaml +109 -0
  625. package/templates/baselines/helm/deploy/README.md +94 -0
  626. package/templates/patches/@effect__cluster@0.59.0.patch +262 -0
@@ -0,0 +1,72 @@
1
+ // The URL-prefix i18n helpers — the load-bearing pure functions that drive the
2
+ // [locale] mirror routing + the language switcher. `useUrlLocale` (the one hook
3
+ // in this module) reads useLocation from @voltro/web; the rest are pure string
4
+ // functions we can exercise directly.
5
+
6
+ import { describe, expect, test } from 'vitest'
7
+ import {
8
+ SUPPORTED_LOCALES,
9
+ DEFAULT_LOCALE,
10
+ getCatalog,
11
+ localeFromPathname,
12
+ withLocalePrefix,
13
+ stripLocalePrefix,
14
+ } from './locale'
15
+
16
+ describe('locale constants', () => {
17
+ test('supports en + de with en as the default', () => {
18
+ expect(SUPPORTED_LOCALES).toEqual(['en', 'de'])
19
+ expect(DEFAULT_LOCALE).toBe('en')
20
+ })
21
+ })
22
+
23
+ describe('getCatalog', () => {
24
+ test('returns the matching catalog for a supported locale', () => {
25
+ expect(getCatalog('de')['nav.language']).toBe('Sprache')
26
+ expect(getCatalog('en')['nav.language']).toBe('Language')
27
+ })
28
+
29
+ test('falls back to the default catalog for an unsupported locale', () => {
30
+ expect(getCatalog('zz')['nav.language']).toBe('Language')
31
+ })
32
+ })
33
+
34
+ describe('localeFromPathname', () => {
35
+ test('extracts a prefixed locale', () => {
36
+ expect(localeFromPathname('/de')).toBe('de')
37
+ expect(localeFromPathname('/de/about')).toBe('de')
38
+ })
39
+
40
+ test('defaults to en for the bare path or an unknown prefix', () => {
41
+ expect(localeFromPathname('/')).toBe('en')
42
+ expect(localeFromPathname('/about')).toBe('en')
43
+ expect(localeFromPathname('/fr/about')).toBe('en')
44
+ })
45
+ })
46
+
47
+ describe('withLocalePrefix', () => {
48
+ test('leaves default-locale paths bare', () => {
49
+ expect(withLocalePrefix('/about', 'en')).toBe('/about')
50
+ expect(withLocalePrefix('/', 'en')).toBe('/')
51
+ })
52
+
53
+ test('prefixes non-default locales, mapping / to /<locale>', () => {
54
+ expect(withLocalePrefix('/about', 'de')).toBe('/de/about')
55
+ expect(withLocalePrefix('/', 'de')).toBe('/de')
56
+ })
57
+ })
58
+
59
+ describe('stripLocalePrefix', () => {
60
+ test('removes a locale prefix, mapping /<locale> back to /', () => {
61
+ expect(stripLocalePrefix('/de/about')).toBe('/about')
62
+ expect(stripLocalePrefix('/de')).toBe('/')
63
+ })
64
+
65
+ test('leaves an unprefixed path unchanged', () => {
66
+ expect(stripLocalePrefix('/about')).toBe('/about')
67
+ })
68
+
69
+ test('round-trips with withLocalePrefix', () => {
70
+ expect(stripLocalePrefix(withLocalePrefix('/about', 'de'))).toBe('/about')
71
+ })
72
+ })
@@ -0,0 +1,55 @@
1
+ // The single source of truth for this app's locales + the URL-prefix
2
+ // helpers. `app.config.ts` lists the same codes for the build pipeline;
3
+ // the [locale] mirror pages + the layout's <I18nProvider> read THIS file.
4
+ //
5
+ // Keep SUPPORTED_LOCALES / DEFAULT_LOCALE in sync with `locales` /
6
+ // `defaultLocale` in app.config.ts.
7
+
8
+ import { useLocation } from '@voltro/web'
9
+ import en from '../locales/en'
10
+ import de from '../locales/de'
11
+
12
+ export const SUPPORTED_LOCALES = ['en', 'de'] as const
13
+ export const DEFAULT_LOCALE = 'en'
14
+
15
+ export type Locale = (typeof SUPPORTED_LOCALES)[number]
16
+
17
+ // The catalogs, keyed by code. The layout picks one to feed the inner
18
+ // <I18nProvider>; `meta` (build-time, per locale) reads one via getCatalog.
19
+ export const CATALOGS = { en, de } as const
20
+
21
+ // Every catalog mirrors `en`'s keys (defineLocale enforces it), so a
22
+ // plain string-record over en's keys is the common type both satisfy —
23
+ // what `meta` reads at build time (`c['meta.home.title']` → string).
24
+ export type Messages = Record<keyof typeof en, string>
25
+
26
+ const isSupported = (v: string): v is Locale =>
27
+ (SUPPORTED_LOCALES as ReadonlyArray<string>).includes(v)
28
+
29
+ export const getCatalog = (locale: string): Messages =>
30
+ isSupported(locale) ? CATALOGS[locale] : CATALOGS[DEFAULT_LOCALE]
31
+
32
+ // `/de/about` → 'de'; `/about` → 'en' (default lives at the bare path).
33
+ export const localeFromPathname = (pathname: string): Locale => {
34
+ const match = /^\/([a-z]{2})(?:\/|$)/.exec(pathname)
35
+ return match && isSupported(match[1]!) ? (match[1] as Locale) : DEFAULT_LOCALE
36
+ }
37
+
38
+ // Reactive: useLocation re-renders on client-side nav, so the layout's
39
+ // provider swaps catalogs when you move between /about and /de/about
40
+ // without a full reload.
41
+ export const useUrlLocale = (): Locale => localeFromPathname(useLocation())
42
+
43
+ // '/about' + 'de' → '/de/about'; the default locale stays bare ('/about').
44
+ export const withLocalePrefix = (path: string, locale: string): string => {
45
+ if (locale === DEFAULT_LOCALE) return path
46
+ const p = path.startsWith('/') ? path : `/${path}`
47
+ return p === '/' ? `/${locale}` : `/${locale}${p}`
48
+ }
49
+
50
+ // '/de/about' → '/about'; '/about' → '/about'. The inverse of the above,
51
+ // used to keep the active path when switching language.
52
+ export const stripLocalePrefix = (pathname: string): string => {
53
+ const match = /^\/([a-z]{2})(\/.*|$)/.exec(pathname)
54
+ return match && isSupported(match[1]!) ? match[2] || '/' : pathname
55
+ }
@@ -0,0 +1,19 @@
1
+ // German catalog. MUST mirror en.ts key-for-key — `defineLocale<typeof en>()`
2
+ // returns a curried function that fails `tsc --noEmit` on a missing or extra
3
+ // key, so translations can never silently drift out of sync.
4
+ import { defineLocale } from '@voltro/i18n'
5
+ import en from './en'
6
+
7
+ export default defineLocale<typeof en>()({
8
+ 'nav.language': 'Sprache',
9
+
10
+ 'home.heading': 'Blog',
11
+ 'home.postCount': '{count} Beiträge · zur Build-Zeit vorgerendert',
12
+
13
+ 'post.readingTime': '{minutes} Min.',
14
+ 'post.readingTimeRead': '{minutes} Min. Lesezeit',
15
+ 'post.backToList': '← Alle Beiträge',
16
+
17
+ 'meta.home.title': '{{capProjectName}} — Blog',
18
+ 'meta.home.description': 'Ein statischer, vorgerenderter Blog, gebaut mit Voltro.',
19
+ })
@@ -0,0 +1,26 @@
1
+ // Base catalog — the source of truth. Every other locale (de.ts) mirrors
2
+ // these keys EXACTLY; `defineLocale<typeof en>()` enforces that at `tsc`.
3
+ //
4
+ // CHROME ONLY: nav, headings, section labels + page meta live here. Post
5
+ // titles / excerpts / bodies / dates are CONTENT (src/content/posts.ts) and
6
+ // are NOT translated — they stay in the data source.
7
+ //
8
+ // Keep ICU placeholders (`{count}`, `{minutes}`) identical across locales —
9
+ // react-intl validates them at render time. `{{capProjectName}}` is a scaffold
10
+ // token (replaced by `voltro create-project`), only ever read from `meta`
11
+ // (plain property access, never parsed as ICU) — leave it verbatim.
12
+ import { defineCatalog } from '@voltro/i18n'
13
+
14
+ export default defineCatalog({
15
+ 'nav.language': 'Language',
16
+
17
+ 'home.heading': 'Blog',
18
+ 'home.postCount': '{count} posts · pre-rendered at build time',
19
+
20
+ 'post.readingTime': '{minutes} min',
21
+ 'post.readingTimeRead': '{minutes} min read',
22
+ 'post.backToList': '← All posts',
23
+
24
+ 'meta.home.title': '{{capProjectName}} — Blog',
25
+ 'meta.home.description': 'A static, pre-rendered blog built with Voltro.',
26
+ } as const)
@@ -0,0 +1,20 @@
1
+ // Locale mirror of `/blog/[slug]` → renders `/de/blog/<slug>`. Re-exports the
2
+ // bare post page verbatim; only `getStaticPaths` differs.
3
+ //
4
+ // This is a DYNAMIC route under a DYNAMIC locale segment, so the paths to
5
+ // pre-render are the CROSS-PRODUCT of every non-default locale × every post
6
+ // slug (the bare page enumerates just the slugs; here each slug is emitted
7
+ // once per non-default locale). `voltro build` produces one HTML file per
8
+ // pair, e.g. /de/blog/hello-static.
9
+ import { SUPPORTED_LOCALES, DEFAULT_LOCALE } from '../../../lib/locale'
10
+ import { posts } from '../../../content/posts'
11
+
12
+ export { default } from '../../blog/[slug]'
13
+ export { renderMode, interactive, loader, meta } from '../../blog/[slug]'
14
+
15
+ export const getStaticPaths = async (): Promise<
16
+ Array<{ params: { locale: string; slug: string } }>
17
+ > =>
18
+ SUPPORTED_LOCALES
19
+ .filter((l) => l !== DEFAULT_LOCALE)
20
+ .flatMap((locale) => posts.map((post) => ({ params: { locale, slug: post.slug } })))
@@ -0,0 +1,13 @@
1
+ // Locale mirror of `/` → renders `/de`. Re-exports the bare page verbatim
2
+ // (the component reads its locale from the URL via the layout's provider);
3
+ // `getStaticPaths` tells `voltro build` which locale-prefixed paths to emit.
4
+ // The default locale is NOT listed here — it lives at the bare `/`.
5
+ import { SUPPORTED_LOCALES, DEFAULT_LOCALE } from '../../lib/locale'
6
+
7
+ export { default } from '../index'
8
+ export { renderMode, interactive, meta } from '../index'
9
+
10
+ export const getStaticPaths = async (): Promise<Array<{ params: { locale: string } }>> =>
11
+ SUPPORTED_LOCALES
12
+ .filter((l) => l !== DEFAULT_LOCALE)
13
+ .map((locale) => ({ params: { locale } }))
@@ -0,0 +1,62 @@
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 post mirror emits the CROSS-PRODUCT of non-default
7
+ // locales × post slugs (here: de × every post).
8
+
9
+ import { describe, expect, test, vi } from 'vitest'
10
+ import type { ComponentType } from 'react'
11
+ import { posts } from '../../content/posts'
12
+
13
+ // The mirrors transitively import the base pages, which import @voltro/web
14
+ // (island / loader / notFound / useLocation via lib/locale) and @voltro/i18n.
15
+ // Stub both so importing the modules doesn't require the real runtimes.
16
+ vi.mock('@voltro/web', () => ({
17
+ useLoaderData: () => ({}),
18
+ notFound: (detail?: string) => {
19
+ throw new Error(detail)
20
+ },
21
+ island: (Component: ComponentType<unknown>) => Component,
22
+ useLocation: () => '/',
23
+ }))
24
+ vi.mock('@voltro/i18n', () => ({
25
+ defineCatalog: <T,>(c: T): T => c,
26
+ defineLocale:
27
+ () =>
28
+ <T,>(c: T): T =>
29
+ c,
30
+ useLocale: () => 'en',
31
+ useT: (id: string) => id,
32
+ T: ({ id }: { id: string }) => id,
33
+ }))
34
+
35
+ const indexMirror = await import('./index')
36
+ const postMirror = await import('./blog/[slug]')
37
+
38
+ describe('home mirror — getStaticPaths', () => {
39
+ test('emits only non-default locales (just /de)', async () => {
40
+ const paths = await indexMirror.getStaticPaths()
41
+ expect(paths).toEqual([{ params: { locale: 'de' } }])
42
+ })
43
+
44
+ test('re-exports the base page component + its meta function', () => {
45
+ expect(typeof indexMirror.default).toBe('function')
46
+ expect(indexMirror.meta({ locale: 'de' }).title).toContain('Blog')
47
+ })
48
+ })
49
+
50
+ describe('post mirror — getStaticPaths (locale × slug cross-product)', () => {
51
+ test('emits one path per non-default locale × post slug', async () => {
52
+ const paths = await postMirror.getStaticPaths()
53
+ expect(paths).toEqual(posts.map((post) => ({ params: { locale: 'de', slug: post.slug } })))
54
+ })
55
+
56
+ test('re-exports the base post page + its config exports', () => {
57
+ expect(typeof postMirror.default).toBe('function')
58
+ expect(postMirror.renderMode).toBe('static')
59
+ expect(postMirror.interactive).toBe('islands')
60
+ expect(typeof postMirror.loader).toBe('function')
61
+ })
62
+ })
@@ -0,0 +1,116 @@
1
+ // @vitest-environment jsdom
2
+ //
3
+ // `/blog/[slug]` — the three static-generation exports: `getStaticPaths`
4
+ // (enumerate slugs), `loader` (fetch this post, `notFound()` for a miss), and
5
+ // `meta(loaderData)` (per-post title). Plus the article render. The page reads
6
+ // `useLoaderData` and renders a `<ReadingProgress>` island — both come from
7
+ // `@voltro/web`, mocked here so `useLoaderData` returns our post and `island`
8
+ // is the identity wrapper.
9
+
10
+ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
11
+ import { act, createElement, type ComponentType, type ReactNode } from 'react'
12
+ import { createRoot, type Root } from 'react-dom/client'
13
+ import { posts, type Post } from '../../content/posts'
14
+ import en from '../../locales/en'
15
+
16
+ const loaderData = vi.fn<() => Post>()
17
+
18
+ const fill = (msg: string, values?: Record<string, unknown>): string =>
19
+ values ? msg.replace(/\{(\w+)\}/g, (_, k: string) => String(values[k] ?? `{${k}}`)) : msg
20
+
21
+ class NotFoundError extends Error {}
22
+ vi.mock('@voltro/web', () => ({
23
+ useLoaderData: () => loaderData(),
24
+ // Real `notFound()` throws to abort the loader; a distinct error lets us
25
+ // assert the miss path without pulling in the router runtime.
26
+ notFound: (detail?: string) => {
27
+ throw new NotFoundError(detail)
28
+ },
29
+ // Real `island()` wraps the component in a hydration marker; identity keeps
30
+ // the inner component observable in a unit test.
31
+ island: (Component: ComponentType<unknown>) => Component,
32
+ // `withLocalePrefix` (lib/locale) reads useLocation; the post page pins the
33
+ // locale via useLocale, so any bare path is fine here.
34
+ useLocation: () => '/',
35
+ }))
36
+
37
+ // The page renders chrome (back-link, reading-time) via <T>/useLocale — resolve
38
+ // them against the real English catalog with a controllable "current locale".
39
+ vi.mock('@voltro/i18n', () => ({
40
+ defineCatalog: <T,>(c: T): T => c,
41
+ defineLocale:
42
+ () =>
43
+ <T,>(c: T): T =>
44
+ c,
45
+ useLocale: () => 'en',
46
+ T: ({ id, values }: { id: string; values?: Record<string, unknown> }) =>
47
+ fill((en as Record<string, string>)[id] ?? id, values),
48
+ }))
49
+
50
+ const { default: BlogPost, getStaticPaths, loader, meta } = await import('./[slug]')
51
+
52
+ ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
53
+
54
+ let container: HTMLDivElement
55
+ let root: Root
56
+
57
+ const render = (node: ReactNode): void => {
58
+ container = document.createElement('div')
59
+ document.body.appendChild(container)
60
+ act(() => {
61
+ root = createRoot(container)
62
+ root.render(node)
63
+ })
64
+ }
65
+
66
+ beforeEach(() => {
67
+ loaderData.mockReset()
68
+ })
69
+
70
+ afterEach(() => {
71
+ if (root) act(() => root.unmount())
72
+ container?.remove()
73
+ document.body.innerHTML = ''
74
+ })
75
+
76
+ describe('blog [slug] — getStaticPaths', () => {
77
+ test('enumerates one path per post', async () => {
78
+ const paths = await getStaticPaths()
79
+ expect(paths.map((p) => p.params.slug)).toEqual(posts.map((p) => p.slug))
80
+ })
81
+ })
82
+
83
+ describe('blog [slug] — loader', () => {
84
+ test('returns the matching post for a known slug', async () => {
85
+ const post = await loader({ params: { slug: 'hello-static' } } as never)
86
+ expect(post.slug).toBe('hello-static')
87
+ expect(post.title).toContain('zero JavaScript')
88
+ })
89
+
90
+ test('throws notFound for an unknown slug', async () => {
91
+ await expect(loader({ params: { slug: 'nope' } } as never)).rejects.toBeInstanceOf(
92
+ NotFoundError,
93
+ )
94
+ })
95
+ })
96
+
97
+ describe('blog [slug] — meta', () => {
98
+ test('builds a per-post title + description from the loader data', () => {
99
+ const post = posts[0]!
100
+ const m = meta({ loaderData: post })
101
+ expect(m.title).toContain(post.title)
102
+ expect(m.description).toBe(post.excerpt)
103
+ })
104
+ })
105
+
106
+ describe('blog [slug] — render', () => {
107
+ test('renders the post title, meta line, and each body paragraph', () => {
108
+ const post = posts[1]!
109
+ loaderData.mockReturnValue(post)
110
+ render(createElement(BlogPost))
111
+ expect(container.querySelector('article h1')?.textContent).toBe(post.title)
112
+ expect(container.textContent).toContain(`${post.readingMinutes} min read`)
113
+ const paras = post.body.split('\n\n')
114
+ expect(container.querySelectorAll('article p').length).toBeGreaterThanOrEqual(paras.length)
115
+ })
116
+ })
@@ -0,0 +1,69 @@
1
+ // A dynamic route — `/blog/<slug>` — pre-rendered STATICALLY, one HTML
2
+ // file per post. Three exports make that work:
3
+ //
4
+ // • getStaticPaths → enumerate which slugs to build (from the content
5
+ // source; point it at a CMS/DB in a real app).
6
+ // • loader → fetch THIS post's data; runs at build per slug.
7
+ // • meta(loaderData)→ per-post <title>/<description> baked into the HTML.
8
+ //
9
+ // `interactive: 'islands'` hydrates ONLY the <ReadingProgress> island; the
10
+ // article itself is inert HTML.
11
+
12
+ import type { ReactNode } from 'react'
13
+ import { useLoaderData, notFound, type LoaderFn, type PageMeta } from '@voltro/web'
14
+ import { T, useLocale } from '@voltro/i18n'
15
+ import { posts, type Post } from '../../content/posts'
16
+ import { withLocalePrefix } from '../../lib/locale'
17
+ import ReadingProgress from '../../components/ReadingProgress.island'
18
+
19
+ export const renderMode = 'static' as const
20
+ export const interactive = 'islands' as const
21
+
22
+ // Which concrete paths to pre-render. Every returned `params` becomes one
23
+ // built HTML file; un-enumerated slugs are simply not built (→ 404).
24
+ export const getStaticPaths = async (): Promise<Array<{ params: { slug: string } }>> =>
25
+ posts.map((post) => ({ params: { slug: post.slug } }))
26
+
27
+ // Runs at build time for each enumerated slug. Returning `notFound()` skips
28
+ // the artifact at build / 404s at runtime — defensive, though getStaticPaths
29
+ // only ever feeds us slugs that exist.
30
+ export const loader: LoaderFn<Post> = async ({ params }) => {
31
+ const post = posts.find((p) => p.slug === params.slug)
32
+ if (!post) return notFound(`post ${params.slug}`)
33
+ return post
34
+ }
35
+
36
+ // `meta` as a function of the loader data → correct per-post <title> in the
37
+ // pre-rendered HTML (great for SEO + social cards). The title/description are
38
+ // POST CONTENT, not chrome, so they are the same on `/blog/x` and `/de/blog/x`
39
+ // — no per-locale variant needed here.
40
+ export const meta = ({ loaderData }: { loaderData: Post }): PageMeta => ({
41
+ title: `${loaderData.title} — {{capProjectName}}`,
42
+ description: loaderData.excerpt,
43
+ })
44
+
45
+ export default function BlogPost(): ReactNode {
46
+ const post = useLoaderData<Post>()
47
+ // The active locale (from the layout's URL provider) keeps the "all posts"
48
+ // link on the current language.
49
+ const locale = useLocale()
50
+ return (
51
+ <main>
52
+ <ReadingProgress />
53
+ <p>
54
+ <a href={withLocalePrefix('/', locale)}>
55
+ <T id="post.backToList" />
56
+ </a>
57
+ </p>
58
+ <article>
59
+ <h1>{post.title}</h1>
60
+ <p className="muted">
61
+ {post.date} · <T id="post.readingTimeRead" values={{ minutes: post.readingMinutes }} />
62
+ </p>
63
+ {post.body.split('\n\n').map((para, i) => (
64
+ <p key={i}>{para}</p>
65
+ ))}
66
+ </article>
67
+ </main>
68
+ )
69
+ }
@@ -0,0 +1,100 @@
1
+ // @vitest-environment jsdom
2
+ //
3
+ // `/` blog index — a static list page. Two things worth testing:
4
+ // 1. `meta({ locale })` is localized — it reads the REAL catalog via
5
+ // getCatalog, so we assert en vs de descriptions directly (no mock).
6
+ // 2. the rendered list shows localized chrome + locale-prefixed links. The
7
+ // page pulls chrome from @voltro/i18n (<T>, useLocale); we mock those to
8
+ // resolve against the real catalogs for a controllable "current locale",
9
+ // so the assertions are genuine localized strings. Post titles/excerpts
10
+ // come from the real content source and are NOT translated.
11
+
12
+ import { afterEach, describe, expect, test, vi } from 'vitest'
13
+ import { act, createElement, type ReactNode } from 'react'
14
+ import { createRoot, type Root } from 'react-dom/client'
15
+ import { posts } from '../content/posts'
16
+ import en from '../locales/en'
17
+ import de from '../locales/de'
18
+
19
+ const catalogs: Record<string, Record<string, string>> = { en, de }
20
+ let currentLocale = 'en'
21
+
22
+ // Minimal ICU-ish interpolation: replace {key} with values[key].
23
+ const fill = (msg: string, values?: Record<string, unknown>): string =>
24
+ values ? msg.replace(/\{(\w+)\}/g, (_, k: string) => String(values[k] ?? `{${k}}`)) : msg
25
+
26
+ vi.mock('@voltro/i18n', () => ({
27
+ // The locale catalogs call these at module top-level; identity mirrors the
28
+ // real (compile-time-only) implementations so importing them doesn't throw.
29
+ defineCatalog: <T,>(c: T): T => c,
30
+ defineLocale:
31
+ () =>
32
+ <T,>(c: T): T =>
33
+ c,
34
+ useLocale: () => currentLocale,
35
+ useT: (id: string, values?: Record<string, unknown>) =>
36
+ fill(catalogs[currentLocale]?.[id] ?? id, values),
37
+ T: ({ id, values }: { id: string; values?: Record<string, unknown> }) =>
38
+ fill(catalogs[currentLocale]?.[id] ?? id, values),
39
+ }))
40
+
41
+ const { default: Index, meta, renderMode, interactive } = await import('./index')
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
+ const render = (node: ReactNode): void => {
49
+ container = document.createElement('div')
50
+ document.body.appendChild(container)
51
+ act(() => {
52
+ root = createRoot(container)
53
+ root.render(node)
54
+ })
55
+ }
56
+
57
+ afterEach(() => {
58
+ currentLocale = 'en'
59
+ if (root) act(() => root.unmount())
60
+ container?.remove()
61
+ document.body.innerHTML = ''
62
+ })
63
+
64
+ describe('index — config + localized meta', () => {
65
+ test('is a static page with zero client JS', () => {
66
+ expect(renderMode).toBe('static')
67
+ expect(interactive).toBe('none')
68
+ })
69
+
70
+ test('meta() returns a localized title + description per locale', () => {
71
+ expect(meta({ locale: 'en' }).title).toContain('Blog')
72
+ expect(meta({ locale: 'en' }).description).toContain('pre-rendered')
73
+ expect(meta({ locale: 'de' }).description).toContain('vorgerenderter')
74
+ })
75
+
76
+ test('meta() falls back to the default (en) catalog for an unknown locale', () => {
77
+ expect(meta({ locale: 'zz' }).description).toContain('pre-rendered')
78
+ })
79
+ })
80
+
81
+ describe('index — localized render', () => {
82
+ test('lists every post, newest first, with bare links + English chrome (en)', () => {
83
+ currentLocale = 'en'
84
+ render(createElement(Index))
85
+ const links = Array.from(container.querySelectorAll('.post-list a'))
86
+ expect(links).toHaveLength(posts.length)
87
+ // Newest-first: the 2025-02-03 post precedes the 2025-01-15 one.
88
+ expect(links[0]!.getAttribute('href')).toBe('/blog/cms-to-ssg')
89
+ expect(links[links.length - 1]!.getAttribute('href')).toBe('/blog/hello-static')
90
+ expect(container.textContent).toContain(`${posts.length} posts`)
91
+ })
92
+
93
+ test('prefixes post links with /de and renders German chrome (de)', () => {
94
+ currentLocale = 'de'
95
+ render(createElement(Index))
96
+ const links = Array.from(container.querySelectorAll('.post-list a'))
97
+ expect(links[0]!.getAttribute('href')).toBe('/de/blog/cms-to-ssg')
98
+ expect(container.textContent).toContain(`${posts.length} Beiträge`)
99
+ })
100
+ })
@@ -0,0 +1,58 @@
1
+ // Blog index, mounted at `/` (default locale) and mirrored at `/de` by
2
+ // src/pages/[locale]/index.tsx. The post LIST is pure content — no
3
+ // interactivity — so it ships zero JavaScript: `interactive = 'none'` strips
4
+ // every framework <script> from the rendered HTML. The list is read from the
5
+ // same content source the [slug] pages + getStaticPaths use.
6
+ //
7
+ // `meta` is a FUNCTION of `{ locale }` — that's what localises each variant's
8
+ // <title> / <description> in the pre-rendered HTML. Post titles/excerpts are
9
+ // CONTENT (src/content/posts.ts) and are NOT translated; only the chrome
10
+ // (heading, count line, reading-time label) comes from the catalogs.
11
+
12
+ import type { ReactNode } from 'react'
13
+ import type { PageMeta } from '@voltro/web'
14
+ import { T, useLocale } from '@voltro/i18n'
15
+ import { posts } from '../content/posts'
16
+ import { getCatalog, withLocalePrefix } from '../lib/locale'
17
+
18
+ export const renderMode = 'static' as const
19
+ export const interactive = 'none' as const
20
+
21
+ export const meta = ({ locale }: { locale: string }): PageMeta => {
22
+ const c = getCatalog(locale)
23
+ return {
24
+ title: c['meta.home.title'],
25
+ description: c['meta.home.description'],
26
+ }
27
+ }
28
+
29
+ // Newest first — sort a copy so the source array stays stable.
30
+ const byDateDesc = [...posts].sort((a, b) => b.date.localeCompare(a.date))
31
+
32
+ export default function Index(): ReactNode {
33
+ const locale = useLocale()
34
+ return (
35
+ <main>
36
+ <h1>
37
+ <T id="home.heading" />
38
+ </h1>
39
+ <p className="muted">
40
+ <T id="home.postCount" values={{ count: posts.length }} />
41
+ </p>
42
+ <ul className="post-list">
43
+ {byDateDesc.map((post) => (
44
+ <li key={post.slug}>
45
+ <a href={withLocalePrefix(`/blog/${post.slug}`, locale)}>{post.title}</a>
46
+ <p>
47
+ <span className="muted">
48
+ {post.date} · <T id="post.readingTime" values={{ minutes: post.readingMinutes }} />
49
+ </span>
50
+ {' — '}
51
+ {post.excerpt}
52
+ </p>
53
+ </li>
54
+ ))}
55
+ </ul>
56
+ </main>
57
+ )
58
+ }