@decocms/start 2.28.2 → 2.30.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 (49) hide show
  1. package/.agents/skills/deco-to-tanstack-migration/SKILL.md +1 -1
  2. package/.agents/skills/deco-to-tanstack-migration/references/vtex-commerce.md +5 -1
  3. package/.agents/skills/deco-to-tanstack-migration/references/worker-cloudflare.md +107 -10
  4. package/.agents/skills/deco-to-tanstack-migration/templates/package-json.md +5 -1
  5. package/.cursor/rules/migration-tooling-policy.mdc +22 -2
  6. package/.github/workflows/deploy.yml +115 -0
  7. package/.github/workflows/preview.yml +143 -0
  8. package/.github/workflows/regen-blocks.yml +56 -0
  9. package/.github/workflows/release.yml +26 -0
  10. package/.github/workflows/sync-secrets.yml +173 -0
  11. package/CODEOWNERS +16 -0
  12. package/MIGRATION_TOOLING_PLAN.md +16 -4
  13. package/README.md +178 -79
  14. package/deploy/README.md +85 -0
  15. package/deploy/sites/als-tanstack.jsonc +7 -0
  16. package/deploy/sites/americanas-tanstack.jsonc +4 -0
  17. package/deploy/sites/baggagio-tanstack.jsonc +4 -0
  18. package/deploy/sites/casaevideo-storefront.jsonc +11 -0
  19. package/deploy/sites/lebiscuit-tanstack.jsonc +19 -0
  20. package/deploy/sites/miess-01-tanstack.jsonc +8 -0
  21. package/deploy/wrangler-template.jsonc +28 -0
  22. package/package.json +18 -15
  23. package/scripts/deploy/build-wrangler-config.mjs +49 -0
  24. package/scripts/deploy/jsonc.mjs +76 -0
  25. package/scripts/deploy/resolve-site.mjs +58 -0
  26. package/scripts/deploy/site-registry.mjs +142 -0
  27. package/scripts/deploy/wrangler-wrapper.mjs +126 -0
  28. package/scripts/migrate/phase-scaffold.ts +13 -3
  29. package/scripts/migrate/phase-verify.ts +6 -1
  30. package/scripts/migrate/templates/github-workflows.ts +98 -0
  31. package/scripts/migrate/templates/package-json.ts +9 -2
  32. package/src/cms/resolve.ts +81 -63
  33. package/src/cms/sectionLoaders.ts +11 -0
  34. package/src/index.ts +3 -0
  35. package/src/sdk/cachedLoader.ts +36 -13
  36. package/src/sdk/composite.test.ts +121 -0
  37. package/src/sdk/composite.ts +114 -0
  38. package/src/sdk/instrumentedFetch.ts +56 -0
  39. package/src/sdk/logger.test.ts +135 -0
  40. package/src/sdk/logger.ts +166 -0
  41. package/src/sdk/observability.ts +75 -0
  42. package/src/sdk/otel.test.ts +59 -0
  43. package/src/sdk/otel.ts +270 -29
  44. package/src/sdk/otelAdapters.test.ts +135 -0
  45. package/src/sdk/otelAdapters.ts +401 -0
  46. package/src/sdk/sampler.test.ts +127 -0
  47. package/src/sdk/sampler.ts +183 -0
  48. package/src/sdk/workerEntry.ts +541 -476
  49. package/scripts/migrate/templates/wrangler.ts +0 -30
