@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.
- package/.agents/skills/deco-to-tanstack-migration/SKILL.md +1 -1
- package/.agents/skills/deco-to-tanstack-migration/references/vtex-commerce.md +5 -1
- package/.agents/skills/deco-to-tanstack-migration/references/worker-cloudflare.md +107 -10
- package/.agents/skills/deco-to-tanstack-migration/templates/package-json.md +5 -1
- package/.cursor/rules/migration-tooling-policy.mdc +22 -2
- package/.github/workflows/deploy.yml +115 -0
- package/.github/workflows/preview.yml +143 -0
- package/.github/workflows/regen-blocks.yml +56 -0
- package/.github/workflows/release.yml +26 -0
- package/.github/workflows/sync-secrets.yml +173 -0
- package/CODEOWNERS +16 -0
- package/MIGRATION_TOOLING_PLAN.md +16 -4
- package/README.md +178 -79
- package/deploy/README.md +85 -0
- package/deploy/sites/als-tanstack.jsonc +7 -0
- package/deploy/sites/americanas-tanstack.jsonc +4 -0
- package/deploy/sites/baggagio-tanstack.jsonc +4 -0
- package/deploy/sites/casaevideo-storefront.jsonc +11 -0
- package/deploy/sites/lebiscuit-tanstack.jsonc +19 -0
- package/deploy/sites/miess-01-tanstack.jsonc +8 -0
- package/deploy/wrangler-template.jsonc +28 -0
- package/package.json +18 -15
- package/scripts/deploy/build-wrangler-config.mjs +49 -0
- package/scripts/deploy/jsonc.mjs +76 -0
- package/scripts/deploy/resolve-site.mjs +58 -0
- package/scripts/deploy/site-registry.mjs +142 -0
- package/scripts/deploy/wrangler-wrapper.mjs +126 -0
- package/scripts/migrate/phase-scaffold.ts +13 -3
- package/scripts/migrate/phase-verify.ts +6 -1
- package/scripts/migrate/templates/github-workflows.ts +98 -0
- package/scripts/migrate/templates/package-json.ts +9 -2
- package/src/cms/resolve.ts +81 -63
- package/src/cms/sectionLoaders.ts +11 -0
- package/src/index.ts +3 -0
- package/src/sdk/cachedLoader.ts +36 -13
- package/src/sdk/composite.test.ts +121 -0
- package/src/sdk/composite.ts +114 -0
- package/src/sdk/instrumentedFetch.ts +56 -0
- package/src/sdk/logger.test.ts +135 -0
- package/src/sdk/logger.ts +166 -0
- package/src/sdk/observability.ts +75 -0
- package/src/sdk/otel.test.ts +59 -0
- package/src/sdk/otel.ts +270 -29
- package/src/sdk/otelAdapters.test.ts +135 -0
- package/src/sdk/otelAdapters.ts +401 -0
- package/src/sdk/sampler.test.ts +127 -0
- package/src/sdk/sampler.ts +183 -0
- package/src/sdk/workerEntry.ts +541 -476
- 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
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
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
|
[](https://www.npmjs.com/package/@decocms/start)
|
|
4
4
|
[](https://github.com/decocms/deco-start/blob/main/LICENSE)
|
|
5
5
|
|
|
6
|
-
Framework layer for [
|
|
6
|
+
Framework layer for [deco.cx](https://deco.cx) storefronts on **TanStack Start + React 19 + Cloudflare Workers**.
|
|
7
7
|
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
###
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
160
|
+
---
|
|
66
161
|
|
|
67
|
-
|
|
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
|
|
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
|
|
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
|
|
176
|
+
### Or run the script directly
|
|
80
177
|
|
|
81
178
|
```bash
|
|
82
|
-
#
|
|
179
|
+
# from inside the v1 storefront directory
|
|
83
180
|
npx -p @decocms/start deco-migrate
|
|
84
181
|
```
|
|
85
182
|
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
189
|
+
## Documentation
|
|
96
190
|
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
|
|
199
|
+
---
|
|
108
200
|
|
|
109
|
-
|
|
201
|
+
## Peer dependencies
|
|
110
202
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
214
|
+
OpenTelemetry is optional but recommended: `@microlabs/otel-cf-workers >=1.0.0-rc.0`, `@opentelemetry/api >=1.9.0`.
|
|
117
215
|
|
|
118
|
-
|
|
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
|
|
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
|
|
package/deploy/README.md
ADDED
|
@@ -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,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
|
+
}
|