@voltro/cli 0.52.0 → 0.54.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 (244) hide show
  1. package/CHANGELOG.md +424 -0
  2. package/THIRD-PARTY-NOTICES.md +8311 -3318
  3. package/dist/{agentsMd-0l980yhL.js → agentsMd-BaLC10Na.js} +110 -82
  4. package/dist/agentsMd-DCY1RSs8.js +2 -0
  5. package/dist/apiBuild-CeUN55uk.js +2 -0
  6. package/dist/{apiBuild-CSFI8QGq.js → apiBuild-DTWp0S_q.js} +11 -5
  7. package/dist/bin.js +1 -1
  8. package/dist/build-D4ygSbnV.js +843 -0
  9. package/dist/{checkCommand-COmqc2cB.js → checkCommand-Dg1G7Gwd.js} +6 -6
  10. package/dist/{checkCommand-2SbqzukH.js → checkCommand-L7DTlpIF.js} +1 -1
  11. package/dist/{cloudCmd-QUXh-b5w.js → cloudCmd-DzKcSYuy.js} +1 -1
  12. package/dist/{codegen-VF479Cnb.js → codegen-DSLM8Su9.js} +1 -1
  13. package/dist/codegen-DjgxEOnD.js +2 -0
  14. package/dist/codegenCommand-CG_Vx4lc.js +41 -0
  15. package/dist/{codemodRunner-r7J9lIa7.js → codemodRunner-Cd4xkC6u.js} +109 -11
  16. package/dist/{commands-Cc_nV8WI.js → commands-6Kzi92Np.js} +96 -73
  17. package/dist/{dashboardCommand-C-gKvwqh.js → dashboardCommand-Cq1PWvI1.js} +5 -5
  18. package/dist/{dataCommand-BgpBHnlB.js → dataCommand-DYzW8vkv.js} +3 -3
  19. package/dist/{dbCommand-sHedr-NJ.js → dbCommand-B4NWZtGL.js} +278 -237
  20. package/dist/dbCommand-CSFWs9ev.js +2 -0
  21. package/dist/{dev-CRHoCEiy.js → dev-CmuvUKRq.js} +2903 -2306
  22. package/dist/{dev--A3nsxA3.js → dev-cKUiZZsB.js} +1 -1
  23. package/dist/{doctorCommand-DtfJ3FA6.js → doctorCommand-DCiFVMtZ.js} +101 -69
  24. package/dist/doctorCommand-J3qu4E0Y.js +2 -0
  25. package/dist/{dormancyCommand-Drn7o0No.js → dormancyCommand-w1TrmgYP.js} +1 -1
  26. package/dist/{embeddingsCommand-Z-jO1fWN.js → embeddingsCommand-CMgPyRTr.js} +1 -1
  27. package/dist/{envCommand-D4gCrrTZ.js → envCommand-Cyynmcfa.js} +8 -8
  28. package/dist/{evolveCommand-CMROeKeA.js → evolveCommand-BwvQ8dVH.js} +2 -2
  29. package/dist/fileConventions-l-RIXbx8.js +36 -0
  30. package/dist/{fileTaxonomy-DvDUV9wq.js → fileTaxonomy-CbyMQYx_.js} +42 -42
  31. package/dist/fontPipeline-LxIHa1vo.js +2 -0
  32. package/dist/fontPipeline-Tsh8kZfA.js +152 -0
  33. package/dist/{frameworkTableAssembly-w-XnLa3q.js → frameworkTableAssembly-D7LJuALW.js} +5 -5
  34. package/dist/frameworkTableAssembly-IPD1pUnZ.js +2 -0
  35. package/dist/imagePipeline-B_GVJgm6.js +2 -0
  36. package/dist/imagePipeline-CBZmjT4i.js +127 -0
  37. package/dist/index.js +2 -2
  38. package/dist/{infoCommand-DXM868o_.js → infoCommand-DlYlUPqs.js} +1 -1
  39. package/dist/{inspect-CtL_xTbu.js → inspect-Bd8-9wsi.js} +1 -1
  40. package/dist/inspect-CuoDInfZ.js +2 -0
  41. package/dist/{inspectGateHint-BjnFubmH.js → inspectGateHint-4LxkNtrz.js} +1 -1
  42. package/dist/manifestBuild-C4-J1-m_.js +2 -0
  43. package/dist/{manifestBuild-CuU1VrSm.js → manifestBuild-Cqgsx2bM.js} +1 -1
  44. package/dist/{metaCommands-C6RFmF1r.js → metaCommands-x7RCi2AF.js} +9 -3
  45. package/dist/{migrate-D0F-eTlK.js → migrate-BK_Bbx-_.js} +2 -2
  46. package/dist/mobileCommand-D9O6iq3D.js +428 -0
  47. package/dist/mobileCommand-DAum7tsG.js +2 -0
  48. package/dist/{pageConvention-CzUiSbtU.js → pageConvention-CMpfDN6r.js} +1 -1
  49. package/dist/{privacyCommand-DGdopOI6.js → privacyCommand-BCa2OoZG.js} +1 -1
  50. package/dist/{probeCommand-C9gazU0H.js → probeCommand-_C0YU207.js} +83 -24
  51. package/dist/{projectScaffold-EzlErR4E.js → projectScaffold-CJfP-xbT.js} +1 -1
  52. package/dist/{projectScaffold-B4dmTlwT.js → projectScaffold-CSN0OzBV.js} +2 -2
  53. package/dist/renderModeScan-43yQ2opo.js +147 -0
  54. package/dist/{renderProfile-Ck32Fzxr.js → renderProfile-DvrhVJHa.js} +2 -2
  55. package/dist/{runtimeTrace-BPQyCmC5.js → runtimeTrace-CGWx1Q6l.js} +1 -1
  56. package/dist/{sdkgen-Se88ifTd.js → sdkgen-CDGHQUFj.js} +1 -1
  57. package/dist/serveCommand-BiPe8BJm.js +2 -0
  58. package/dist/{serveCommand-DkP3OT0W.js → serveCommand-Bje09q1v.js} +889 -825
  59. package/dist/serveEntry.js +1 -1
  60. package/dist/start-B0bnJgxI.js +3 -0
  61. package/dist/{start-jw89Xbqy.js → start-Clz-1BHB.js} +633 -455
  62. package/dist/startEntry.js +1 -1
  63. package/dist/{staticCommand-BwNEDlSU.js → staticCommand-ey0kYmOT.js} +1 -1
  64. package/dist/{subcommandNames-DpYs3DXr.js → subcommandNames-CDzfEtKV.js} +3 -3
  65. package/dist/{templates-BR-fb4SP.js → templates-BTWZkJJT.js} +41 -9
  66. package/dist/{test-f3amja6a.js → test-D_kW4KMj.js} +1 -1
  67. package/dist/updateCommand-CIoVDKnj.js +2 -0
  68. package/dist/{updateCommand-BMk2e4ky.js → updateCommand-CRJlAOaM.js} +139 -115
  69. package/dist/{webDev-BgWL9gKV.js → webDev-DSI9SOhs.js} +1598 -1028
  70. package/dist/webDev-DlvZO30c.js +2 -0
  71. package/dist/{webhooksCommand-CoIO3jbj.js → webhooksCommand-BvzXNHji.js} +1 -1
  72. package/dist/workspaceDeps-RKEkX92S.js +45 -0
  73. package/package.json +60 -19
  74. package/templates/AGENTS.core.md +2 -0
  75. package/templates/AGENTS.md +8 -4
  76. package/templates/agent-docs/_index.md +6 -4
  77. package/templates/agent-docs/_manifest.json +21 -5
  78. package/templates/agent-docs/ai.md +6 -6
  79. package/templates/agent-docs/authentication.md +73 -1
  80. package/templates/agent-docs/cli.md +6 -3
  81. package/templates/agent-docs/configuration.md +17 -0
  82. package/templates/agent-docs/data.md +522 -29
  83. package/templates/agent-docs/database/advancedqueries.md +8 -8
  84. package/templates/agent-docs/database/columntypes.md +2 -2
  85. package/templates/agent-docs/database/migrations.md +1 -1
  86. package/templates/agent-docs/database/querying.md +1 -1
  87. package/templates/agent-docs/database/schema.md +1 -1
  88. package/templates/agent-docs/database/seedsdialects.md +65 -3
  89. package/templates/agent-docs/database/transactions.md +3 -3
  90. package/templates/agent-docs/deployment.md +8 -0
  91. package/templates/agent-docs/internationalization.md +4 -2
  92. package/templates/agent-docs/introduction.md +32 -1
  93. package/templates/agent-docs/local-first-mobile.md +226 -30
  94. package/templates/agent-docs/observability.md +5 -1
  95. package/templates/agent-docs/plugins/atlassian.md +2 -2
  96. package/templates/agent-docs/plugins/audit.md +2 -2
  97. package/templates/agent-docs/plugins/auth.md +1 -1
  98. package/templates/agent-docs/plugins/billing.md +1 -1
  99. package/templates/agent-docs/plugins/cdc-out.md +8 -3
  100. package/templates/agent-docs/plugins/comments.md +164 -0
  101. package/templates/agent-docs/plugins/notifications.md +47 -4
  102. package/templates/agent-docs/plugins/presence.md +45 -3
  103. package/templates/agent-docs/plugins/prometheus.md +3 -1
  104. package/templates/agent-docs/plugins/queue.md +172 -0
  105. package/templates/agent-docs/plugins.md +17 -13
  106. package/templates/agent-docs/reference.md +35 -4
  107. package/templates/agent-docs/routing.md +585 -7
  108. package/templates/agent-docs/scheduling.md +1 -1
  109. package/templates/agent-docs/schema-driven-ui.md +226 -4
  110. package/templates/agent-docs/security.md +3 -3
  111. package/templates/agent-docs/templates/appshells.md +36 -4
  112. package/templates/agent-docs/whats-new.md +134 -66
  113. package/templates/apps/api-ai/package.json +6 -6
  114. package/templates/apps/api-auth/package.json +8 -8
  115. package/templates/apps/api-backend/package.json +7 -7
  116. package/templates/apps/api-backend-deactivation/package.json +7 -7
  117. package/templates/apps/api-backend-mail/package.json +8 -8
  118. package/templates/apps/api-backend-mariadb/package.json +9 -9
  119. package/templates/apps/api-backend-sqlite/package.json +8 -8
  120. package/templates/apps/api-backend-storage/package.json +8 -8
  121. package/templates/apps/api-cms/package.json +9 -9
  122. package/templates/apps/api-collab/README.md +3 -3
  123. package/templates/apps/api-collab/app.config.ts +1 -1
  124. package/templates/apps/api-collab/database/schema.ts +12 -8
  125. package/templates/apps/api-collab/mutations/documents.create.mutation.server.ts +1 -1
  126. package/templates/apps/api-collab/mutations/documents.setBody.mutation.server.ts +1 -1
  127. package/templates/apps/api-collab/mutations/documents.setBody.mutation.ts +1 -1
  128. package/templates/apps/api-collab/package.json +8 -8
  129. package/templates/apps/api-collab/template.json +1 -1
  130. package/templates/apps/api-collab/tests/documents.setBody.test.ts +10 -2
  131. package/templates/apps/api-data-advanced/package.json +8 -8
  132. package/templates/apps/api-durable/package.json +8 -8
  133. package/templates/apps/api-feature-flags/package.json +9 -9
  134. package/templates/apps/api-governance/package.json +8 -8
  135. package/templates/apps/api-kv/package.json +8 -8
  136. package/templates/apps/api-moderation/package.json +8 -8
  137. package/templates/apps/api-observability/package.json +8 -8
  138. package/templates/apps/api-ratelimit/package.json +8 -8
  139. package/templates/apps/api-rbac/package.json +8 -8
  140. package/templates/apps/api-rest/package.json +7 -7
  141. package/templates/apps/api-row-history/package.json +8 -8
  142. package/templates/apps/api-saas/package.json +11 -10
  143. package/templates/apps/api-saas-starter/package.json +10 -10
  144. package/templates/apps/api-search/package.json +8 -8
  145. package/templates/apps/api-status/package.json +8 -8
  146. package/templates/apps/api-webhooks/package.json +9 -9
  147. package/templates/apps/changelog/app.config.ts +26 -2
  148. package/templates/apps/changelog/content/releases/{0.1.0.mdx → v0-1-0.mdx} +0 -1
  149. package/templates/apps/changelog/content/releases/{0.2.0.mdx → v0-2-0.mdx} +0 -1
  150. package/templates/apps/changelog/package.json +9 -8
  151. package/templates/apps/changelog/src/collections/releases.collection.ts +48 -0
  152. package/templates/apps/changelog/src/globals.d.ts +1 -1
  153. package/templates/apps/changelog/src/locales/de.ts +1 -1
  154. package/templates/apps/changelog/src/locales/en.ts +1 -1
  155. package/templates/apps/changelog/src/pages/[locale]/[slug]/page.tsx +7 -4
  156. package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +13 -41
  157. package/templates/apps/changelog/src/pages/[slug]/page.test.tsx +26 -51
  158. package/templates/apps/changelog/src/pages/[slug]/page.tsx +21 -19
  159. package/templates/apps/changelog/src/pages/page.test.tsx +14 -37
  160. package/templates/apps/changelog/src/pages/page.tsx +18 -12
  161. package/templates/apps/edge-functions/package.json +2 -2
  162. package/templates/apps/frontend-admin/package.json +8 -8
  163. package/templates/apps/frontend-app/package.json +9 -9
  164. package/templates/apps/frontend-auth/package.json +8 -8
  165. package/templates/apps/frontend-blank/package.json +7 -7
  166. package/templates/apps/frontend-cms/package.json +9 -9
  167. package/templates/apps/frontend-collab/README.md +43 -24
  168. package/templates/apps/frontend-collab/app.config.ts +3 -3
  169. package/templates/apps/frontend-collab/package.json +14 -10
  170. package/templates/apps/frontend-collab/src/locales/de.ts +1 -2
  171. package/templates/apps/frontend-collab/src/locales/en.ts +1 -2
  172. package/templates/apps/frontend-collab/src/pages/page.test.tsx +72 -76
  173. package/templates/apps/frontend-collab/src/pages/page.tsx +45 -22
  174. package/templates/apps/frontend-collab/template.json +2 -2
  175. package/templates/apps/frontend-contact/package.json +7 -7
  176. package/templates/apps/frontend-dashboard/package.json +7 -7
  177. package/templates/apps/frontend-docs/content/docs/de/guides/first-page.md +4 -0
  178. package/templates/apps/frontend-docs/content/docs/de/intro/getting-started.md +4 -0
  179. package/templates/apps/frontend-docs/content/docs/en/guides/first-page.md +4 -0
  180. package/templates/apps/frontend-docs/content/docs/en/intro/getting-started.md +4 -0
  181. package/templates/apps/frontend-docs/package.json +9 -6
  182. package/templates/apps/frontend-docs/src/collections/docs.collection.ts +21 -0
  183. package/templates/apps/frontend-docs/src/locales/de.ts +0 -5
  184. package/templates/apps/frontend-docs/src/locales/en.ts +0 -5
  185. package/templates/apps/frontend-docs/src/pages/[locale]/docs/[...slug]/page.tsx +17 -9
  186. package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +10 -5
  187. package/templates/apps/frontend-docs/src/pages/[locale]/page.tsx +8 -0
  188. package/templates/apps/frontend-docs/src/pages/docs/[...slug]/page.test.tsx +53 -15
  189. package/templates/apps/frontend-docs/src/pages/docs/[...slug]/page.tsx +27 -33
  190. package/templates/apps/frontend-docs/src/pages/page.test.tsx +17 -3
  191. package/templates/apps/frontend-docs/src/pages/page.tsx +16 -12
  192. package/templates/apps/frontend-i18n/package.json +6 -6
  193. package/templates/apps/frontend-landing/README.md +48 -0
  194. package/templates/apps/frontend-landing/app.config.ts +28 -0
  195. package/templates/apps/frontend-landing/package.json +7 -6
  196. package/templates/apps/frontend-landing/src/assets/hero.jpg +0 -0
  197. package/templates/apps/frontend-landing/src/fonts/Geist-Variable.woff2 +0 -0
  198. package/templates/apps/frontend-landing/src/fonts/LICENSE-Geist.txt +92 -0
  199. package/templates/apps/frontend-landing/src/globals.css +15 -0
  200. package/templates/apps/frontend-landing/src/globals.d.ts +17 -0
  201. package/templates/apps/frontend-landing/src/locales/de.ts +3 -2
  202. package/templates/apps/frontend-landing/src/locales/en.ts +5 -2
  203. package/templates/apps/frontend-landing/src/pages/page.test.tsx +79 -0
  204. package/templates/apps/frontend-landing/src/pages/page.tsx +26 -3
  205. package/templates/apps/frontend-landing/template.json +2 -2
  206. package/templates/apps/frontend-portal/package.json +8 -8
  207. package/templates/apps/frontend-saas/package.json +8 -8
  208. package/templates/apps/frontend-spa/package.json +7 -7
  209. package/templates/apps/frontend-ssr/package.json +7 -7
  210. package/templates/apps/frontend-ssr-api/package.json +8 -8
  211. package/templates/apps/frontend-static-blog/content/posts/cms-to-ssg.md +12 -0
  212. package/templates/apps/frontend-static-blog/content/posts/hello-static.md +12 -0
  213. package/templates/apps/frontend-static-blog/content/posts/islands-not-hydration.md +14 -0
  214. package/templates/apps/frontend-static-blog/package.json +9 -6
  215. package/templates/apps/frontend-static-blog/src/collections/posts.collection.ts +27 -0
  216. package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/[slug]/page.tsx +7 -4
  217. package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +7 -1
  218. package/templates/apps/frontend-static-blog/src/pages/blog/[slug]/page.test.tsx +19 -9
  219. package/templates/apps/frontend-static-blog/src/pages/blog/[slug]/page.tsx +19 -21
  220. package/templates/apps/frontend-static-blog/src/pages/page.test.tsx +18 -5
  221. package/templates/apps/frontend-static-blog/src/pages/page.tsx +21 -14
  222. package/templates/apps/frontend-status/package.json +8 -8
  223. package/templates/apps/mobile-app/package.json +12 -11
  224. package/templates/apps/mobile-app/src/lib/deeplinks.ts +29 -17
  225. package/templates/apps/mobile-app/tests/deeplinks.test.ts +16 -0
  226. package/dist/agentsMd-SDDSkyl4.js +0 -2
  227. package/dist/apiBuild-BYBpL7Pz.js +0 -2
  228. package/dist/build-CPgcMQug.js +0 -793
  229. package/dist/codegen-CctkDO-1.js +0 -2
  230. package/dist/codegenCommand-DCdG2JN-.js +0 -137
  231. package/dist/dbCommand-DNb6yeOG.js +0 -2
  232. package/dist/doctorCommand-CqoWA2p5.js +0 -2
  233. package/dist/fileConventions-DOqD3lPS.js +0 -34
  234. package/dist/frameworkTableAssembly-C6ETawPR.js +0 -2
  235. package/dist/inspect-CuGDYES0.js +0 -2
  236. package/dist/manifestBuild-CPjhvM62.js +0 -2
  237. package/dist/renderModeScan-CcH2X1_D.js +0 -120
  238. package/dist/serveCommand-DLc-BznW.js +0 -2
  239. package/dist/start-DfL3fOiN.js +0 -3
  240. package/dist/updateCommand-5gFVfK5q.js +0 -2
  241. package/dist/webDev-CZbTsDcH.js +0 -2
  242. package/templates/apps/changelog/scripts/generate-rss.mjs +0 -38
  243. package/templates/apps/changelog/src/lib/releases.ts +0 -21
  244. package/templates/apps/frontend-static-blog/src/content/posts.ts +0 -64
