@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.
- package/CHANGELOG.md +424 -0
- package/THIRD-PARTY-NOTICES.md +8311 -3318
- package/dist/{agentsMd-0l980yhL.js → agentsMd-BaLC10Na.js} +110 -82
- package/dist/agentsMd-DCY1RSs8.js +2 -0
- package/dist/apiBuild-CeUN55uk.js +2 -0
- package/dist/{apiBuild-CSFI8QGq.js → apiBuild-DTWp0S_q.js} +11 -5
- package/dist/bin.js +1 -1
- package/dist/build-D4ygSbnV.js +843 -0
- package/dist/{checkCommand-COmqc2cB.js → checkCommand-Dg1G7Gwd.js} +6 -6
- package/dist/{checkCommand-2SbqzukH.js → checkCommand-L7DTlpIF.js} +1 -1
- package/dist/{cloudCmd-QUXh-b5w.js → cloudCmd-DzKcSYuy.js} +1 -1
- package/dist/{codegen-VF479Cnb.js → codegen-DSLM8Su9.js} +1 -1
- package/dist/codegen-DjgxEOnD.js +2 -0
- package/dist/codegenCommand-CG_Vx4lc.js +41 -0
- package/dist/{codemodRunner-r7J9lIa7.js → codemodRunner-Cd4xkC6u.js} +109 -11
- package/dist/{commands-Cc_nV8WI.js → commands-6Kzi92Np.js} +96 -73
- package/dist/{dashboardCommand-C-gKvwqh.js → dashboardCommand-Cq1PWvI1.js} +5 -5
- package/dist/{dataCommand-BgpBHnlB.js → dataCommand-DYzW8vkv.js} +3 -3
- package/dist/{dbCommand-sHedr-NJ.js → dbCommand-B4NWZtGL.js} +278 -237
- package/dist/dbCommand-CSFWs9ev.js +2 -0
- package/dist/{dev-CRHoCEiy.js → dev-CmuvUKRq.js} +2903 -2306
- package/dist/{dev--A3nsxA3.js → dev-cKUiZZsB.js} +1 -1
- package/dist/{doctorCommand-DtfJ3FA6.js → doctorCommand-DCiFVMtZ.js} +101 -69
- package/dist/doctorCommand-J3qu4E0Y.js +2 -0
- package/dist/{dormancyCommand-Drn7o0No.js → dormancyCommand-w1TrmgYP.js} +1 -1
- package/dist/{embeddingsCommand-Z-jO1fWN.js → embeddingsCommand-CMgPyRTr.js} +1 -1
- package/dist/{envCommand-D4gCrrTZ.js → envCommand-Cyynmcfa.js} +8 -8
- package/dist/{evolveCommand-CMROeKeA.js → evolveCommand-BwvQ8dVH.js} +2 -2
- package/dist/fileConventions-l-RIXbx8.js +36 -0
- package/dist/{fileTaxonomy-DvDUV9wq.js → fileTaxonomy-CbyMQYx_.js} +42 -42
- package/dist/fontPipeline-LxIHa1vo.js +2 -0
- package/dist/fontPipeline-Tsh8kZfA.js +152 -0
- package/dist/{frameworkTableAssembly-w-XnLa3q.js → frameworkTableAssembly-D7LJuALW.js} +5 -5
- package/dist/frameworkTableAssembly-IPD1pUnZ.js +2 -0
- package/dist/imagePipeline-B_GVJgm6.js +2 -0
- package/dist/imagePipeline-CBZmjT4i.js +127 -0
- package/dist/index.js +2 -2
- package/dist/{infoCommand-DXM868o_.js → infoCommand-DlYlUPqs.js} +1 -1
- package/dist/{inspect-CtL_xTbu.js → inspect-Bd8-9wsi.js} +1 -1
- package/dist/inspect-CuoDInfZ.js +2 -0
- package/dist/{inspectGateHint-BjnFubmH.js → inspectGateHint-4LxkNtrz.js} +1 -1
- package/dist/manifestBuild-C4-J1-m_.js +2 -0
- package/dist/{manifestBuild-CuU1VrSm.js → manifestBuild-Cqgsx2bM.js} +1 -1
- package/dist/{metaCommands-C6RFmF1r.js → metaCommands-x7RCi2AF.js} +9 -3
- package/dist/{migrate-D0F-eTlK.js → migrate-BK_Bbx-_.js} +2 -2
- package/dist/mobileCommand-D9O6iq3D.js +428 -0
- package/dist/mobileCommand-DAum7tsG.js +2 -0
- package/dist/{pageConvention-CzUiSbtU.js → pageConvention-CMpfDN6r.js} +1 -1
- package/dist/{privacyCommand-DGdopOI6.js → privacyCommand-BCa2OoZG.js} +1 -1
- package/dist/{probeCommand-C9gazU0H.js → probeCommand-_C0YU207.js} +83 -24
- package/dist/{projectScaffold-EzlErR4E.js → projectScaffold-CJfP-xbT.js} +1 -1
- package/dist/{projectScaffold-B4dmTlwT.js → projectScaffold-CSN0OzBV.js} +2 -2
- package/dist/renderModeScan-43yQ2opo.js +147 -0
- package/dist/{renderProfile-Ck32Fzxr.js → renderProfile-DvrhVJHa.js} +2 -2
- package/dist/{runtimeTrace-BPQyCmC5.js → runtimeTrace-CGWx1Q6l.js} +1 -1
- package/dist/{sdkgen-Se88ifTd.js → sdkgen-CDGHQUFj.js} +1 -1
- package/dist/serveCommand-BiPe8BJm.js +2 -0
- package/dist/{serveCommand-DkP3OT0W.js → serveCommand-Bje09q1v.js} +889 -825
- package/dist/serveEntry.js +1 -1
- package/dist/start-B0bnJgxI.js +3 -0
- package/dist/{start-jw89Xbqy.js → start-Clz-1BHB.js} +633 -455
- package/dist/startEntry.js +1 -1
- package/dist/{staticCommand-BwNEDlSU.js → staticCommand-ey0kYmOT.js} +1 -1
- package/dist/{subcommandNames-DpYs3DXr.js → subcommandNames-CDzfEtKV.js} +3 -3
- package/dist/{templates-BR-fb4SP.js → templates-BTWZkJJT.js} +41 -9
- package/dist/{test-f3amja6a.js → test-D_kW4KMj.js} +1 -1
- package/dist/updateCommand-CIoVDKnj.js +2 -0
- package/dist/{updateCommand-BMk2e4ky.js → updateCommand-CRJlAOaM.js} +139 -115
- package/dist/{webDev-BgWL9gKV.js → webDev-DSI9SOhs.js} +1598 -1028
- package/dist/webDev-DlvZO30c.js +2 -0
- package/dist/{webhooksCommand-CoIO3jbj.js → webhooksCommand-BvzXNHji.js} +1 -1
- package/dist/workspaceDeps-RKEkX92S.js +45 -0
- package/package.json +60 -19
- package/templates/AGENTS.core.md +2 -0
- package/templates/AGENTS.md +8 -4
- package/templates/agent-docs/_index.md +6 -4
- package/templates/agent-docs/_manifest.json +21 -5
- package/templates/agent-docs/ai.md +6 -6
- package/templates/agent-docs/authentication.md +73 -1
- package/templates/agent-docs/cli.md +6 -3
- package/templates/agent-docs/configuration.md +17 -0
- package/templates/agent-docs/data.md +522 -29
- package/templates/agent-docs/database/advancedqueries.md +8 -8
- package/templates/agent-docs/database/columntypes.md +2 -2
- package/templates/agent-docs/database/migrations.md +1 -1
- package/templates/agent-docs/database/querying.md +1 -1
- package/templates/agent-docs/database/schema.md +1 -1
- package/templates/agent-docs/database/seedsdialects.md +65 -3
- package/templates/agent-docs/database/transactions.md +3 -3
- package/templates/agent-docs/deployment.md +8 -0
- package/templates/agent-docs/internationalization.md +4 -2
- package/templates/agent-docs/introduction.md +32 -1
- package/templates/agent-docs/local-first-mobile.md +226 -30
- package/templates/agent-docs/observability.md +5 -1
- package/templates/agent-docs/plugins/atlassian.md +2 -2
- package/templates/agent-docs/plugins/audit.md +2 -2
- package/templates/agent-docs/plugins/auth.md +1 -1
- package/templates/agent-docs/plugins/billing.md +1 -1
- package/templates/agent-docs/plugins/cdc-out.md +8 -3
- package/templates/agent-docs/plugins/comments.md +164 -0
- package/templates/agent-docs/plugins/notifications.md +47 -4
- package/templates/agent-docs/plugins/presence.md +45 -3
- package/templates/agent-docs/plugins/prometheus.md +3 -1
- package/templates/agent-docs/plugins/queue.md +172 -0
- package/templates/agent-docs/plugins.md +17 -13
- package/templates/agent-docs/reference.md +35 -4
- package/templates/agent-docs/routing.md +585 -7
- package/templates/agent-docs/scheduling.md +1 -1
- package/templates/agent-docs/schema-driven-ui.md +226 -4
- package/templates/agent-docs/security.md +3 -3
- package/templates/agent-docs/templates/appshells.md +36 -4
- package/templates/agent-docs/whats-new.md +134 -66
- package/templates/apps/api-ai/package.json +6 -6
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-sqlite/package.json +8 -8
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/package.json +9 -9
- package/templates/apps/api-collab/README.md +3 -3
- package/templates/apps/api-collab/app.config.ts +1 -1
- package/templates/apps/api-collab/database/schema.ts +12 -8
- package/templates/apps/api-collab/mutations/documents.create.mutation.server.ts +1 -1
- package/templates/apps/api-collab/mutations/documents.setBody.mutation.server.ts +1 -1
- package/templates/apps/api-collab/mutations/documents.setBody.mutation.ts +1 -1
- package/templates/apps/api-collab/package.json +8 -8
- package/templates/apps/api-collab/template.json +1 -1
- package/templates/apps/api-collab/tests/documents.setBody.test.ts +10 -2
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-row-history/package.json +8 -8
- package/templates/apps/api-saas/package.json +11 -10
- package/templates/apps/api-saas-starter/package.json +10 -10
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/app.config.ts +26 -2
- package/templates/apps/changelog/content/releases/{0.1.0.mdx → v0-1-0.mdx} +0 -1
- package/templates/apps/changelog/content/releases/{0.2.0.mdx → v0-2-0.mdx} +0 -1
- package/templates/apps/changelog/package.json +9 -8
- package/templates/apps/changelog/src/collections/releases.collection.ts +48 -0
- package/templates/apps/changelog/src/globals.d.ts +1 -1
- package/templates/apps/changelog/src/locales/de.ts +1 -1
- package/templates/apps/changelog/src/locales/en.ts +1 -1
- package/templates/apps/changelog/src/pages/[locale]/[slug]/page.tsx +7 -4
- package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +13 -41
- package/templates/apps/changelog/src/pages/[slug]/page.test.tsx +26 -51
- package/templates/apps/changelog/src/pages/[slug]/page.tsx +21 -19
- package/templates/apps/changelog/src/pages/page.test.tsx +14 -37
- package/templates/apps/changelog/src/pages/page.tsx +18 -12
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-app/package.json +9 -9
- package/templates/apps/frontend-auth/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-cms/package.json +9 -9
- package/templates/apps/frontend-collab/README.md +43 -24
- package/templates/apps/frontend-collab/app.config.ts +3 -3
- package/templates/apps/frontend-collab/package.json +14 -10
- package/templates/apps/frontend-collab/src/locales/de.ts +1 -2
- package/templates/apps/frontend-collab/src/locales/en.ts +1 -2
- package/templates/apps/frontend-collab/src/pages/page.test.tsx +72 -76
- package/templates/apps/frontend-collab/src/pages/page.tsx +45 -22
- package/templates/apps/frontend-collab/template.json +2 -2
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-docs/content/docs/de/guides/first-page.md +4 -0
- package/templates/apps/frontend-docs/content/docs/de/intro/getting-started.md +4 -0
- package/templates/apps/frontend-docs/content/docs/en/guides/first-page.md +4 -0
- package/templates/apps/frontend-docs/content/docs/en/intro/getting-started.md +4 -0
- package/templates/apps/frontend-docs/package.json +9 -6
- package/templates/apps/frontend-docs/src/collections/docs.collection.ts +21 -0
- package/templates/apps/frontend-docs/src/locales/de.ts +0 -5
- package/templates/apps/frontend-docs/src/locales/en.ts +0 -5
- package/templates/apps/frontend-docs/src/pages/[locale]/docs/[...slug]/page.tsx +17 -9
- package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +10 -5
- package/templates/apps/frontend-docs/src/pages/[locale]/page.tsx +8 -0
- package/templates/apps/frontend-docs/src/pages/docs/[...slug]/page.test.tsx +53 -15
- package/templates/apps/frontend-docs/src/pages/docs/[...slug]/page.tsx +27 -33
- package/templates/apps/frontend-docs/src/pages/page.test.tsx +17 -3
- package/templates/apps/frontend-docs/src/pages/page.tsx +16 -12
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/README.md +48 -0
- package/templates/apps/frontend-landing/app.config.ts +28 -0
- package/templates/apps/frontend-landing/package.json +7 -6
- package/templates/apps/frontend-landing/src/assets/hero.jpg +0 -0
- package/templates/apps/frontend-landing/src/fonts/Geist-Variable.woff2 +0 -0
- package/templates/apps/frontend-landing/src/fonts/LICENSE-Geist.txt +92 -0
- package/templates/apps/frontend-landing/src/globals.css +15 -0
- package/templates/apps/frontend-landing/src/globals.d.ts +17 -0
- package/templates/apps/frontend-landing/src/locales/de.ts +3 -2
- package/templates/apps/frontend-landing/src/locales/en.ts +5 -2
- package/templates/apps/frontend-landing/src/pages/page.test.tsx +79 -0
- package/templates/apps/frontend-landing/src/pages/page.tsx +26 -3
- package/templates/apps/frontend-landing/template.json +2 -2
- package/templates/apps/frontend-portal/package.json +8 -8
- package/templates/apps/frontend-saas/package.json +8 -8
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/content/posts/cms-to-ssg.md +12 -0
- package/templates/apps/frontend-static-blog/content/posts/hello-static.md +12 -0
- package/templates/apps/frontend-static-blog/content/posts/islands-not-hydration.md +14 -0
- package/templates/apps/frontend-static-blog/package.json +9 -6
- package/templates/apps/frontend-static-blog/src/collections/posts.collection.ts +27 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/[slug]/page.tsx +7 -4
- package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +7 -1
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug]/page.test.tsx +19 -9
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug]/page.tsx +19 -21
- package/templates/apps/frontend-static-blog/src/pages/page.test.tsx +18 -5
- package/templates/apps/frontend-static-blog/src/pages/page.tsx +21 -14
- package/templates/apps/frontend-status/package.json +8 -8
- package/templates/apps/mobile-app/package.json +12 -11
- package/templates/apps/mobile-app/src/lib/deeplinks.ts +29 -17
- package/templates/apps/mobile-app/tests/deeplinks.test.ts +16 -0
- package/dist/agentsMd-SDDSkyl4.js +0 -2
- package/dist/apiBuild-BYBpL7Pz.js +0 -2
- package/dist/build-CPgcMQug.js +0 -793
- package/dist/codegen-CctkDO-1.js +0 -2
- package/dist/codegenCommand-DCdG2JN-.js +0 -137
- package/dist/dbCommand-DNb6yeOG.js +0 -2
- package/dist/doctorCommand-CqoWA2p5.js +0 -2
- package/dist/fileConventions-DOqD3lPS.js +0 -34
- package/dist/frameworkTableAssembly-C6ETawPR.js +0 -2
- package/dist/inspect-CuGDYES0.js +0 -2
- package/dist/manifestBuild-CPjhvM62.js +0 -2
- package/dist/renderModeScan-CcH2X1_D.js +0 -120
- package/dist/serveCommand-DLc-BznW.js +0 -2
- package/dist/start-DfL3fOiN.js +0 -3
- package/dist/updateCommand-5gFVfK5q.js +0 -2
- package/dist/webDev-CZbTsDcH.js +0 -2
- package/templates/apps/changelog/scripts/generate-rss.mjs +0 -38
- package/templates/apps/changelog/src/lib/releases.ts +0 -21
- 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 {
|
|
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(
|
|
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(`${
|
|
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(`${
|
|
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]/
|
|
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.
|
|
5
|
-
//
|
|
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
|
|
10
|
-
//
|
|
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
|
|
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 '../
|
|
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
|
|
30
|
-
const
|
|
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:
|
|
47
|
+
<T id="home.postCount" values={{ count: entries.length }} />
|
|
41
48
|
</p>
|
|
42
49
|
<ul className="post-list">
|
|
43
|
-
{
|
|
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.
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/client": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/i18n": "0.
|
|
20
|
-
"@voltro/ui-shadcn": "0.
|
|
21
|
-
"@voltro/web": "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.
|
|
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.
|
|
19
|
-
"@voltro/react-native": "0.
|
|
20
|
-
"@{{projectName}}/api": "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.
|
|
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
|
|
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.
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
/**
|
|
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
|
|