@voltro/cli 0.54.0 → 0.56.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 +639 -2
- package/bin/voltro.mjs +24 -0
- package/dist/{apiBuild-DTWp0S_q.js → apiBuild-Bdaetr37.js} +119 -88
- package/dist/apiBuild-Vw1figjO.js +2 -0
- package/dist/bin.js +1 -1
- package/dist/{build-D4ygSbnV.js → build-CI36wL4R.js} +339 -314
- package/dist/buildReport-52gHKgfO.js +64 -0
- package/dist/{checkCommand-Dg1G7Gwd.js → checkCommand-CAwFXrxA.js} +6 -6
- package/dist/{checkCommand-L7DTlpIF.js → checkCommand-D0QV_zM_.js} +1 -1
- package/dist/{clusterCmd-DrVFCzSj.js → clusterCmd-CdLB1GkT.js} +1 -1
- package/dist/{codegen-DSLM8Su9.js → codegen-Bth5lUTU.js} +76 -64
- package/dist/codegen-DbH7NbCR.js +2 -0
- package/dist/{codegenCommand-CG_Vx4lc.js → codegenCommand-CidbQzbv.js} +15 -14
- package/dist/{codemodRunner-Cd4xkC6u.js → codemodRunner-BlQPfjzA.js} +277 -0
- package/dist/{commands-6Kzi92Np.js → commands-CWjfThXv.js} +35 -35
- package/dist/{dashboardCommand-Cq1PWvI1.js → dashboardCommand-BekcY5Ls.js} +3 -3
- package/dist/{dataCommand-DYzW8vkv.js → dataCommand-2pccgbIy.js} +267 -195
- package/dist/{dbCommand-B4NWZtGL.js → dbCommand-DpK_vQET.js} +457 -441
- package/dist/dbCommand-DrycGWWt.js +2 -0
- package/dist/{dev-cKUiZZsB.js → dev-B9Gz0k85.js} +1 -1
- package/dist/{dev-CmuvUKRq.js → dev-Dw263KPu.js} +2598 -2452
- package/dist/doctorCommand-BMWs6aVm.js +2 -0
- package/dist/{doctorCommand-DCiFVMtZ.js → doctorCommand-aR_bFmIi.js} +353 -251
- package/dist/{dormancyCommand-w1TrmgYP.js → dormancyCommand-eXTQMbHU.js} +1 -1
- package/dist/{embeddingsCommand-CMgPyRTr.js → embeddingsCommand-CTmiQvwa.js} +1 -1
- package/dist/{envCommand-Cyynmcfa.js → envCommand-BDUgV7EM.js} +12 -12
- package/dist/{evolveCommand-BwvQ8dVH.js → evolveCommand-YV8qW1LU.js} +2 -2
- package/dist/frameworkTableAssembly-CGNC0qr7.js +2 -0
- package/dist/{frameworkTableAssembly-D7LJuALW.js → frameworkTableAssembly-DNOFXfEQ.js} +102 -100
- package/dist/index.js +1 -1
- package/dist/{infoCommand-DlYlUPqs.js → infoCommand-BjVXpMlP.js} +1 -1
- package/dist/inspect-CNYvNXPU.js +1484 -0
- package/dist/inspect-S2rWy1Ys.js +2 -0
- package/dist/{inspectCmd-niF97fAq.js → inspectCmd-CP-G0sVK.js} +1 -1
- package/dist/{inspectFetch-EMuhTG_9.js → inspectFetch-BU1NyzxV.js} +36 -24
- package/dist/{inspectMetrics-CGF94puw.js → inspectMetrics-BY0Sjb2F.js} +19 -19
- package/dist/interruptedReplace-CwnkBb2X.js +41 -0
- package/dist/interruptedReplace-qzmFI020.js +2 -0
- package/dist/{logsCmd-B6oNsfaZ.js → logsCmd-BU8uCdys.js} +1 -1
- package/dist/{manifestBuild-Cqgsx2bM.js → manifestBuild-CEkjfpwc.js} +1 -1
- package/dist/manifestBuild-DIa_s6u0.js +2 -0
- package/dist/{migrate-BK_Bbx-_.js → migrate-SICulyz1.js} +2 -2
- package/dist/precompressAssets-YhTi1aWp.js +40 -0
- package/dist/{probeCommand-_C0YU207.js → probeCommand-6HxEkNDG.js} +2 -2
- package/dist/{runtimeTrace-CGWx1Q6l.js → runtimeTrace-DgYMc09E.js} +1 -1
- package/dist/{scheduleCmd-DQRu6BZC.js → scheduleCmd-DYBUfo_T.js} +1 -1
- package/dist/{sdkgen-CDGHQUFj.js → sdkgen-PY-umd6O.js} +1 -1
- package/dist/{seedRunner-Dgsiwk_e.js → seedRunner-DISBKow-.js} +16 -16
- package/dist/serveCommand-BITS8Hpj.js +2 -0
- package/dist/{serveCommand-Bje09q1v.js → serveCommand-DIJ3ma76.js} +951 -909
- package/dist/serveEntry.js +1 -1
- package/dist/{start-Clz-1BHB.js → start-B9NGB8gn.js} +627 -588
- package/dist/{start-B0bnJgxI.js → start-BFQQkL1i.js} +1 -1
- package/dist/startEntry.js +1 -1
- package/dist/staticCachePolicy-CIyj6DbS.js +15 -0
- package/dist/{test-D_kW4KMj.js → test-jipIQ5Mx.js} +1 -1
- package/dist/{tracesCmd-DgtgOUdi.js → tracesCmd-BWYDqMy6.js} +1 -1
- package/dist/{updateCommand-CRJlAOaM.js → updateCommand-C9n_Z_oG.js} +8 -2
- package/dist/updateCommand-DsXEAHbd.js +2 -0
- package/dist/webDev-C2dRz9s5.js +2 -0
- package/dist/{webDev-DSI9SOhs.js → webDev-C53hJdcL.js} +1265 -1288
- package/dist/{webhooksCommand-BvzXNHji.js → webhooksCommand-uuPu8qQX.js} +1 -1
- package/dist/{workflowsCmd-BGF-mRZ5.js → workflowsCmd-g-DNpaUc.js} +1 -1
- package/package.json +67 -19
- package/templates/AGENTS.core.md +20 -1
- package/templates/AGENTS.md +21 -2
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/_manifest.json +1 -1
- package/templates/agent-docs/authentication.md +49 -6
- package/templates/agent-docs/cli.md +216 -11
- package/templates/agent-docs/data.md +209 -18
- package/templates/agent-docs/database/scaling.md +40 -1
- package/templates/agent-docs/deployment.md +53 -1
- package/templates/agent-docs/internationalization.md +32 -0
- package/templates/agent-docs/local-first-mobile.md +9 -2
- package/templates/agent-docs/observability.md +227 -0
- package/templates/agent-docs/plugins/billing.md +15 -0
- package/templates/agent-docs/plugins/broadcast.md +2 -1
- package/templates/agent-docs/plugins/ratelimit.md +6 -1
- package/templates/agent-docs/plugins/row-history.md +11 -0
- package/templates/agent-docs/plugins.md +65 -8
- package/templates/agent-docs/reference.md +5 -3
- package/templates/agent-docs/routing.md +18 -0
- package/templates/agent-docs/schema-driven-ui.md +125 -0
- package/templates/agent-docs/templates/appshells.md +3 -3
- package/templates/agent-docs/whats-new.md +408 -106
- 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/package.json +8 -8
- 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 -11
- 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/package.json +7 -7
- package/templates/apps/changelog/src/pages/[locale]/page.tsx +7 -1
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +7 -8
- package/templates/apps/frontend-admin/src/pages/(marketing)/layout.tsx +1 -2
- package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +1 -2
- package/templates/apps/frontend-app/package.json +8 -9
- package/templates/apps/frontend-app/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-auth/package.json +7 -8
- package/templates/apps/frontend-auth/src/components/AuthShell.tsx +1 -2
- package/templates/apps/frontend-blank/package.json +6 -7
- package/templates/apps/frontend-blank/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-cms/package.json +8 -9
- package/templates/apps/frontend-cms/src/pages/(app)/layout.tsx +1 -2
- package/templates/apps/frontend-collab/README.md +7 -2
- package/templates/apps/frontend-collab/package.json +9 -10
- package/templates/apps/frontend-collab/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-collab/src/pages/page.test.tsx +8 -4
- package/templates/apps/frontend-collab/src/pages/page.tsx +10 -5
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-contact/src/components/ContactForm.island.tsx +1 -1
- package/templates/apps/frontend-dashboard/package.json +6 -7
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/layout.tsx +1 -2
- package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.tsx +1 -2
- package/templates/apps/frontend-docs/package.json +8 -8
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-portal/package.json +7 -8
- package/templates/apps/frontend-portal/src/pages/(portal)/layout.tsx +1 -2
- package/templates/apps/frontend-saas/package.json +7 -8
- package/templates/apps/frontend-saas/src/pages/(marketing)/layout.tsx +1 -2
- package/templates/apps/frontend-saas/src/pages/dashboard/layout.tsx +1 -2
- package/templates/apps/frontend-spa/package.json +6 -7
- package/templates/apps/frontend-spa/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-ssr/package.json +6 -7
- package/templates/apps/frontend-ssr/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-ssr-api/package.json +7 -8
- package/templates/apps/frontend-ssr-api/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-static-blog/package.json +8 -8
- package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.tsx +1 -1
- package/templates/apps/frontend-static-blog/src/pages/[locale]/page.tsx +7 -1
- package/templates/apps/frontend-status/package.json +7 -8
- package/templates/apps/frontend-status/src/pages/layout.tsx +1 -2
- package/templates/apps/mobile-app/package.json +4 -4
- package/templates/baselines/compose/docker/api.Dockerfile +61 -5
- package/templates/baselines/compose/docker/web.Dockerfile +55 -10
- package/templates/baselines/compose-mariadb/docker/api.Dockerfile +61 -5
- package/templates/baselines/compose-mariadb/docker/web.Dockerfile +55 -10
- package/dist/apiBuild-CeUN55uk.js +0 -2
- package/dist/codegen-DjgxEOnD.js +0 -2
- package/dist/dbCommand-CSFWs9ev.js +0 -2
- package/dist/doctorCommand-J3qu4E0Y.js +0 -2
- package/dist/frameworkTableAssembly-IPD1pUnZ.js +0 -2
- package/dist/inspect-Bd8-9wsi.js +0 -1193
- package/dist/inspect-CuoDInfZ.js +0 -2
- package/dist/interruptedReplace-C3O3M1MM.js +0 -28
- package/dist/interruptedReplace-CvmiAM9K.js +0 -2
- package/dist/manifestBuild-C4-J1-m_.js +0 -2
- package/dist/serveCommand-BiPe8BJm.js +0 -2
- package/dist/updateCommand-CIoVDKnj.js +0 -2
- package/dist/webDev-DlvZO30c.js +0 -2
|
@@ -12,18 +12,17 @@
|
|
|
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/
|
|
21
|
-
"@voltro/web": "0.54.0",
|
|
15
|
+
"@{{projectName}}/api": "0.56.0",
|
|
16
|
+
"@voltro/cli": "0.56.0",
|
|
17
|
+
"@voltro/client": "0.56.0",
|
|
18
|
+
"@voltro/env": "0.56.0",
|
|
19
|
+
"@voltro/i18n": "0.56.0",
|
|
20
|
+
"@voltro/web": "0.56.0",
|
|
22
21
|
"react": "^19.0.0",
|
|
23
22
|
"react-dom": "^19.0.0"
|
|
24
23
|
},
|
|
25
24
|
"devDependencies": {
|
|
26
|
-
"@voltro/devtools": "0.
|
|
25
|
+
"@voltro/devtools": "0.56.0",
|
|
27
26
|
"@types/react": "^19.0.0",
|
|
28
27
|
"@types/react-dom": "^19.0.0",
|
|
29
28
|
"jsdom": "^30.0.0",
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
// this runs per request.
|
|
6
6
|
import type { ReactNode } from 'react'
|
|
7
7
|
import { RedirectError, useLoaderData, useLocation, type LoaderFn } from '@voltro/web'
|
|
8
|
-
import { T, useLocale, useT } from '@voltro/i18n'
|
|
9
|
-
import { LocaleSwitcher } from '@voltro/ui-shadcn'
|
|
8
|
+
import { T, useLocale, useT, LocaleSwitcher } from '@voltro/i18n'
|
|
10
9
|
import { APP_NAME } from '../../config'
|
|
11
10
|
import type { Identity } from '../../lib/api'
|
|
12
11
|
|
|
@@ -12,18 +12,17 @@
|
|
|
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/
|
|
21
|
-
"@voltro/web": "0.54.0",
|
|
15
|
+
"@{{projectName}}/api": "0.56.0",
|
|
16
|
+
"@voltro/cli": "0.56.0",
|
|
17
|
+
"@voltro/client": "0.56.0",
|
|
18
|
+
"@voltro/env": "0.56.0",
|
|
19
|
+
"@voltro/i18n": "0.56.0",
|
|
20
|
+
"@voltro/web": "0.56.0",
|
|
22
21
|
"react": "^19.0.0",
|
|
23
22
|
"react-dom": "^19.0.0"
|
|
24
23
|
},
|
|
25
24
|
"devDependencies": {
|
|
26
|
-
"@voltro/devtools": "0.
|
|
25
|
+
"@voltro/devtools": "0.56.0",
|
|
27
26
|
"@types/react": "^19.0.0",
|
|
28
27
|
"@types/react-dom": "^19.0.0",
|
|
29
28
|
"jsdom": "^30.0.0",
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
// `/login`. It gives the public pages a marketing header distinct from the
|
|
4
4
|
// dashboard's sidebar shell.
|
|
5
5
|
import type { ReactNode } from 'react'
|
|
6
|
-
import { T, useLocale, useT } from '@voltro/i18n'
|
|
7
|
-
import { LocaleSwitcher } from '@voltro/ui-shadcn'
|
|
6
|
+
import { T, useLocale, useT, LocaleSwitcher } from '@voltro/i18n'
|
|
8
7
|
import { APP_NAME } from '../../config'
|
|
9
8
|
|
|
10
9
|
const LOCALES = [
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
// per-visitor, and the build rejects a RedirectError there.
|
|
7
7
|
import type { ReactNode } from 'react'
|
|
8
8
|
import { RedirectError, useLoaderData, useLocation, type LoaderFn } from '@voltro/web'
|
|
9
|
-
import { T, useLocale, useT } from '@voltro/i18n'
|
|
10
|
-
import { LocaleSwitcher } from '@voltro/ui-shadcn'
|
|
9
|
+
import { T, useLocale, useT, LocaleSwitcher } from '@voltro/i18n'
|
|
11
10
|
import { APP_NAME } from '../../config'
|
|
12
11
|
import type { Identity } from '../../lib/api'
|
|
13
12
|
|
|
@@ -12,17 +12,16 @@
|
|
|
12
12
|
"typecheck": "tsc --noEmit"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/client": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/i18n": "0.
|
|
19
|
-
"@voltro/
|
|
20
|
-
"@voltro/web": "0.54.0",
|
|
15
|
+
"@voltro/cli": "0.56.0",
|
|
16
|
+
"@voltro/client": "0.56.0",
|
|
17
|
+
"@voltro/env": "0.56.0",
|
|
18
|
+
"@voltro/i18n": "0.56.0",
|
|
19
|
+
"@voltro/web": "0.56.0",
|
|
21
20
|
"react": "^19.0.0",
|
|
22
21
|
"react-dom": "^19.0.0"
|
|
23
22
|
},
|
|
24
23
|
"devDependencies": {
|
|
25
|
-
"@voltro/devtools": "0.
|
|
24
|
+
"@voltro/devtools": "0.56.0",
|
|
26
25
|
"@types/react": "^19.0.0",
|
|
27
26
|
"@types/react-dom": "^19.0.0",
|
|
28
27
|
"jsdom": "^30.0.0",
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
// chosen language.
|
|
7
7
|
|
|
8
8
|
import type { ReactNode } from 'react'
|
|
9
|
-
import { useLocale, useT } from '@voltro/i18n'
|
|
10
|
-
import { LocaleSwitcher } from '@voltro/ui-shadcn'
|
|
9
|
+
import { useLocale, useT, LocaleSwitcher } from '@voltro/i18n'
|
|
11
10
|
import '../globals.css'
|
|
12
11
|
|
|
13
12
|
const LOCALES = [
|
|
@@ -12,17 +12,16 @@
|
|
|
12
12
|
"test": "voltro test"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/client": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/i18n": "0.
|
|
19
|
-
"@voltro/
|
|
20
|
-
"@voltro/web": "0.54.0",
|
|
15
|
+
"@voltro/cli": "0.56.0",
|
|
16
|
+
"@voltro/client": "0.56.0",
|
|
17
|
+
"@voltro/env": "0.56.0",
|
|
18
|
+
"@voltro/i18n": "0.56.0",
|
|
19
|
+
"@voltro/web": "0.56.0",
|
|
21
20
|
"react": "^19.0.0",
|
|
22
21
|
"react-dom": "^19.0.0"
|
|
23
22
|
},
|
|
24
23
|
"devDependencies": {
|
|
25
|
-
"@voltro/devtools": "0.
|
|
24
|
+
"@voltro/devtools": "0.56.0",
|
|
26
25
|
"@types/react": "^19.0.0",
|
|
27
26
|
"@types/react-dom": "^19.0.0",
|
|
28
27
|
"jsdom": "^30.0.0",
|
|
@@ -7,8 +7,7 @@
|
|
|
7
7
|
// + reloads so the framework re-renders in the chosen language.
|
|
8
8
|
|
|
9
9
|
import type { ReactNode } from 'react'
|
|
10
|
-
import { T, useLocale, useT } from '@voltro/i18n'
|
|
11
|
-
import { LocaleSwitcher } from '@voltro/ui-shadcn'
|
|
10
|
+
import { T, useLocale, useT, LocaleSwitcher } from '@voltro/i18n'
|
|
12
11
|
import '../globals.css'
|
|
13
12
|
|
|
14
13
|
const LOCALES = [
|
|
@@ -12,18 +12,17 @@
|
|
|
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/
|
|
21
|
-
"@voltro/web": "0.54.0",
|
|
15
|
+
"@{{projectName}}/api": "0.56.0",
|
|
16
|
+
"@voltro/cli": "0.56.0",
|
|
17
|
+
"@voltro/client": "0.56.0",
|
|
18
|
+
"@voltro/env": "0.56.0",
|
|
19
|
+
"@voltro/i18n": "0.56.0",
|
|
20
|
+
"@voltro/web": "0.56.0",
|
|
22
21
|
"react": "^19.0.0",
|
|
23
22
|
"react-dom": "^19.0.0"
|
|
24
23
|
},
|
|
25
24
|
"devDependencies": {
|
|
26
|
-
"@voltro/devtools": "0.
|
|
25
|
+
"@voltro/devtools": "0.56.0",
|
|
27
26
|
"@types/react": "^19.0.0",
|
|
28
27
|
"@types/react-dom": "^19.0.0",
|
|
29
28
|
"jsdom": "^30.0.0",
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
// writes the `voltro:locale` cookie + reloads so the framework re-renders in the
|
|
6
6
|
// chosen language.
|
|
7
7
|
import type { ReactNode } from 'react'
|
|
8
|
-
import { useLocale, useT } from '@voltro/i18n'
|
|
9
|
-
import { LocaleSwitcher } from '@voltro/ui-shadcn'
|
|
8
|
+
import { useLocale, useT, LocaleSwitcher } from '@voltro/i18n'
|
|
10
9
|
import '../globals.css'
|
|
11
10
|
|
|
12
11
|
const LOCALES = [
|
|
@@ -12,19 +12,19 @@
|
|
|
12
12
|
"test": "voltro test"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/client": "0.
|
|
17
|
-
"@voltro/content": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/i18n": "0.
|
|
20
|
-
"@voltro/ui-shadcn": "0.
|
|
21
|
-
"@voltro/web": "0.
|
|
15
|
+
"@voltro/cli": "0.56.0",
|
|
16
|
+
"@voltro/client": "0.56.0",
|
|
17
|
+
"@voltro/content": "0.56.0",
|
|
18
|
+
"@voltro/env": "0.56.0",
|
|
19
|
+
"@voltro/i18n": "0.56.0",
|
|
20
|
+
"@voltro/ui-shadcn": "0.56.0",
|
|
21
|
+
"@voltro/web": "0.56.0",
|
|
22
22
|
"effect": "^3.22.0",
|
|
23
23
|
"react": "^19.0.0",
|
|
24
24
|
"react-dom": "^19.0.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@voltro/devtools": "0.
|
|
27
|
+
"@voltro/devtools": "0.56.0",
|
|
28
28
|
"@types/react": "^19.0.0",
|
|
29
29
|
"@types/react-dom": "^19.0.0",
|
|
30
30
|
"jsdom": "^30.0.0",
|
|
@@ -5,7 +5,13 @@
|
|
|
5
5
|
import { SUPPORTED_LOCALES, DEFAULT_LOCALE } from '../../lib/locale'
|
|
6
6
|
|
|
7
7
|
export { default } from '../page'
|
|
8
|
-
|
|
8
|
+
// `loader` belongs in this list: the re-exported component calls
|
|
9
|
+
// `useLoaderData()`, and a mirror that forwards everything EXCEPT the loader
|
|
10
|
+
// renders it at a level that declares none — which is a prerender failure, not
|
|
11
|
+
// a missing-data warning. It was absent here while the `[slug]` mirror next
|
|
12
|
+
// door forwarded it correctly, and no test could see the difference: a mirror
|
|
13
|
+
// test asserts what IS re-exported, so an omission reads as a shorter list.
|
|
14
|
+
export { renderMode, interactive, loader, meta } from '../page'
|
|
9
15
|
|
|
10
16
|
export const getStaticPaths = async (): Promise<Array<{ params: { locale: string } }>> =>
|
|
11
17
|
SUPPORTED_LOCALES
|
|
@@ -12,18 +12,17 @@
|
|
|
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/
|
|
21
|
-
"@voltro/web": "0.54.0",
|
|
15
|
+
"@{{projectName}}/api": "0.56.0",
|
|
16
|
+
"@voltro/cli": "0.56.0",
|
|
17
|
+
"@voltro/client": "0.56.0",
|
|
18
|
+
"@voltro/env": "0.56.0",
|
|
19
|
+
"@voltro/i18n": "0.56.0",
|
|
20
|
+
"@voltro/web": "0.56.0",
|
|
22
21
|
"react": "^19.0.0",
|
|
23
22
|
"react-dom": "^19.0.0"
|
|
24
23
|
},
|
|
25
24
|
"devDependencies": {
|
|
26
|
-
"@voltro/devtools": "0.
|
|
25
|
+
"@voltro/devtools": "0.56.0",
|
|
27
26
|
"@types/react": "^19.0.0",
|
|
28
27
|
"@types/react-dom": "^19.0.0",
|
|
29
28
|
"jsdom": "^30.0.0",
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// Root layout — the status-page header (brand + language) and the stylesheet.
|
|
2
2
|
// Don't render <html>/<head>/<body> here — the framework shell owns them.
|
|
3
3
|
import type { ReactNode } from 'react'
|
|
4
|
-
import { T, useLocale, useT } from '@voltro/i18n'
|
|
5
|
-
import { LocaleSwitcher } from '@voltro/ui-shadcn'
|
|
4
|
+
import { T, useLocale, useT, LocaleSwitcher } from '@voltro/i18n'
|
|
6
5
|
import { APP_NAME } from '../config'
|
|
7
6
|
import '../globals.css'
|
|
8
7
|
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@react-native-async-storage/async-storage": "1.23.1",
|
|
18
18
|
"@react-native-community/netinfo": "11.4.1",
|
|
19
|
-
"@voltro/client": "0.
|
|
20
|
-
"@voltro/react-native": "0.
|
|
21
|
-
"@{{projectName}}/api": "0.
|
|
19
|
+
"@voltro/client": "0.56.0",
|
|
20
|
+
"@voltro/react-native": "0.56.0",
|
|
21
|
+
"@{{projectName}}/api": "0.56.0",
|
|
22
22
|
"expo": "^52.0.0",
|
|
23
23
|
"expo-constants": "~17.0.0",
|
|
24
24
|
"expo-linking": "~7.0.0",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@types/node": "^22.0.0",
|
|
34
34
|
"@types/react": "^19.0.0",
|
|
35
35
|
"@vitest/coverage-v8": "^4.1.10",
|
|
36
|
-
"@voltro/cli": "0.
|
|
36
|
+
"@voltro/cli": "0.56.0",
|
|
37
37
|
"typescript": "^6.0.3",
|
|
38
38
|
"vitest": "^4.1.10"
|
|
39
39
|
}
|
|
@@ -58,15 +58,71 @@ RUN APP_NAME=$(node -p "require('/workspace/${APP_PATH}/package.json').name") \
|
|
|
58
58
|
&& pnpm --filter="${APP_NAME}" --prod --no-optional --legacy deploy /deploy \
|
|
59
59
|
; cp -r /workspace/${APP_PATH}/.framework /deploy/.framework 2>/dev/null || echo "no .framework (serve loads from source)"
|
|
60
60
|
|
|
61
|
+
|
|
62
|
+
# SLIM the runtime tree + BAKE the V8 code cache, and prove the image boots.
|
|
63
|
+
#
|
|
64
|
+
# The entrypoint below is `node <serveBundle>`, so the container never resolves
|
|
65
|
+
# the @voltro/cli bin — `voltro prune-runtime` @vercel/nft-traces the reachable
|
|
66
|
+
# set FROM that bundle and drops the rest (the used native SQL driver is traced
|
|
67
|
+
# and kept automatically; an unused one is dropped). It runs in THIS stage,
|
|
68
|
+
# where @vercel/nft is still installed.
|
|
69
|
+
#
|
|
70
|
+
# The boot smoke is the gate: it starts the PRUNED tree with the EXACT prod
|
|
71
|
+
# entrypoint. `NODE_COMPILE_CACHE` makes that same run write node's compiled
|
|
72
|
+
# bytecode into the image — a scale-from-zero container starts with an empty
|
|
73
|
+
# temp dir, so baking it here is the only way it is warm when it matters.
|
|
74
|
+
# Measured on the reference fixture: `voltro serve` boot 287 ms → 222 ms.
|
|
75
|
+
#
|
|
76
|
+
# The prune's `|| echo` covers an older framework without the command.
|
|
77
|
+
#
|
|
78
|
+
# Then TWO steps, and their being two instructions is the point.
|
|
79
|
+
#
|
|
80
|
+
# The LOAD is a hard gate. An api legitimately refuses to boot in this stage —
|
|
81
|
+
# it has no database and no secrets — so demanding `serve: ready` here would
|
|
82
|
+
# fail every honest build. But "cannot boot without a database" is not the same
|
|
83
|
+
# sentence as "cannot be checked": every failure this step exists to catch — a
|
|
84
|
+
# module `prune-runtime` traced away, a truncated artefact, a wrong entry path,
|
|
85
|
+
# a missing export — happens at IMPORT, before anything connects. So the import
|
|
86
|
+
# must succeed and `runServe` must be callable, or the build stops here.
|
|
87
|
+
#
|
|
88
|
+
# The SMOKE is not a gate. It reports how far the boot got, and both steps write
|
|
89
|
+
# the V8 code cache into the image.
|
|
90
|
+
#
|
|
91
|
+
# They are separate RUN instructions on purpose. Chained into one shell command,
|
|
92
|
+
# the smoke's trailing `|| echo` becomes the instruction's exit status, and a
|
|
93
|
+
# failed load gate would ship a green build — the same shape as a pipe
|
|
94
|
+
# swallowing the exit code of the check in front of it.
|
|
95
|
+
RUN cd /deploy \
|
|
96
|
+
&& (pnpm --filter="$(node -p "require('/deploy/package.json').name")" exec voltro prune-runtime /deploy \
|
|
97
|
+
|| echo "prune-runtime unavailable — shipping the un-pruned tree") \
|
|
98
|
+
&& NODE_COMPILE_CACHE=/deploy/.framework/.compile-cache node --input-type=module -e "\
|
|
99
|
+
const m = await import('/deploy/.framework/dist-api/serveBundle/serveEntry.js'); \
|
|
100
|
+
if (typeof m.runServe !== 'function') { \
|
|
101
|
+
console.error('load gate: the serve bundle exports no callable runServe'); \
|
|
102
|
+
process.exit(1); \
|
|
103
|
+
} \
|
|
104
|
+
console.log('load gate: the serve bundle imports, and runServe is callable');"
|
|
105
|
+
|
|
106
|
+
RUN cd /deploy \
|
|
107
|
+
; NODE_ENV=production NODE_COMPILE_CACHE=/deploy/.framework/.compile-cache \
|
|
108
|
+
timeout 20 node .framework/dist-api/serveBundle/serveEntry.js > /tmp/smoke.log 2>&1 \
|
|
109
|
+
; grep -q 'serve: ready' /tmp/smoke.log \
|
|
110
|
+
&& echo "boot smoke: ready (compile cache baked)" \
|
|
111
|
+
|| echo "boot smoke: no DB/secrets in this stage — the load gate above is the one that had to pass"
|
|
112
|
+
|
|
61
113
|
# ---------- prod runner ----------
|
|
62
114
|
FROM node:${NODE_VERSION} AS prod
|
|
63
115
|
ARG PNPM_VERSION
|
|
64
|
-
RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate
|
|
65
116
|
WORKDIR /app
|
|
66
117
|
COPY --from=builder /deploy /app
|
|
67
118
|
EXPOSE 4000
|
|
68
119
|
ENV NODE_ENV=production
|
|
69
|
-
#
|
|
70
|
-
|
|
71
|
-
#
|
|
72
|
-
|
|
120
|
+
# Reuse the cache the builder baked; node reads it before any user code runs.
|
|
121
|
+
ENV NODE_COMPILE_CACHE=/app/.framework/.compile-cache
|
|
122
|
+
# Direct-node entrypoint — boots the precompiled serve bundle IN THIS PROCESS.
|
|
123
|
+
# No pnpm process, no @voltro/cli bin to resolve, which is what lets
|
|
124
|
+
# `prune-runtime` drop the CLI from the runtime tree entirely. The bundle's
|
|
125
|
+
# generated entry loads `.env` itself and chdir's to the app root, so it is
|
|
126
|
+
# self-contained. (`pnpm voltro serve .` still works and does the same thing —
|
|
127
|
+
# it just pays a second node startup and needs the CLI present.)
|
|
128
|
+
CMD ["node", ".framework/dist-api/serveBundle/serveEntry.js"]
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
# Must match the app's app.config.ts `port` field.
|
|
11
11
|
#
|
|
12
12
|
# Targets:
|
|
13
|
-
# prod — production image
|
|
13
|
+
# prod — production image: `voltro build` → `pnpm deploy` → `prune-runtime`
|
|
14
|
+
# → boot smoke (which also bakes the V8 code cache) → `node startEntry.js`
|
|
14
15
|
#
|
|
15
16
|
# Build:
|
|
16
17
|
# docker build \
|
|
@@ -41,19 +42,63 @@ RUN pnpm install --frozen-lockfile
|
|
|
41
42
|
FROM deps AS builder
|
|
42
43
|
ARG APP_PATH
|
|
43
44
|
WORKDIR /workspace/${APP_PATH}
|
|
45
|
+
# Builds the client assets, the SSR bundle and the start bundle. NOT
|
|
46
|
+
# best-effort: an SSR-bundle failure is fatal by design, so an image whose
|
|
47
|
+
# `voltro start` would fall back to compiling TSX per request never ships.
|
|
44
48
|
RUN pnpm voltro build .
|
|
45
49
|
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
# Isolate a prod-only, self-contained tree for THIS app, then carry the build
|
|
51
|
+
# OUTPUT into it — `pnpm deploy` copies package files, not generated output.
|
|
52
|
+
#
|
|
53
|
+
# `--no-optional` drops @voltro/cli's BUILD toolchain (tsx/esbuild/vite/tailwind
|
|
54
|
+
# and their native tree), declared as optionalDependencies precisely so a
|
|
55
|
+
# serve-only install sheds them. Nothing on the production start path is
|
|
56
|
+
# TypeScript: `voltro build` precompiled `app.config.ts` next to the SSR bundle,
|
|
57
|
+
# and the CMD below runs the start bundle directly.
|
|
52
58
|
WORKDIR /workspace
|
|
59
|
+
RUN APP_NAME=$(node -p "require('/workspace/${APP_PATH}/package.json').name") \
|
|
60
|
+
&& pnpm --filter="${APP_NAME}" --prod --no-optional --legacy deploy /deploy \
|
|
61
|
+
&& cp -r /workspace/${APP_PATH}/.framework /deploy/.framework \
|
|
62
|
+
&& rm -f /deploy/app.config.ts \
|
|
63
|
+
&& (pnpm --filter="${APP_NAME}" exec voltro prune-runtime /deploy \
|
|
64
|
+
|| echo "prune-runtime unavailable — shipping the un-pruned tree")
|
|
65
|
+
# The prune runs in THIS stage, where @vercel/nft (an @voltro/cli
|
|
66
|
+
# optionalDependency) is still installed, and in the same RUN as the deploy so
|
|
67
|
+
# `APP_NAME` is still in scope. The entrypoint is `node <startBundle>` (the CMD
|
|
68
|
+
# below), so the container never resolves the @voltro/cli bin — nft traces the
|
|
69
|
+
# reachable set FROM that bundle and drops everything else. A used native leaf
|
|
70
|
+
# (a SQL driver an ISR/config path touches) is traced + kept automatically.
|
|
53
71
|
|
|
54
|
-
|
|
72
|
+
# BAKE THE V8 CODE CACHE, and prove the image boots, in one step.
|
|
73
|
+
#
|
|
74
|
+
# The boot smoke is the gate: it starts the PRUNED tree with the EXACT prod
|
|
75
|
+
# entrypoint and REQUIRES the framework's `start: ready` line, so a pruned-away
|
|
76
|
+
# module or a broken artefact fails the BUILD instead of rolling out red. No
|
|
77
|
+
# `|| true` — no ready line MUST fail the build.
|
|
78
|
+
#
|
|
79
|
+
# `NODE_COMPILE_CACHE` makes that same run write node's compiled-bytecode cache
|
|
80
|
+
# into the image. A scale-from-zero container starts with an empty temp dir, so
|
|
81
|
+
# without baking it here the cache would never be warm when it matters. Measured
|
|
82
|
+
# on the reference fixture: `voltro start` boot 166 ms → 83 ms.
|
|
83
|
+
RUN cd /deploy \
|
|
84
|
+
&& NODE_ENV=production PORT=8099 VOLTRO_BOOT_TIMING=1 \
|
|
85
|
+
NODE_COMPILE_CACHE=/deploy/.framework/.compile-cache \
|
|
86
|
+
timeout 25 node .framework/dist-web/startBundle/startEntry.js 2>&1 \
|
|
87
|
+
| grep -m1 'start: ready'
|
|
55
88
|
|
|
56
|
-
|
|
89
|
+
# ---------- prod runner ----------
|
|
90
|
+
FROM node:${NODE_VERSION} AS prod
|
|
91
|
+
ARG APP_PORT
|
|
92
|
+
# No pnpm/corepack here: the entrypoint is a direct `node` invocation of the
|
|
93
|
+
# self-contained start bundle, so the runtime image needs only the node base
|
|
94
|
+
# plus the pruned /deploy tree.
|
|
95
|
+
WORKDIR /app
|
|
96
|
+
COPY --from=builder /deploy /app
|
|
57
97
|
EXPOSE ${APP_PORT}
|
|
58
98
|
ENV NODE_ENV=production
|
|
59
|
-
|
|
99
|
+
# Reuse the cache the builder baked. Node reads it before any user code runs.
|
|
100
|
+
ENV NODE_COMPILE_CACHE=/app/.framework/.compile-cache
|
|
101
|
+
# Direct-node entrypoint — boots the precompiled start bundle IN THIS PROCESS,
|
|
102
|
+
# no `pnpm voltro start` wrapper (no pnpm process, no @voltro/cli bin to
|
|
103
|
+
# resolve). This is what lets `prune-runtime` drop the CLI from the runtime tree.
|
|
104
|
+
CMD ["node", ".framework/dist-web/startBundle/startEntry.js"]
|
|
@@ -58,15 +58,71 @@ RUN APP_NAME=$(node -p "require('/workspace/${APP_PATH}/package.json').name") \
|
|
|
58
58
|
&& pnpm --filter="${APP_NAME}" --prod --no-optional --legacy deploy /deploy \
|
|
59
59
|
; cp -r /workspace/${APP_PATH}/.framework /deploy/.framework 2>/dev/null || echo "no .framework (serve loads from source)"
|
|
60
60
|
|
|
61
|
+
|
|
62
|
+
# SLIM the runtime tree + BAKE the V8 code cache, and prove the image boots.
|
|
63
|
+
#
|
|
64
|
+
# The entrypoint below is `node <serveBundle>`, so the container never resolves
|
|
65
|
+
# the @voltro/cli bin — `voltro prune-runtime` @vercel/nft-traces the reachable
|
|
66
|
+
# set FROM that bundle and drops the rest (the used native SQL driver is traced
|
|
67
|
+
# and kept automatically; an unused one is dropped). It runs in THIS stage,
|
|
68
|
+
# where @vercel/nft is still installed.
|
|
69
|
+
#
|
|
70
|
+
# The boot smoke is the gate: it starts the PRUNED tree with the EXACT prod
|
|
71
|
+
# entrypoint. `NODE_COMPILE_CACHE` makes that same run write node's compiled
|
|
72
|
+
# bytecode into the image — a scale-from-zero container starts with an empty
|
|
73
|
+
# temp dir, so baking it here is the only way it is warm when it matters.
|
|
74
|
+
# Measured on the reference fixture: `voltro serve` boot 287 ms → 222 ms.
|
|
75
|
+
#
|
|
76
|
+
# The prune's `|| echo` covers an older framework without the command.
|
|
77
|
+
#
|
|
78
|
+
# Then TWO steps, and their being two instructions is the point.
|
|
79
|
+
#
|
|
80
|
+
# The LOAD is a hard gate. An api legitimately refuses to boot in this stage —
|
|
81
|
+
# it has no database and no secrets — so demanding `serve: ready` here would
|
|
82
|
+
# fail every honest build. But "cannot boot without a database" is not the same
|
|
83
|
+
# sentence as "cannot be checked": every failure this step exists to catch — a
|
|
84
|
+
# module `prune-runtime` traced away, a truncated artefact, a wrong entry path,
|
|
85
|
+
# a missing export — happens at IMPORT, before anything connects. So the import
|
|
86
|
+
# must succeed and `runServe` must be callable, or the build stops here.
|
|
87
|
+
#
|
|
88
|
+
# The SMOKE is not a gate. It reports how far the boot got, and both steps write
|
|
89
|
+
# the V8 code cache into the image.
|
|
90
|
+
#
|
|
91
|
+
# They are separate RUN instructions on purpose. Chained into one shell command,
|
|
92
|
+
# the smoke's trailing `|| echo` becomes the instruction's exit status, and a
|
|
93
|
+
# failed load gate would ship a green build — the same shape as a pipe
|
|
94
|
+
# swallowing the exit code of the check in front of it.
|
|
95
|
+
RUN cd /deploy \
|
|
96
|
+
&& (pnpm --filter="$(node -p "require('/deploy/package.json').name")" exec voltro prune-runtime /deploy \
|
|
97
|
+
|| echo "prune-runtime unavailable — shipping the un-pruned tree") \
|
|
98
|
+
&& NODE_COMPILE_CACHE=/deploy/.framework/.compile-cache node --input-type=module -e "\
|
|
99
|
+
const m = await import('/deploy/.framework/dist-api/serveBundle/serveEntry.js'); \
|
|
100
|
+
if (typeof m.runServe !== 'function') { \
|
|
101
|
+
console.error('load gate: the serve bundle exports no callable runServe'); \
|
|
102
|
+
process.exit(1); \
|
|
103
|
+
} \
|
|
104
|
+
console.log('load gate: the serve bundle imports, and runServe is callable');"
|
|
105
|
+
|
|
106
|
+
RUN cd /deploy \
|
|
107
|
+
; NODE_ENV=production NODE_COMPILE_CACHE=/deploy/.framework/.compile-cache \
|
|
108
|
+
timeout 20 node .framework/dist-api/serveBundle/serveEntry.js > /tmp/smoke.log 2>&1 \
|
|
109
|
+
; grep -q 'serve: ready' /tmp/smoke.log \
|
|
110
|
+
&& echo "boot smoke: ready (compile cache baked)" \
|
|
111
|
+
|| echo "boot smoke: no DB/secrets in this stage — the load gate above is the one that had to pass"
|
|
112
|
+
|
|
61
113
|
# ---------- prod runner ----------
|
|
62
114
|
FROM node:${NODE_VERSION} AS prod
|
|
63
115
|
ARG PNPM_VERSION
|
|
64
|
-
RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate
|
|
65
116
|
WORKDIR /app
|
|
66
117
|
COPY --from=builder /deploy /app
|
|
67
118
|
EXPOSE 4000
|
|
68
119
|
ENV NODE_ENV=production
|
|
69
|
-
#
|
|
70
|
-
|
|
71
|
-
#
|
|
72
|
-
|
|
120
|
+
# Reuse the cache the builder baked; node reads it before any user code runs.
|
|
121
|
+
ENV NODE_COMPILE_CACHE=/app/.framework/.compile-cache
|
|
122
|
+
# Direct-node entrypoint — boots the precompiled serve bundle IN THIS PROCESS.
|
|
123
|
+
# No pnpm process, no @voltro/cli bin to resolve, which is what lets
|
|
124
|
+
# `prune-runtime` drop the CLI from the runtime tree entirely. The bundle's
|
|
125
|
+
# generated entry loads `.env` itself and chdir's to the app root, so it is
|
|
126
|
+
# self-contained. (`pnpm voltro serve .` still works and does the same thing —
|
|
127
|
+
# it just pays a second node startup and needs the CLI present.)
|
|
128
|
+
CMD ["node", ".framework/dist-api/serveBundle/serveEntry.js"]
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
# Must match the app's app.config.ts `port` field.
|
|
11
11
|
#
|
|
12
12
|
# Targets:
|
|
13
|
-
# prod — production image
|
|
13
|
+
# prod — production image: `voltro build` → `pnpm deploy` → `prune-runtime`
|
|
14
|
+
# → boot smoke (which also bakes the V8 code cache) → `node startEntry.js`
|
|
14
15
|
#
|
|
15
16
|
# Build:
|
|
16
17
|
# docker build \
|
|
@@ -41,19 +42,63 @@ RUN pnpm install --frozen-lockfile
|
|
|
41
42
|
FROM deps AS builder
|
|
42
43
|
ARG APP_PATH
|
|
43
44
|
WORKDIR /workspace/${APP_PATH}
|
|
45
|
+
# Builds the client assets, the SSR bundle and the start bundle. NOT
|
|
46
|
+
# best-effort: an SSR-bundle failure is fatal by design, so an image whose
|
|
47
|
+
# `voltro start` would fall back to compiling TSX per request never ships.
|
|
44
48
|
RUN pnpm voltro build .
|
|
45
49
|
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
# Isolate a prod-only, self-contained tree for THIS app, then carry the build
|
|
51
|
+
# OUTPUT into it — `pnpm deploy` copies package files, not generated output.
|
|
52
|
+
#
|
|
53
|
+
# `--no-optional` drops @voltro/cli's BUILD toolchain (tsx/esbuild/vite/tailwind
|
|
54
|
+
# and their native tree), declared as optionalDependencies precisely so a
|
|
55
|
+
# serve-only install sheds them. Nothing on the production start path is
|
|
56
|
+
# TypeScript: `voltro build` precompiled `app.config.ts` next to the SSR bundle,
|
|
57
|
+
# and the CMD below runs the start bundle directly.
|
|
52
58
|
WORKDIR /workspace
|
|
59
|
+
RUN APP_NAME=$(node -p "require('/workspace/${APP_PATH}/package.json').name") \
|
|
60
|
+
&& pnpm --filter="${APP_NAME}" --prod --no-optional --legacy deploy /deploy \
|
|
61
|
+
&& cp -r /workspace/${APP_PATH}/.framework /deploy/.framework \
|
|
62
|
+
&& rm -f /deploy/app.config.ts \
|
|
63
|
+
&& (pnpm --filter="${APP_NAME}" exec voltro prune-runtime /deploy \
|
|
64
|
+
|| echo "prune-runtime unavailable — shipping the un-pruned tree")
|
|
65
|
+
# The prune runs in THIS stage, where @vercel/nft (an @voltro/cli
|
|
66
|
+
# optionalDependency) is still installed, and in the same RUN as the deploy so
|
|
67
|
+
# `APP_NAME` is still in scope. The entrypoint is `node <startBundle>` (the CMD
|
|
68
|
+
# below), so the container never resolves the @voltro/cli bin — nft traces the
|
|
69
|
+
# reachable set FROM that bundle and drops everything else. A used native leaf
|
|
70
|
+
# (a SQL driver an ISR/config path touches) is traced + kept automatically.
|
|
53
71
|
|
|
54
|
-
|
|
72
|
+
# BAKE THE V8 CODE CACHE, and prove the image boots, in one step.
|
|
73
|
+
#
|
|
74
|
+
# The boot smoke is the gate: it starts the PRUNED tree with the EXACT prod
|
|
75
|
+
# entrypoint and REQUIRES the framework's `start: ready` line, so a pruned-away
|
|
76
|
+
# module or a broken artefact fails the BUILD instead of rolling out red. No
|
|
77
|
+
# `|| true` — no ready line MUST fail the build.
|
|
78
|
+
#
|
|
79
|
+
# `NODE_COMPILE_CACHE` makes that same run write node's compiled-bytecode cache
|
|
80
|
+
# into the image. A scale-from-zero container starts with an empty temp dir, so
|
|
81
|
+
# without baking it here the cache would never be warm when it matters. Measured
|
|
82
|
+
# on the reference fixture: `voltro start` boot 166 ms → 83 ms.
|
|
83
|
+
RUN cd /deploy \
|
|
84
|
+
&& NODE_ENV=production PORT=8099 VOLTRO_BOOT_TIMING=1 \
|
|
85
|
+
NODE_COMPILE_CACHE=/deploy/.framework/.compile-cache \
|
|
86
|
+
timeout 25 node .framework/dist-web/startBundle/startEntry.js 2>&1 \
|
|
87
|
+
| grep -m1 'start: ready'
|
|
55
88
|
|
|
56
|
-
|
|
89
|
+
# ---------- prod runner ----------
|
|
90
|
+
FROM node:${NODE_VERSION} AS prod
|
|
91
|
+
ARG APP_PORT
|
|
92
|
+
# No pnpm/corepack here: the entrypoint is a direct `node` invocation of the
|
|
93
|
+
# self-contained start bundle, so the runtime image needs only the node base
|
|
94
|
+
# plus the pruned /deploy tree.
|
|
95
|
+
WORKDIR /app
|
|
96
|
+
COPY --from=builder /deploy /app
|
|
57
97
|
EXPOSE ${APP_PORT}
|
|
58
98
|
ENV NODE_ENV=production
|
|
59
|
-
|
|
99
|
+
# Reuse the cache the builder baked. Node reads it before any user code runs.
|
|
100
|
+
ENV NODE_COMPILE_CACHE=/app/.framework/.compile-cache
|
|
101
|
+
# Direct-node entrypoint — boots the precompiled start bundle IN THIS PROCESS,
|
|
102
|
+
# no `pnpm voltro start` wrapper (no pnpm process, no @voltro/cli bin to
|
|
103
|
+
# resolve). This is what lets `prune-runtime` drop the CLI from the runtime tree.
|
|
104
|
+
CMD ["node", ".framework/dist-web/startBundle/startEntry.js"]
|