@@ -12,10 +12,16 @@
12
12
  import { afterEach, describe, expect, test, vi } from 'vitest'
13
13
  import { act, createElement, type ReactNode } from 'react'
14
14
  import { createRoot, type Root } from 'react-dom/client'
15
- import { posts } from '../content/posts'
15
+ import { dirname, join } from 'node:path'
16
+ import { fileURLToPath } from 'node:url'
17
+ import { setContentRoot } from '@voltro/content'
16
18
  import en from '../locales/en'
17
19
  import de from '../locales/de'
18
20
 
21
+ // The list now comes from the posts CONTENT COLLECTION — point the server
22
+ // half at this app's root and load the REAL content/posts/*.md files.
23
+ setContentRoot(join(dirname(fileURLToPath(import.meta.url)), '..', '..'))
24
+
19
25
  const catalogs: Record<string, Record<string, string>> = { en, de }
20
26
  let currentLocale = 'en'
21
27
 
@@ -23,6 +29,10 @@ let currentLocale = 'en'
23
29
  const fill = (msg: string, values?: Record<string, unknown>): string =>
24
30
  values ? msg.replace(/\{(\w+)\}/g, (_, k: string) => String(values[k] ?? `{${k}}`)) : msg
25
31
 
32
+ vi.mock('@voltro/web', () => ({
33
+ useLoaderData: () => entries,
34
+ }))
35
+
26
36
  vi.mock('@voltro/i18n', () => ({
27
37
  // The locale catalogs call these at module top-level; identity mirrors the
28
38
  // real (compile-time-only) implementations so importing them doesn't throw.
@@ -38,7 +48,9 @@ vi.mock('@voltro/i18n', () => ({
38
48
  fill(catalogs[currentLocale]?.[id] ?? id, values),
39
49
  }))
40
50
 
41
- const { default: Index, meta, renderMode, interactive } = await import('./page')
51
+ const { default: Index, meta, renderMode, interactive, loader } = await import('./page')
52
+ // The real markdown entries, loaded through the real collection pipeline.
53
+ const entries = await loader()
42
54
 
43
55
  ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
44
56
 
@@ -83,11 +95,12 @@ describe('index — localized render', () => {
83
95
  currentLocale = 'en'
84
96
  render(createElement(Index))
85
97
  const links = Array.from(container.querySelectorAll('.post-list a'))
86
- expect(links).toHaveLength(posts.length)
98
+ expect(links).toHaveLength(entries.length)
99
+ expect(entries.length).toBeGreaterThan(0)
87
100
  // Newest-first: the 2025-02-03 post precedes the 2025-01-15 one.
88
101
  expect(links[0]!.getAttribute('href')).toBe('/blog/cms-to-ssg')
89
102
  expect(links[links.length - 1]!.getAttribute('href')).toBe('/blog/hello-static')
90
- expect(container.textContent).toContain(`${posts.length} posts`)
103
+ expect(container.textContent).toContain(`${entries.length} posts`)
91
104
  })
92
105
 
93
106
  test('prefixes post links with /de and renders German chrome (de)', () => {
@@ -95,6 +108,6 @@ describe('index — localized render', () => {
95
108
  render(createElement(Index))
96
109
  const links = Array.from(container.querySelectorAll('.post-list a'))
97
110
  expect(links[0]!.getAttribute('href')).toBe('/de/blog/cms-to-ssg')
98
- expect(container.textContent).toContain(`${posts.length} Beiträge`)
111
+ expect(container.textContent).toContain(`${entries.length} Beiträge`)
99
112
  })
100
113
  })
@@ -1,18 +1,22 @@
1
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
2
+ // src/pages/[locale]/page.tsx. The post LIST is pure content — no
3
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.
4
+ // every framework <script> from the rendered HTML.
5
+ //
6
+ // The list comes from the `posts` CONTENT COLLECTION (content/posts/*.md):
7
+ // the loader runs at build time and reads the filesystem — markdown with
8
+ // schema-checked frontmatter, no markdown dependency in this app.
6
9
  //
7
10
  // `meta` is a FUNCTION of `{ locale }` — that's what localises each variant's
8
11
  // <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.
12
+ // CONTENT and are NOT translated; only the chrome (heading, count line,
13
+ // reading-time label) comes from the catalogs.
11
14
 
12
15
  import type { ReactNode } from 'react'
13
- import type { PageMeta } from '@voltro/web'
16
+ import { useLoaderData, type PageMeta } from '@voltro/web'
17
+ import { getCollection, type ContentEntry } from '@voltro/content'
14
18
  import { T, useLocale } from '@voltro/i18n'
15
- import { posts } from '../content/posts'
19
+ import { posts, type Post } from '../collections/posts.collection'
16
20
  import { getCatalog, withLocalePrefix } from '../lib/locale'
17
21
 
18
22
  export const renderMode = 'static' as const
@@ -26,10 +30,13 @@ export const meta = ({ locale }: { locale: string }): PageMeta => {
26
30
  }
27
31
  }
28
32
 
29
- // Newest first sort a copy so the source array stays stable.
30
- const byDateDesc = [...posts].sort((a, b) => b.date.localeCompare(a.date))
33
+ // Newest first. Runs once at build time (this page is static).
34
+ export const loader = async (): Promise<ReadonlyArray<ContentEntry<Post>>> =>
35
+ [...await getCollection<Post>(posts.name)]
36
+ .sort((a, b) => b.data.date.localeCompare(a.data.date))
31
37
 
32
38
  export default function Index(): ReactNode {
39
+ const entries = useLoaderData<ReadonlyArray<ContentEntry<Post>>>()
33
40
  const locale = useLocale()
34
41
  return (
35
42
  <main>
@@ -37,18 +44,18 @@ export default function Index(): ReactNode {
37
44
  <T id="home.heading" />
38
45
  </h1>
39
46
  <p className="muted">
40
- <T id="home.postCount" values={{ count: posts.length }} />
47
+ <T id="home.postCount" values={{ count: entries.length }} />
41
48
  </p>
42
49
  <ul className="post-list">
43
- {byDateDesc.map((post) => (
50
+ {entries.map((post) => (
44
51
  <li key={post.slug}>
45
- <a href={withLocalePrefix(`/blog/${post.slug}`, locale)}>{post.title}</a>
52
+ <a href={withLocalePrefix(`/blog/${post.slug}`, locale)}>{post.data.title}</a>
46
53
  <p>
47
54
  <span className="muted">
48
- {post.date} · <T id="post.readingTime" values={{ minutes: post.readingMinutes }} />
55
+ {post.data.date} · <T id="post.readingTime" values={{ minutes: post.data.readingMinutes }} />
49
56
  </span>
50
57
  {' — '}
51
- {post.excerpt}
58
+ {post.data.excerpt}
52
59
  </p>
53
60
  </li>
54
61
  ))}
@@ -12,18 +12,18 @@
12
12
  "test": "voltro test"
13
13
  },
14
14
  "dependencies": {
15
- "@{{projectName}}/api": "0.52.0",
16
- "@voltro/cli": "0.52.0",
17
- "@voltro/client": "0.52.0",
18
- "@voltro/env": "0.52.0",
19
- "@voltro/i18n": "0.52.0",
20
- "@voltro/ui-shadcn": "0.52.0",
21
- "@voltro/web": "0.52.0",
15
+ "@{{projectName}}/api": "0.54.0",
16
+ "@voltro/cli": "0.54.0",
17
+ "@voltro/client": "0.54.0",
18
+ "@voltro/env": "0.54.0",
19
+ "@voltro/i18n": "0.54.0",
20
+ "@voltro/ui-shadcn": "0.54.0",
21
+ "@voltro/web": "0.54.0",
22
22
  "react": "^19.0.0",
23
23
  "react-dom": "^19.0.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@voltro/devtools": "0.52.0",
26
+ "@voltro/devtools": "0.54.0",
27
27
  "@types/react": "^19.0.0",
28
28
  "@types/react-dom": "^19.0.0",
29
29
  "jsdom": "^30.0.0",
@@ -4,20 +4,21 @@
4
4
  "private": true,
5
5
  "main": "expo-router/entry",
6
6
  "scripts": {
7
- "start": "voltro codegen . && expo start",
8
- "ios": "voltro codegen . && expo run:ios",
9
- "android": "voltro codegen . && expo run:android",
10
- "web": "voltro codegen . && expo start --web",
11
- "test": "vitest run",
12
- "typecheck": "tsc --noEmit",
13
- "codegen": "voltro codegen ."
7
+ "start": "voltro mobile codegen . && expo start",
8
+ "ios": "voltro mobile codegen . && expo run:ios",
9
+ "android": "voltro mobile codegen . && expo run:android",
10
+ "web": "voltro mobile codegen . && expo start --web",
11
+ "test": "voltro mobile codegen . && vitest run",
12
+ "typecheck": "voltro mobile codegen . && tsc --noEmit",
13
+ "codegen": "voltro mobile codegen .",
14
+ "links": "voltro mobile links ."
14
15
  },
15
16
  "dependencies": {
16
17
  "@react-native-async-storage/async-storage": "1.23.1",
17
18
  "@react-native-community/netinfo": "11.4.1",
18
- "@voltro/client": "0.52.0",
19
- "@voltro/react-native": "0.52.0",
20
- "@{{projectName}}/api": "0.52.0",
19
+ "@voltro/client": "0.54.0",
20
+ "@voltro/react-native": "0.54.0",
21
+ "@{{projectName}}/api": "0.54.0",
21
22
  "expo": "^52.0.0",
22
23
  "expo-constants": "~17.0.0",
23
24
  "expo-linking": "~7.0.0",
@@ -32,7 +33,7 @@
32
33
  "@types/node": "^22.0.0",
33
34
  "@types/react": "^19.0.0",
34
35
  "@vitest/coverage-v8": "^4.1.10",
35
- "@voltro/cli": "0.52.0",
36
+ "@voltro/cli": "0.54.0",
36
37
  "typescript": "^6.0.3",
37
38
  "vitest": "^4.1.10"
38
39
  }
@@ -1,27 +1,39 @@
1
- // The app's deep-link table + a pure resolver.
1
+ // The app's deep-link table GENERATED, not hand-written.
2
2
  //
3
3
  // A deep link maps an inbound URL — a universal link (`https://…/orders/42`), a
4
4
  // custom-scheme URL (`{{appName}}://orders/42`), or a push payload's
5
- // `data.deepLink` — to an in-app navigation. `defineDeepLink` + the pure matcher
6
- // come from `@voltro/react-native`; this module is browser/RN-safe (no Metro, no
7
- // screens), which is exactly why it is unit-testable without a simulator.
5
+ // `data.deepLink` — to an in-app navigation. Notice what is NOT in this file: a
6
+ // list of links. `voltro mobile codegen` derives one link per Expo Router screen
7
+ // under `src/app/`, because a screen's route path and a deep-link pattern are
8
+ // the same path in two syntaxes (`app/orders/[id].tsx` ↔ `/orders/:id`). Add a
9
+ // screen and its link exists.
8
10
  //
9
- // Declaration order wins in `dispatchDeepLink`, so list more-specific patterns
10
- // FIRST. The `navigate` callback is injected (the screen layer passes
11
- // expo-router's `router.push`), keeping this table pure + testable.
12
- import {
13
- defineDeepLink,
14
- dispatchDeepLink,
15
- type DeepLinkDescriptor,
16
- } from '@voltro/react-native'
11
+ // **When you need a link file.** Write a `*.deepLink.ts` (default-exporting
12
+ // `defineDeepLink`) when a link needs something the derived one cannot do —
13
+ // param coercion, an auth check, or a side effect before navigation:
14
+ //
15
+ // // src/lib/invoice.deepLink.ts — NOT under src/app/: Expo Router routes
16
+ // // every .ts/.tsx there, so a link file placed inside it ships a screen.
17
+ // import { router } from 'expo-router'
18
+ // import { defineDeepLink } from '@voltro/react-native'
19
+ //
20
+ // export default defineDeepLink({
21
+ // pattern: '/invoices/:number',
22
+ // handler: ({ number }) => router.push(`/invoices/${number.padStart(6, '0')}`),
23
+ // })
24
+ //
25
+ // Declared links are matched BEFORE derived ones, so one for `/orders/:id`
26
+ // replaces the screen-derived link for that pattern rather than racing it.
27
+ //
28
+ // This module stays a thin adapter on purpose: it is browser/RN-safe (no Metro,
29
+ // no screens), which is what makes the routing unit-testable without a simulator.
30
+ import { dispatchDeepLink, type DeepLinkDescriptor } from '@voltro/react-native'
31
+ import { deepLinks } from '../../.framework/deepLinks.generated'
17
32
 
18
- /** Build the app's deep links against a navigate function (expo-router's `push`). */
33
+ /** The app's deep links, bound to a navigate function (expo-router's `push`). */
19
34
  export const makeDeepLinks = (
20
35
  navigate: (href: string) => void,
21
- ): ReadonlyArray<DeepLinkDescriptor> => [
22
- defineDeepLink({ pattern: '/orders/:id', handler: ({ id }) => navigate(`/orders/${id}`) }),
23
- defineDeepLink({ pattern: '/settings', handler: () => navigate('/settings') }),
24
- ]
36
+ ): ReadonlyArray<DeepLinkDescriptor> => deepLinks(navigate)
25
37
 
26
38
  /**
27
39
  * Route an inbound URL through the link table. Returns `true` when a link
@@ -1,11 +1,27 @@
1
+ // Deep-link routing, against the GENERATED table.
2
+ //
3
+ // Nothing here names a hand-written link, because there are none: every
4
+ // assertion below is about a link `voltro mobile codegen` derived from a screen
5
+ // under `src/app/`. That is the property worth pinning — add
6
+ // `src/app/invoices/[id].tsx` and `/invoices/42` starts working with no other
7
+ // edit. These tests therefore need the table generated first, which is why the
8
+ // `test` script runs codegen (the same way `start` / `ios` do).
1
9
  import { describe, expect, it, vi } from 'vitest'
2
10
  import { handleDeepLink, makeDeepLinks } from '../src/lib/deeplinks'
11
+ import { DERIVED_DEEP_LINK_PATTERNS } from '../.framework/deepLinks.generated'
3
12
 
4
13
  describe('deep links', () => {
14
+ it('derives one link per screen — nobody wrote this table', () => {
15
+ // src/app/index.tsx, settings.tsx, orders/[id].tsx. `_layout.tsx` is a
16
+ // layout, not a screen, and must not be in here.
17
+ expect([...DERIVED_DEEP_LINK_PATTERNS].sort()).toEqual(['/', '/orders/:id', '/settings'])
18
+ })
19
+
5
20
  it('routes an order URL to the order screen with the captured id', () => {
6
21
  const navigate = vi.fn()
7
22
  const links = makeDeepLinks(navigate)
8
23
  expect(handleDeepLink(links, '/orders/42')).toBe(true)
24
+ // A derived link navigates to the screen it came from, params put back.
9
25
  expect(navigate).toHaveBeenCalledWith('/orders/42')
10
26
  })
11
27
 
@@ -1,2 +0,0 @@
1
- import { a as e } from "./agentsMd-0l980yhL.js";
2
- export { e as runAgentsMd };
@@ -1,2 +0,0 @@
1
- import { c as e, l as t } from "./apiBuild-CSFI8QGq.js";
2
- export { e as runApiBuild, t as runServeBundleBuild };