@@ -0,0 +1,173 @@
1
+ name: sync-secrets (central)
2
+
3
+ # Reusable workflow. Reconciles the caller's `SECRET_*` GitHub repo secrets
4
+ # with the Cloudflare Worker's runtime secrets (with the prefix stripped).
5
+ #
6
+ # Examples:
7
+ # SECRET_STRIPE_KEY in GitHub -> STRIPE_KEY on the worker
8
+ #
9
+ # Defaults to dry-run; the caller must pass `mode: apply` to actually write.
10
+ # Orphans on the worker (present on worker but not in GitHub as SECRET_*) are
11
+ # warned about, never deleted.
12
+ #
13
+ # Caller usage (in customer repo, `.github/workflows/sync-secrets.yml`):
14
+ #
15
+ # on:
16
+ # workflow_dispatch:
17
+ # inputs:
18
+ # mode:
19
+ # description: "dry-run | apply"
20
+ # required: true
21
+ # default: "dry-run"
22
+ # type: choice
23
+ # options: [dry-run, apply]
24
+ # jobs:
25
+ # sync:
26
+ # uses: decocms/deco-start/.github/workflows/sync-secrets.yml@v2
27
+ # with:
28
+ # mode: ${{ inputs.mode }}
29
+ # secrets: inherit
30
+
31
+ on:
32
+ workflow_call:
33
+ inputs:
34
+ mode:
35
+ description: "dry-run = print diff only | apply = set secrets on worker"
36
+ required: false
37
+ type: string
38
+ default: "dry-run"
39
+ secrets:
40
+ CLOUDFLARE_API_TOKEN:
41
+ required: true
42
+ CLOUDFLARE_ACCOUNT_ID:
43
+ required: true
44
+
45
+ permissions:
46
+ contents: read
47
+
48
+ concurrency:
49
+ group: sync-secrets-${{ github.repository }}
50
+ cancel-in-progress: false
51
+
52
+ jobs:
53
+ sync:
54
+ runs-on: ubuntu-latest
55
+ steps:
56
+ - uses: actions/checkout@v4
57
+
58
+ - name: Resolve deco-start ref + site identity
59
+ id: meta
60
+ run: |
61
+ WF_REF="${{ github.workflow_ref }}"
62
+ REF="${WF_REF##*@}"
63
+ REF="${REF#refs/tags/}"
64
+ REF="${REF#refs/heads/}"
65
+ echo "deco_start_ref=$REF" >> "$GITHUB_OUTPUT"
66
+ echo "site_name=${GITHUB_REPOSITORY#*/}" >> "$GITHUB_OUTPUT"
67
+
68
+ - name: Checkout deco-start registry at the same ref
69
+ uses: actions/checkout@v4
70
+ with:
71
+ repository: decocms/deco-start
72
+ ref: ${{ steps.meta.outputs.deco_start_ref }}
73
+ path: .deco-start
74
+
75
+ - uses: actions/setup-node@v4
76
+ with:
77
+ node-version: 22
78
+
79
+ - name: Resolve site manifest
80
+ id: site
81
+ run: node .deco-start/scripts/deploy/resolve-site.mjs
82
+ env:
83
+ DECO_START_PATH: .deco-start
84
+ SITE_NAME: ${{ steps.meta.outputs.site_name }}
85
+
86
+ - name: Generate wrangler.jsonc
87
+ run: node .deco-start/scripts/deploy/build-wrangler-config.mjs
88
+ env:
89
+ DECO_START_PATH: .deco-start
90
+ SITE_NAME: ${{ steps.meta.outputs.site_name }}
91
+ OUTPUT_PATH: ./wrangler.jsonc
92
+
93
+ - name: Install wrangler
94
+ run: npm install --no-save wrangler@4
95
+
96
+ - name: Plan and (optionally) apply
97
+ env:
98
+ CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
99
+ CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
100
+ ALL_SECRETS: ${{ toJSON(secrets) }}
101
+ MODE: ${{ inputs.mode }}
102
+ run: |
103
+ set -euo pipefail
104
+
105
+ # Build desired-state map from SECRET_* entries.
106
+ desired_json=$(printf '%s' "$ALL_SECRETS" | jq '
107
+ to_entries
108
+ | map(select(.key | startswith("SECRET_")))
109
+ | map({ key: (.key | sub("^SECRET_"; "")), value: .value })
110
+ | from_entries
111
+ ')
112
+
113
+ desired_names=$(printf '%s' "$desired_json" | jq -r 'keys[]' | sort)
114
+
115
+ # Validate names before any side effect.
116
+ while IFS= read -r name; do
117
+ [ -z "$name" ] && continue
118
+ if ! [[ "$name" =~ ^[A-Z][A-Z0-9_]{0,63}$ ]]; then
119
+ echo "::error::Invalid worker secret name derived from SECRET_${name}: must match ^[A-Z][A-Z0-9_]{0,63}$"
120
+ exit 1
121
+ fi
122
+ done <<< "$desired_names"
123
+
124
+ # Snapshot what's currently on the worker.
125
+ existing=$(npx wrangler secret list --format=json | jq -r '.[].name' | sort)
126
+
127
+ # Diff
128
+ to_set="$desired_names"
129
+ orphans=$(comm -23 <(printf '%s\n' "$existing") <(printf '%s\n' "$desired_names") || true)
130
+
131
+ {
132
+ echo "## Diff"
133
+ echo ""
134
+ echo "### Will set (from SECRET_* in GitHub):"
135
+ if [ -z "$to_set" ]; then
136
+ echo " (none)"
137
+ else
138
+ echo "$to_set" | sed 's/^/ + /'
139
+ fi
140
+ echo ""
141
+ echo "### Orphans (on worker, not in GitHub as SECRET_*):"
142
+ if [ -z "$orphans" ]; then
143
+ echo " (none)"
144
+ else
145
+ echo "$orphans" | sed 's/^/ ! /'
146
+ echo ""
147
+ echo " These will NOT be deleted automatically. To remove manually:"
148
+ echo "$orphans" | sed 's|^| npx wrangler secret delete "|; s|$|" --force|'
149
+ fi
150
+ } | tee -a "$GITHUB_STEP_SUMMARY"
151
+
152
+ if [ -n "$orphans" ]; then
153
+ echo "::warning::${orphans//$'\n'/, } exist on the worker but not in GitHub as SECRET_*. Not deleting."
154
+ fi
155
+
156
+ if [ "$MODE" = "dry-run" ]; then
157
+ echo "::notice::dry-run mode -- no changes applied"
158
+ exit 0
159
+ fi
160
+
161
+ # Apply
162
+ if [ -z "$to_set" ]; then
163
+ echo "Nothing to apply."
164
+ exit 0
165
+ fi
166
+
167
+ printf '%s' "$desired_json" | jq -r 'to_entries[] | "\(.key)\t\(.value)"' \
168
+ | while IFS=$'\t' read -r name value; do
169
+ echo "Setting $name..."
170
+ printf '%s' "$value" | npx wrangler secret put "$name"
171
+ done
172
+
173
+ echo "::notice::Applied $(echo "$to_set" | wc -l | tr -d ' ') secrets."
package/CODEOWNERS ADDED
@@ -0,0 +1,16 @@
1
+ # CODEOWNERS for decocms/deco-start
2
+ #
3
+ # `deploy/` is the trust boundary for the central deploy pipeline. The files
4
+ # under `deploy/sites/` immutably bind a customer repo to a Cloudflare worker;
5
+ # `deploy/wrangler-template.jsonc` is the canonical wrangler config every site
6
+ # inherits. A bad PR here can misroute a deploy or change every site's runtime
7
+ # configuration in one shot. Only the platform team approves changes.
8
+ #
9
+ # The central reusable workflows are in the same trust boundary: they decide
10
+ # how every site is built and deployed.
11
+ deploy/ @vibe-dex
12
+ .github/workflows/deploy.yml @vibe-dex
13
+ .github/workflows/preview.yml @vibe-dex
14
+ .github/workflows/sync-secrets.yml @vibe-dex
15
+ .github/workflows/regen-blocks.yml @vibe-dex
16
+ scripts/deploy/ @vibe-dex
@@ -117,6 +117,7 @@ this plan.
117
117
  | 2026-05-01 | **D3 — Stub generation: throw at runtime (Option C)** | Migration-time stubs throw with a clear pointer to the canonical replacement instead of silently identity-casting. Forces audit `--fix` to cover swap cases (no permanent detect-only state) and skills to keep up with stub generation. |
118
118
  | 2026-05-01 | **D4 — Site-local apps: local by default, promote at 3** | Site-specific apps live in `src/apps/local/` until ≥3 sites use them, then promote to `@decocms/apps`. |
119
119
  | 2026-05-01 | **D5 — Failed migrations: rm -rf and re-run** | No `--restart` mode. Half-migrated sites are throwaways. Failure modes get documented in skills, not encoded as escape hatches. |
120
+ | 2026-05-07 | **D6 — Deploy / preview / secrets pipelines: centralize in `deco-start`** | At 6 sites the "1-minute copy" of `deploy.yml` / `preview.yml` / `wrangler.jsonc` had already produced unintended drift (lebiscuit missing 2 workflows, miess missing `account_id`, casaevideo's `loadtest:tail` worker name out of sync with its wrangler config). All sites now consume reusable workflows from `decocms/deco-start@v2` and a per-site registry under [`deploy/sites/<repo>.jsonc`](./deploy/) deep-merged on top of [`deploy/wrangler-template.jsonc`](./deploy/wrangler-template.jsonc) at deploy time. Customer repos hold only ~5-line caller workflows; `wrangler.jsonc` is generated and gitignored. The repo→worker binding is the trust boundary that prevents one site's commits from misrouting onto another site's worker (the central workflow ignores caller `inputs:` for identity and derives the site name from `${{ github.repository }}`). See [`deploy/README.md`](./deploy/README.md) for the contract. |
120
121
 
121
122
  The full text of the constitutional rule (loaded into every agent
122
123
  session for this repo) lives at
@@ -1669,10 +1670,21 @@ props. One broken section never takes the page down.
1669
1670
  release picks up the framework export — TODO is checked into
1670
1671
  `src/lib/http-utils.ts`.
1671
1672
  - **CI/CD porting from casaevideo to a new TanStack site is a 1-minute
1672
- copy.** `deploy.yml`, `preview.yml`, `regen-blocks.yml`, plus
1673
- `wrangler.jsonc` worker `name` rename, plus `account_id` paste from
1674
- another site. No template needed yet — three sites is too few. Will
1675
- template if a fourth migration needs it.
1673
+ copy** (when this note was written, at 3 sites). By 6 sites
1674
+ the copy-paste had drifted: lebiscuit was missing
1675
+ `regen-blocks.yml` and `sync-secrets.yml`, miess was missing
1676
+ `regen-blocks.yml` and its `wrangler.jsonc` lacked `account_id`,
1677
+ lebiscuit's preview workflow swallowed `wrangler` exit codes, and
1678
+ casaevideo's `loadtest:tail` referenced a worker name that didn't
1679
+ match its `wrangler.jsonc`. **Resolved 2026-05-07 via D6:** all
1680
+ workflows + wrangler config are centralized in
1681
+ [`deco-start/.github/workflows/`](./.github/workflows/) and
1682
+ [`deco-start/deploy/`](./deploy/). New-site onboarding is now: open a
1683
+ PR adding `deploy/sites/<repo>.jsonc` to deco-start, then drop the
1684
+ ~5-line caller workflows into the new site's repo. No `wrangler.jsonc`
1685
+ is committed to the site repo — `deco-wrangler gen`
1686
+ (a `bin` shipped from `@decocms/start`) materializes it from the
1687
+ central registry on demand for local dev and CI alike.
1676
1688
 
1677
1689
  #### Counter-evidence the user-rule asks for
1678
1690
 
package/README.md CHANGED
@@ -1,124 +1,219 @@
1
- # @decocms/start
1
+ # @decocms/start
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@decocms/start.svg)](https://www.npmjs.com/package/@decocms/start)
4
4
  [![license](https://img.shields.io/npm/l/@decocms/start.svg)](https://github.com/decocms/deco-start/blob/main/LICENSE)
5
5
 
6
- Framework layer for [Deco](https://deco.cx) storefronts built on **TanStack Start + React 19 + Cloudflare Workers**.
6
+ Framework layer for [deco.cx](https://deco.cx) storefronts on **TanStack Start + React 19 + Cloudflare Workers**.
7
7
 
8
- Provides CMS block resolution, admin protocol handlers, section rendering, schema generation, edge caching, and SDK utilities. This is **not** a storefront — it's the npm package that storefronts depend on.
8
+ `@decocms/start` is the npm package that storefronts depend on. It provides the CMS bridge, admin protocol, section registry, schema generation, edge caching, the Vite plugin, and a small SDK. It is **not** itself a storefront — it is what storefronts build on top of.
9
9
 
10
- ## Install
10
+ 📖 **[Read the full documentation →](https://docs.deco.cx/v2/en/getting-started/overview)**
11
+
12
+ ---
13
+
14
+ ## What's in the box
11
15
 
12
- ```bash
13
- npm install @decocms/start
16
+ ```
17
+ ┌─────────────────────────────────────────────────┐
18
+ │ Site repo (your storefront) │ ← Components, sections, routes
19
+ ├─────────────────────────────────────────────────┤
20
+ │ @decocms/apps (commerce integrations) │ ← VTEX, Shopify, Resend
21
+ ├─────────────────────────────────────────────────┤
22
+ │ @decocms/start (framework — this package) │ ← CMS bridge, admin, caching
23
+ └─────────────────────────────────────────────────┘
24
+ ↓ runs on ↓
25
+ TanStack Start + React 19 + Cloudflare Workers
14
26
  ```
15
27
 
16
- ## Architecture
28
+ `@decocms/start` exports cover four surfaces:
29
+
30
+ - **Worker entry** — `createDecoWorkerEntry` wraps your Cloudflare Worker with admin routes, edge cache, and asset bypass.
31
+ - **CMS bridge** — `loadCmsPage`, `resolveDecoPage`, `registerSectionLoaders`, `registerLayoutSections`.
32
+ - **Admin protocol** — `handleMeta`, `handleDecofile`, `handleRender`, `handleInvoke`.
33
+ - **SDK** — `createCachedLoader`, `createInstrumentedFetch`, `createInvoke`, `decoVitePlugin`, plus utilities (cookies, redirects, sitemap, A/B testing).
34
+
35
+ Full export reference: [docs.deco.cx/v2/en/reference/package-exports](https://docs.deco.cx/v2/en/reference/package-exports).
36
+
37
+ ---
38
+
39
+ ## Hello, World
40
+
41
+ A minimal v2 storefront has six files. Here they are.
42
+
43
+ ### `package.json`
44
+
45
+ ```jsonc
46
+ {
47
+ "name": "my-store",
48
+ "type": "module",
49
+ "scripts": {
50
+ "dev": "vite dev",
51
+ "build": "vite build",
52
+ "deploy": "wrangler deploy"
53
+ },
54
+ "dependencies": {
55
+ "@decocms/start": "^2.28.0",
56
+ "@decocms/apps": "^1.11.0",
57
+ "@tanstack/react-start": "^1.166.0",
58
+ "react": "^19.0.0",
59
+ "react-dom": "^19.0.0"
60
+ },
61
+ "devDependencies": {
62
+ "vite": "^6.0.0",
63
+ "wrangler": "^4.72.0"
64
+ }
65
+ }
66
+ ```
17
67
 
68
+ ### `vite.config.ts`
69
+
70
+ ```ts
71
+ import { defineConfig } from "vite";
72
+ import { cloudflare } from "@cloudflare/vite-plugin";
73
+ import { tanstackStart } from "@tanstack/react-start/plugin/vite";
74
+ import react from "@vitejs/plugin-react";
75
+ import decoVitePlugin from "@decocms/start/vite";
76
+
77
+ export default defineConfig({
78
+ plugins: [
79
+ cloudflare({ viteEnvironment: { name: "ssr" } }),
80
+ tanstackStart({ server: { entry: "server" } }),
81
+ react({ babel: { plugins: ["babel-plugin-react-compiler"] } }),
82
+ decoVitePlugin(),
83
+ ],
84
+ resolve: {
85
+ alias: { "~": "/src" },
86
+ deduplicate: ["react", "react-dom", "@decocms/start", "@decocms/apps"],
87
+ },
88
+ });
18
89
  ```
19
- @decocms/start ← Framework (this package)
20
- └─ @decocms/apps ← Commerce integrations (VTEX, Shopify)
21
- └─ site repo ← UI components, routes, styles
90
+
91
+ ### `wrangler.jsonc`
92
+
93
+ ```jsonc
94
+ {
95
+ "name": "my-store",
96
+ "main": "./src/worker-entry.ts",
97
+ "compatibility_date": "2026-02-14",
98
+ "compatibility_flags": [
99
+ "nodejs_compat",
100
+ "no_handle_cross_request_promise_resolution"
101
+ ],
102
+ "assets": { "directory": "./dist/client" }
103
+ }
104
+ ```
105
+
106
+ ### `src/setup.ts`
107
+
108
+ ```ts
109
+ import { createSiteSetup } from "@decocms/start/setup";
110
+ import { applySectionConventions } from "@decocms/start/cms";
111
+
112
+ import blocks from "./server/cms/blocks.gen";
113
+ import sectionsGen from "./server/cms/sections.gen";
114
+ import meta from "./server/cms/meta.gen.json";
115
+
116
+ createSiteSetup({
117
+ sections: import.meta.glob("./sections/**/*.tsx", { eager: true }),
118
+ blocks,
119
+ meta: () => meta,
120
+ productionOrigins: ["https://my-store.com"],
121
+ });
122
+
123
+ applySectionConventions(sectionsGen);
22
124
  ```
23
125
 
24
- ### Package Exports
25
-
26
- | Import | Purpose |
27
- |--------|---------|
28
- | `@decocms/start` | Barrel export |
29
- | `@decocms/start/cms` | Block loading, page resolution, section registry |
30
- | `@decocms/start/admin` | Admin protocol (meta, decofile, invoke, render, schema) |
31
- | `@decocms/start/hooks` | DecoPageRenderer, LiveControls, LazySection |
32
- | `@decocms/start/routes` | CMS route config, admin routes |
33
- | `@decocms/start/middleware` | Observability, deco state, liveness probe |
34
- | `@decocms/start/sdk/workerEntry` | Cloudflare Worker entry with edge caching |
35
- | `@decocms/start/sdk/cacheHeaders` | URL-to-profile cache detection |
36
- | `@decocms/start/sdk/cachedLoader` | In-flight dedup for loaders |
37
- | `@decocms/start/sdk/useScript` | Inline `<script>` with minification |
38
- | `@decocms/start/sdk/useDevice` | SSR-safe device detection |
39
- | `@decocms/start/sdk/analytics` | Analytics event types |
40
- | `@decocms/start/matchers/*` | Feature flag matchers (PostHog, built-ins) |
41
- | `@decocms/start/types` | Section, App, FnContext type definitions |
42
- | `@decocms/start/scripts/*` | Code generation (blocks, schema, invoke) |
43
-
44
- ### Worker Entry Request Flow
126
+ ### `src/worker-entry.ts`
127
+
128
+ ```ts
129
+ import "./setup"; // MUST be first
130
+
131
+ import { createDecoWorkerEntry } from "@decocms/start/sdk/workerEntry";
132
+ import {
133
+ handleMeta,
134
+ handleDecofile,
135
+ handleRender,
136
+ handleInvoke,
137
+ } from "@decocms/start/admin";
138
+ import serverEntry from "./server";
45
139
 
140
+ export default createDecoWorkerEntry(serverEntry, {
141
+ admin: { handleMeta, handleDecofile, handleRender, handleInvoke },
142
+ });
46
143
  ```
47
- Request → createDecoWorkerEntry()
48
- ├─ Admin routes (/live/_meta, /.decofile, /deco/render, /deco/invoke)
49
- ├─ Cache purge check
50
- ├─ Static asset bypass (/assets/*, favicon)
51
- ├─ Cloudflare edge cache (profile-based TTLs)
52
- └─ TanStack Start server entry
144
+
145
+ ### `src/routes/$.tsx`
146
+
147
+ ```tsx
148
+ import { createFileRoute } from "@tanstack/react-router";
149
+ import { cmsRouteConfig } from "@decocms/start/routes";
150
+
151
+ export const Route = createFileRoute("/$")(
152
+ cmsRouteConfig({ siteName: "my-store" }),
153
+ );
53
154
  ```
54
155
 
55
- ### Edge Cache Profiles
156
+ That is the entire skeleton. `npm install`, `npm run dev`, point `admin.deco.cx` at it, and you have a working CMS-driven site.
56
157
 
57
- | URL Pattern | Profile | Edge TTL |
58
- |-------------|---------|----------|
59
- | `/` | static | 1 day |
60
- | `*/p` | product | 5 min |
61
- | `/s`, `?q=` | search | 60s |
62
- | `/cart`, `/checkout` | private | none |
63
- | Everything else | listing | 2 min |
158
+ For commerce integrations (VTEX, Shopify) see [`@decocms/apps`](https://www.npmjs.com/package/@decocms/apps).
64
159
 
65
- ## Migrating from Fresh/Preact/Deno
160
+ ---
66
161
 
67
- `@decocms/start` includes an Agent Skill that handles migration for you. It works with Claude Code, Cursor, Codex, and other AI coding tools. Install the skill, open your Fresh storefront, and tell the AI to migrate:
162
+ ## Migrating from Fresh / Preact / Deno
163
+
164
+ `@decocms/start` ships an Agent Skill that handles the migration for you. It works with Claude Code, Cursor, Codex, and any tool that supports skills.
68
165
 
69
166
  ```bash
70
167
  npx skills add decocms/deco-start
71
168
  ```
72
169
 
73
- Then open your project in any supported tool and say:
170
+ Then, in your editor, point at your Fresh storefront and prompt:
74
171
 
75
172
  > migrate this project to TanStack Start
76
173
 
77
- The skill handles compatibility checking, import rewrites, config generation, section registry setup, and worker entry creation. It knows what `@decocms/start` supports and will flag anything that needs manual attention.
174
+ The skill runs the migration script, walks you through `MIGRATION_REPORT.md`, fixes typecheck/build errors interactively, and shows the diff before committing.
78
175
 
79
- ### Or run the script manually
176
+ ### Or run the script directly
80
177
 
81
178
  ```bash
82
- # From your Fresh site directory (nothing to install beforehand):
179
+ # from inside the v1 storefront directory
83
180
  npx -p @decocms/start deco-migrate
84
181
  ```
85
182
 
86
- **Options:**
183
+ The script runs seven phases (analyze → scaffold → transform → cleanup → report → verify → bootstrap), produces `MIGRATION_REPORT.md` with manual TODOs, and gets you to "compiles clean, builds clean".
184
+
185
+ Full migration playbook: [docs.deco.cx/v2/en/migration/overview](https://docs.deco.cx/v2/en/migration/overview).
87
186
 
88
- | Flag | Description |
89
- |------|-------------|
90
- | `--source <dir>` | Source directory (default: current directory) |
91
- | `--dry-run` | Preview changes without writing files |
92
- | `--verbose` | Show detailed output |
93
- | `--help`, `-h` | Show help message |
187
+ ---
94
188
 
95
- The script runs 7 phases automatically:
189
+ ## Documentation
96
190
 
97
- 1. **Analyze** scan source, detect Preact/Fresh/Deco patterns
98
- 2. **Scaffold** — generate `vite.config.ts`, `wrangler.jsonc`, routes, `setup.ts`, worker entry
99
- 3. **Transform** — rewrite imports (70+ rules), JSX attrs, Fresh APIs, Deno-isms, Tailwind v3→v4
100
- 4. **Cleanup** — delete `islands/`, old routes, `deno.json`, move `static/` → `public/`
101
- 5. **Report** — generate `MIGRATION_REPORT.md` with manual review items
102
- 6. **Verify** — 18+ smoke tests (zero old imports, scaffolded files exist)
103
- 7. **Bootstrap** — `npm install`, generate CMS blocks, generate routes
191
+ The full v2 docs live at **[docs.deco.cx/v2](https://docs.deco.cx/v2/en/getting-started/overview)**:
104
192
 
105
- Your existing `src/sections/`, `src/components/`, and `.deco/blocks/` work as-is. The script gets you to "builds clean with zero old imports" manual work starts at platform hooks (`useCart`) and runtime tuning.
193
+ - [Getting started](https://docs.deco.cx/v2/en/getting-started/overview)install paths, project structure, stack overview.
194
+ - [Concepts](https://docs.deco.cx/v2/en/concepts/sections) — sections, loaders, blocks, routes, deferred rendering.
195
+ - [Framework reference](https://docs.deco.cx/v2/en/framework/overview) — every export of `@decocms/start`, page by page.
196
+ - [Migration](https://docs.deco.cx/v2/en/migration/overview) — v1 → v2 playbook + script + skill.
197
+ - [Case studies](https://docs.deco.cx/v2/en/case-studies/overview) — three production stores end-to-end.
106
198
 
107
- ### Agent Skills
199
+ ---
108
200
 
109
- Skills live in [`.agents/skills/`](.agents/skills/) and provide deep context to AI coding tools:
201
+ ## Peer dependencies
110
202
 
111
- | Skill | What it covers |
112
- |-------|---------------|
113
- | `deco-to-tanstack-migration` | Full 12-phase migration playbook with 22 reference docs and 6 templates |
114
- | `deco-migrate-script` | How the automated `scripts/migrate.ts` works, how to extend it |
203
+ ```json
204
+ {
205
+ "@tanstack/react-start": ">=1.0.0",
206
+ "@tanstack/store": ">=0.7.0",
207
+ "@tanstack/react-query": ">=5.0.0",
208
+ "react": "^19.0.0",
209
+ "react-dom": "^19.0.0",
210
+ "vite": ">=6.0.0"
211
+ }
212
+ ```
115
213
 
116
- ## Peer Dependencies
214
+ OpenTelemetry is optional but recommended: `@microlabs/otel-cf-workers >=1.0.0-rc.0`, `@opentelemetry/api >=1.9.0`.
117
215
 
118
- - `@tanstack/react-start` >= 1.0.0
119
- - `@tanstack/store` >= 0.7.0
120
- - `react` ^19.0.0
121
- - `react-dom` ^19.0.0
216
+ ---
122
217
 
123
218
  ## Development
124
219
 
@@ -128,7 +223,11 @@ npm run lint # biome check
128
223
  npm run check # typecheck + lint + unused exports
129
224
  ```
130
225
 
131
- This is a library — no dev server. Consumer sites run their own `vite dev`.
226
+ This is a library — there is no dev server here. Consumer storefronts run their own `vite dev`.
227
+
228
+ Contributing? See `CLAUDE.md` for the architectural decisions, and `MIGRATION_TOOLING_PLAN.md` for the append-only history of the migration tooling.
229
+
230
+ ---
132
231
 
133
232
  ## License
134
233
 
@@ -0,0 +1,85 @@
1
+ # `deploy/` — central deploy registry
2
+
3
+ This directory is the single source of truth for **what gets deployed where**
4
+ across every storefront on the platform. It is consumed by the reusable GitHub
5
+ workflows under [`.github/workflows/`](../.github/workflows/) (`deploy.yml`,
6
+ `preview.yml`, `sync-secrets.yml`) and by the local `deco-wrangler` CLI.
7
+
8
+ ## Files
9
+
10
+ | File | Purpose |
11
+ |------|---------|
12
+ | `wrangler-template.jsonc` | Canonical wrangler config that every site inherits. Compatibility flags, worker-entry path, observability — everything that is the same for every site. |
13
+ | `sites/<repo-name>.jsonc` | Per-site overrides. Only the keys that genuinely vary per-site live here (`worker_name` always; `routes`, `kv_namespaces`, `analytics_engine_datasets`, `version_metadata` when used). |
14
+
15
+ The repository name (the part of `${{ github.repository }}` after the `/`) is
16
+ the lookup key. `als-tanstack` deploys via `sites/als-tanstack.jsonc`. There is
17
+ no other way to identify a site.
18
+
19
+ ## Trust model
20
+
21
+ - Customer caller workflows pass **no inputs** to the central reusable workflow.
22
+ - The central workflow derives the site name from `${{ github.repository }}`
23
+ (set by GitHub, untamperable by user code) and looks up
24
+ `sites/<repo-name>.jsonc` from this registry.
25
+ - A customer cannot misroute a deploy onto another customer's worker because
26
+ they can't write to `decocms/deco-start`.
27
+
28
+ `deploy/**` is CODEOWNERS-protected. Only the platform team can change site
29
+ manifests or the template.
30
+
31
+ ## How wrangler.jsonc is generated
32
+
33
+ At deploy time, the central workflow runs
34
+ [`scripts/deploy/build-wrangler-config.mjs`](../scripts/deploy/build-wrangler-config.mjs),
35
+ which:
36
+
37
+ 1. Loads `deploy/wrangler-template.jsonc` (canonical defaults).
38
+ 2. Loads `deploy/sites/<site>.jsonc` (per-site overrides).
39
+ 3. Deep-merges: site overrides win. `worker_name` becomes wrangler's `name`.
40
+ Arrays are replaced, not concatenated.
41
+ 4. Writes the result to `./wrangler.jsonc` in the caller checkout.
42
+
43
+ `account_id` is never written to JSON — wrangler reads it from
44
+ `CLOUDFLARE_ACCOUNT_ID` (env var in CI; `wrangler login` locally).
45
+
46
+ ## Adding a new site
47
+
48
+ 1. Open a PR to this repo adding `deploy/sites/<new-repo>.jsonc`:
49
+ ```jsonc
50
+ {
51
+ "worker_name": "<new-repo>" // can differ from repo name if needed
52
+ }
53
+ ```
54
+ 2. After merge, the next `v2.x.y` semantic-release publish auto-moves the
55
+ `@v2` major tag (the major-tag advance step lives inline in
56
+ [`.github/workflows/release.yml`](../.github/workflows/release.yml)).
57
+ 3. In the new repo, add the four caller workflows from
58
+ [`.github/workflows/`](../.github/workflows/) and set the org-level
59
+ `CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID` GitHub secrets.
60
+ 4. Push to `main` and verify the deploy lands on the right worker.
61
+
62
+ ## Per-site override schema
63
+
64
+ ```jsonc
65
+ {
66
+ "worker_name": "string (required, immutable)",
67
+ "routes": [ // optional
68
+ { "pattern": "www.example.com/*", "zone_name": "decocdn.com" }
69
+ ],
70
+ "kv_namespaces": [ // optional
71
+ { "binding": "SITES_KV", "id": "<cf-kv-id>" }
72
+ ],
73
+ "analytics_engine_datasets": [ // optional
74
+ { "binding": "DECO_METRICS", "dataset": "deco_metrics_<site>" }
75
+ ],
76
+ "version_metadata": { // optional
77
+ "binding": "CF_VERSION_METADATA"
78
+ }
79
+ }
80
+ ```
81
+
82
+ All other wrangler keys (compatibility flags, `main`, observability, etc.) come
83
+ from the template — do not duplicate them per-site. If a per-site override is
84
+ genuinely needed for one of those keys, add it to the schema and document the
85
+ reason here.
@@ -0,0 +1,7 @@
1
+ // Per-site overrides for `als-tanstack` (immutable repo->worker binding).
2
+ // Renaming `worker_name` is a breaking change: it points the next deploy at a
3
+ // different Cloudflare worker. Only the platform team can change this file
4
+ // (CODEOWNERS-protected).
5
+ {
6
+ "worker_name": "als-tanstack"
7
+ }
@@ -0,0 +1,4 @@
1
+ // Per-site overrides for `americanas-tanstack`.
2
+ {
3
+ "worker_name": "americanas-tanstack"
4
+ }
@@ -0,0 +1,4 @@
1
+ // Per-site overrides for `baggagio-tanstack`.
2
+ {
3
+ "worker_name": "baggagio-tanstack"
4
+ }
@@ -0,0 +1,11 @@
1
+ // Per-site overrides for `casaevideo-storefront`.
2
+ //
3
+ // NOTE: `kv_namespaces[].id` is shared with `lebiscuit-tanstack`. Verify this
4
+ // is intentional before promoting this registry to v1. R4 in the central-deploy
5
+ // plan adds a CI check that flags shared KV ids as a copy-paste smell.
6
+ {
7
+ "worker_name": "casaevideo-tanstack",
8
+ "kv_namespaces": [
9
+ { "binding": "SITES_KV", "id": "ad0b74fc4d9341c9af9149c4ab85132f" }
10
+ ]
11
+ }