@decocms/start 2.30.1 → 4.0.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/worker-cloudflare.md +31 -107
- package/.agents/skills/deco-to-tanstack-migration/templates/package-json.md +2 -5
- package/.cursor/rules/migration-tooling-policy.mdc +32 -35
- package/CODEOWNERS +4 -14
- package/MIGRATION_TOOLING_PLAN.md +11 -10
- package/package.json +2 -3
- package/scripts/migrate/phase-scaffold.ts +9 -12
- package/scripts/migrate/phase-verify.ts +3 -5
- package/scripts/migrate/templates/package-json.ts +6 -9
- package/.github/workflows/deploy.yml +0 -122
- package/.github/workflows/preview.yml +0 -142
- package/.github/workflows/sync-secrets.yml +0 -180
- package/deploy/README.md +0 -111
- package/deploy/sites/als-tanstack.jsonc +0 -7
- package/deploy/sites/americanas-tanstack.jsonc +0 -4
- package/deploy/sites/baggagio-tanstack.jsonc +0 -4
- package/deploy/sites/casaevideo-storefront.jsonc +0 -11
- package/deploy/sites/lebiscuit-tanstack.jsonc +0 -19
- package/deploy/sites/miess-01-tanstack.jsonc +0 -8
- package/deploy/wrangler-template.jsonc +0 -28
- package/scripts/deploy/build-wrangler-config.mjs +0 -49
- package/scripts/deploy/jsonc.mjs +0 -76
- package/scripts/deploy/resolve-site.mjs +0 -58
- package/scripts/deploy/site-registry.mjs +0 -142
- package/scripts/deploy/wrangler-wrapper.mjs +0 -126
- package/scripts/migrate/templates/github-workflows.ts +0 -98
|
@@ -315,7 +315,7 @@ See: `references/search.md`
|
|
|
315
315
|
5. **`tsconfig.json` mirrors `vite.config.ts`** -- only `"~/*": ["./src/*"]` in paths
|
|
316
316
|
6. **Signals don't auto-subscribe in React** -- reading `signal.value` in render creates NO subscription; use `useStore(signal.store)` from `@tanstack/react-store`
|
|
317
317
|
7. **Commerce loaders need request context** -- `resolve.ts` must pass URL/path to PLP/PDP loaders
|
|
318
|
-
8. **`wrangler.jsonc` main must be a custom worker-entry** -- TanStack Start ignores `export default` in `server.ts`. The `main` field lives in the
|
|
318
|
+
8. **`wrangler.jsonc` main must be a custom worker-entry** -- TanStack Start ignores `export default` in `server.ts`. The `main` field lives in the site's per-site `wrangler.jsonc`.
|
|
319
319
|
9. **Copy components faithfully, never rewrite** -- `cp` the original, then only change mechanical things (class→className, imports). NEVER regenerate or "improve" — AI-rewritten components are the #1 source of visual regressions
|
|
320
320
|
10. **Tailwind v4 logical property hazard** -- mixed `px-*` + `pl-*/pr-*` on the same element breaks the cascade
|
|
321
321
|
11. **oklch CSS variables need triplets, not hex** -- `oklch(var(--x))` must store variables as oklch triplets
|
|
@@ -37,7 +37,7 @@ TanStack Start's Cloudflare adapter **completely ignores** the `export default`
|
|
|
37
37
|
|
|
38
38
|
**Diagnosis**: Search the built `dist/server/worker-entry-*.js` bundle for your custom code (e.g., `X-Cache`, `caches.open`, `_cache/purge`). If absent, TanStack stripped it.
|
|
39
39
|
|
|
40
|
-
**Fix**: Create a **separate** `src/worker-entry.ts` file that wraps TanStack Start's built handler. Wrangler is told to use this file via `main: "./src/worker-entry.ts"` in the
|
|
40
|
+
**Fix**: Create a **separate** `src/worker-entry.ts` file that wraps TanStack Start's built handler. Wrangler is told to use this file via `main: "./src/worker-entry.ts"` in the site's `wrangler.jsonc`.
|
|
41
41
|
|
|
42
42
|
```typescript
|
|
43
43
|
// src/worker-entry.ts
|
|
@@ -58,9 +58,10 @@ export default createDecoWorkerEntry(serverEntry, {
|
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
The `main` field is set centrally so a future migration of the entry path
|
|
61
|
-
applies to every site at once (single PR to the template).
|
|
62
|
-
override
|
|
63
|
-
|
|
61
|
+
applies to every site at once (single PR to the template). There is no
|
|
62
|
+
per-site override file; if a single site truly needs a different
|
|
63
|
+
entry path, change the template (and accept that all sites get it) or add a
|
|
64
|
+
substitution token like `$WORKER_ENTRY_PATH` and feed it from a per-site env.
|
|
64
65
|
|
|
65
66
|
This ensures admin route interception AND edge caching survive the build because they're in the Worker's own fetch handler, outside of TanStack's build pipeline.
|
|
66
67
|
|
|
@@ -140,7 +141,7 @@ After deploying a new build to Cloudflare Workers, the edge cache may still serv
|
|
|
140
141
|
`npx wrangler secret put` manually per-site — the central workflow keeps
|
|
141
142
|
GitHub and Cloudflare in sync.
|
|
142
143
|
2. Call the purge endpoint: `POST /_cache/purge` with `Authorization: Bearer <token>` and body `{"paths":["/"]}`
|
|
143
|
-
3.
|
|
144
|
+
3. Currently this lives in each storefront's per-site `deploy.yml` (D6 centralization was reverted; D6.3 Workers Builds replacement is in flight).
|
|
144
145
|
|
|
145
146
|
|
|
146
147
|
## 44. Runtime Module Import Kills Lazy-Loaded Sections
|
|
@@ -211,111 +212,34 @@ Or in project `.npmrc` with an env var (for CI):
|
|
|
211
212
|
**Tradeoff with `github:` syntax**: No semver resolution — `npm update` is meaningless. Pin to a tag for stability: `github:decocms/deco-start#v0.14.2`. Without a tag, you get HEAD of the default branch.
|
|
212
213
|
|
|
213
214
|
|
|
214
|
-
## 46.
|
|
215
|
+
## 46. Deploy / Wrangler Config (interim, D6.3 in flight)
|
|
215
216
|
|
|
216
|
-
**
|
|
217
|
+
**Status (2026-05-07)**: D6.2's centralized App-mediated dispatch was
|
|
218
|
+
**reverted** in favour of Cloudflare Workers Builds owning the deploy
|
|
219
|
+
pipeline per-worker. The Workers Builds onboarding plan is being
|
|
220
|
+
designed in a follow-up PR. Until it lands, this section describes the
|
|
221
|
+
**interim state**: each storefront retains its own per-site inline
|
|
222
|
+
`deploy.yml` workflow (the original pre-D6 setup), with its own
|
|
223
|
+
`CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` repo secrets.
|
|
217
224
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
`decocms/deco-start/.github/workflows/{deploy,preview,sync-secrets,regen-blocks}.yml@v2`.
|
|
221
|
-
The canonical `wrangler.jsonc` lives at
|
|
222
|
-
`decocms/deco-start/deploy/wrangler-template.jsonc`; per-site overrides live at
|
|
223
|
-
`decocms/deco-start/deploy/sites/<repo-name>.jsonc`. The two are deep-merged at
|
|
224
|
-
deploy time and written to a generated `wrangler.jsonc` in the runner; site
|
|
225
|
-
repos gitignore the file.
|
|
225
|
+
Site repos **do** commit a per-site `wrangler.jsonc` during the interim
|
|
226
|
+
period. The `deco-wrangler` CLI no longer ships from `@decocms/start`.
|
|
226
227
|
|
|
227
|
-
### What
|
|
228
|
+
### What changes when Workers Builds onboarding ships
|
|
228
229
|
|
|
229
|
-
|
|
230
|
+
When the D6.3 replacement lands, expect:
|
|
230
231
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
uses: decocms/deco-start/.github/workflows/deploy.yml@v2
|
|
242
|
-
secrets: inherit
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
(Plus equivalent `preview.yml`, `regen-blocks.yml`, `sync-secrets.yml` —
|
|
246
|
-
see `scripts/migrate/templates/github-workflows.ts` for the canonical text.)
|
|
247
|
-
The migration script generates these for new sites; the same stubs are
|
|
248
|
-
hand-applied to existing sites.
|
|
249
|
-
|
|
250
|
-
### What goes in `decocms/deco-start/deploy/sites/<repo>.jsonc`
|
|
251
|
-
|
|
252
|
-
The minimum:
|
|
253
|
-
|
|
254
|
-
```jsonc
|
|
255
|
-
{
|
|
256
|
-
"worker_name": "<repo-name>"
|
|
257
|
-
}
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
Plus optional `routes`, `kv_namespaces`, `analytics_engine_datasets`,
|
|
261
|
-
`version_metadata` for the few sites that need them. Adding a new site is a
|
|
262
|
-
PR to deco-start, not a change to the site repo.
|
|
263
|
-
|
|
264
|
-
### Local dev
|
|
265
|
-
|
|
266
|
-
Site repos add three package.json hooks so vite picks up the generated
|
|
267
|
-
`wrangler.jsonc`:
|
|
268
|
-
|
|
269
|
-
```jsonc
|
|
270
|
-
"scripts": {
|
|
271
|
-
"gen:wrangler": "deco-wrangler gen",
|
|
272
|
-
"predev": "deco-wrangler gen",
|
|
273
|
-
"prebuild": "deco-wrangler gen",
|
|
274
|
-
"types": "deco-wrangler types",
|
|
275
|
-
"deploy": "echo 'Production deploys are managed by .github/workflows/deploy.yml on push to main. For an emergency manual deploy run: npx deco-wrangler deploy'; exit 1"
|
|
276
|
-
}
|
|
277
|
-
```
|
|
232
|
+
- Per-storefront CF Builds connection (one dashboard click per worker).
|
|
233
|
+
- Per-site `.github/workflows/deploy.yml` removed; CF Builds takes over
|
|
234
|
+
on push.
|
|
235
|
+
- `wrangler.jsonc` continues to live in the site repo, but a `deco-build`
|
|
236
|
+
CLI in `@decocms/start` regenerates the bindings (KV, R2, etc.) from a
|
|
237
|
+
central template at build time so customers can't add bindings to
|
|
238
|
+
other tenants' resources.
|
|
239
|
+
- `name` field in `wrangler.jsonc` is enforced by CF (verified against
|
|
240
|
+
`baggagio-tanstack` 2026-05-07 — a malicious `name` value is ignored
|
|
241
|
+
and CF auto-opens a PR to fix it).
|
|
278
242
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
### Trust model
|
|
284
|
-
|
|
285
|
-
- The central workflow ignores all caller `inputs:` for site identity.
|
|
286
|
-
- Site name is derived from `${{ github.repository }}` (set by GitHub,
|
|
287
|
-
untamperable by user code) and looked up in `deploy/sites/<repo>.jsonc`.
|
|
288
|
-
- A customer cannot misroute their deploy onto another site's worker
|
|
289
|
-
because they can't write to `decocms/deco-start` (CODEOWNERS-protected).
|
|
290
|
-
- `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` live ONLY in
|
|
291
|
-
`decocms/deco-start`'s `production` GitHub Environment. The reusable
|
|
292
|
-
workflow declares `environment: production`, which makes
|
|
293
|
-
`${{ secrets.CLOUDFLARE_* }}` resolve from the called repo's environment
|
|
294
|
-
instead of the caller's repo secrets. Storefront repos never hold the
|
|
295
|
-
Cloudflare credential.
|
|
296
|
-
- Per-site runtime secrets in storefront repos are prefixed `SECRET_*` and
|
|
297
|
-
flow into the worker via the central `sync-secrets.yml`, which inherits
|
|
298
|
-
them via `secrets: inherit`, validates names, and `wrangler secret put`s
|
|
299
|
-
them under their unprefixed name.
|
|
300
|
-
|
|
301
|
-
### Common mistakes (do not do these)
|
|
302
|
-
|
|
303
|
-
- **Committing `wrangler.jsonc` to a site repo.** Generated only;
|
|
304
|
-
always gitignored. If you see it tracked, the site missed migration.
|
|
305
|
-
- **Adding a site-local `deploy.yml` step** (e.g. cache purge after deploy).
|
|
306
|
-
Add it to `deco-start/.github/workflows/deploy.yml` instead so every site
|
|
307
|
-
picks it up at once.
|
|
308
|
-
- **Hard-coding `account_id` in a site's wrangler config.** It comes from
|
|
309
|
-
`CLOUDFLARE_ACCOUNT_ID` (in `decocms/deco-start`'s `production`
|
|
310
|
-
Environment in CI; `wrangler login` locally). Removing it from JSON is
|
|
311
|
-
the one-way protection against accidentally deploying to the wrong
|
|
312
|
-
account.
|
|
313
|
-
- **Adding `CLOUDFLARE_*` to a storefront repo's secrets.** They belong
|
|
314
|
-
in `decocms/deco-start` only, in the `production` environment. The
|
|
315
|
-
central workflow's `environment:` binding overrides anything passed
|
|
316
|
-
from the caller, so an accidental copy on a site is dead code.
|
|
317
|
-
- **Setting `worker_name` to anything other than the repo name** without
|
|
318
|
-
a strong reason. The 1:1 binding makes audit (and incident response)
|
|
319
|
-
trivial. Exceptions today: `casaevideo-storefront` -> `casaevideo-tanstack`
|
|
320
|
-
and `miess-01-tanstack` -> `miess-tanstack` (both for historical
|
|
321
|
-
Cloudflare worker names that predate the repo).
|
|
243
|
+
Until then, do NOT scaffold caller stubs that reference
|
|
244
|
+
`decocms/deco-start/.github/workflows/*.yml@vN` — those workflows are
|
|
245
|
+
gone.
|
|
@@ -12,11 +12,8 @@
|
|
|
12
12
|
"generate:blocks": "tsx node_modules/@decocms/start/scripts/generate-blocks.ts",
|
|
13
13
|
"generate:invoke": "tsx node_modules/@decocms/start/scripts/generate-invoke.ts",
|
|
14
14
|
"generate:schema": "tsx node_modules/@decocms/start/scripts/generate-schema.ts --site storefront",
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"prebuild": "deco-wrangler gen",
|
|
18
|
-
"types": "deco-wrangler types",
|
|
19
|
-
"deploy": "echo 'Production deploys are managed by .github/workflows/deploy.yml on push to main. See D6.'; exit 1"
|
|
15
|
+
"deploy": "wrangler deploy",
|
|
16
|
+
"types": "wrangler types"
|
|
20
17
|
},
|
|
21
18
|
"dependencies": {
|
|
22
19
|
"@decocms/apps": "^0.20.1",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Constitutional decisions for the deco-start migration-tooling effort (D1–D6, priorities, process). Always loaded.
|
|
2
|
+
description: Constitutional decisions for the deco-start migration-tooling effort (D1–D5 + D6.3 revert, priorities, process). Always loaded.
|
|
3
3
|
alwaysApply: true
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -14,7 +14,7 @@ summary; always defer to the plan when they conflict.
|
|
|
14
14
|
1. **Design for 100 sites, not 3.** When the surface of an abstraction is
|
|
15
15
|
well-understood, ship it. Don't defer waiting for "more signal" — design
|
|
16
16
|
for the projection. When the surface is *not* understood, *decide*
|
|
17
|
-
explicitly (write a D-record like D1–
|
|
17
|
+
explicitly (write a D-record like D1–D5), don't ship fast.
|
|
18
18
|
2. **Strict over flexible.** No support layers, no soft drift, no
|
|
19
19
|
fork-runtime adapters. Every fork divergence becomes either a PR back to
|
|
20
20
|
canonical or visible local debt.
|
|
@@ -76,38 +76,36 @@ Failure modes get documented in skills, not encoded as escape hatches.
|
|
|
76
76
|
**Agent behavior**: when a migration goes sideways, propose deletion +
|
|
77
77
|
re-run, not in-place repair. Add the failure mode to the skill.
|
|
78
78
|
|
|
79
|
-
### D6 —
|
|
80
|
-
|
|
81
|
-
`decocms/deco-start/.github/workflows/{deploy,preview,sync-secrets
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
to the
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
`
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
accidental copy on a site is dead code at best and a credential leak
|
|
110
|
-
risk at worst.
|
|
79
|
+
### D6.3 — Revert D6/D6.1/D6.2; Cloudflare Workers Builds owns deploy (signed off 2026-05-07)
|
|
80
|
+
The whole D6 family — centralized GitHub Actions reusable workflows under
|
|
81
|
+
`decocms/deco-start/.github/workflows/{deploy,preview,sync-secrets}.yml`,
|
|
82
|
+
the `decocms-deployer` GitHub App, the per-storefront `<site_name>-secrets`
|
|
83
|
+
GitHub Environments, the central `deploy/wrangler-template.jsonc`, the
|
|
84
|
+
`deco-wrangler` CLI, the `deploy/sites/<repo>.jsonc` registry, and the
|
|
85
|
+
per-site caller stubs — has been **reverted**.
|
|
86
|
+
|
|
87
|
+
Why: GitHub Free orgs do not propagate org-level secrets to private repos,
|
|
88
|
+
which forced the App private key to be distributed to every storefront repo
|
|
89
|
+
individually. That key gives the holder the ability to mint installation
|
|
90
|
+
tokens for any installed repo, concentrating blast radius on a credential
|
|
91
|
+
that had to be rotated across N storefronts. The trust model couldn't pay
|
|
92
|
+
for itself at our scale.
|
|
93
|
+
|
|
94
|
+
**Replacement direction (to be elaborated in a follow-up D-record once
|
|
95
|
+
shipped):** Cloudflare Workers Builds owns deploy/preview pipelines per-worker.
|
|
96
|
+
The dashboard repo<->worker connection is the source of truth — verified
|
|
97
|
+
empirically against `baggagio-tanstack` 2026-05-07 that a malicious
|
|
98
|
+
`wrangler.jsonc` `name` field is ignored, the deploy lands on the connected
|
|
99
|
+
worker, and CF auto-opens a PR to fix the config. Per-storefront wiring is
|
|
100
|
+
one CF dashboard click per worker.
|
|
101
|
+
|
|
102
|
+
**Agent behavior — current interim period**: while D6.3's replacement is
|
|
103
|
+
being designed, do **not** scaffold `decocms/deco-start@vN`-calling caller
|
|
104
|
+
stubs in storefronts and do **not** depend on a `deco-wrangler` CLI from
|
|
105
|
+
`@decocms/start` (it no longer exists). Storefronts retain their existing
|
|
106
|
+
per-site inline `deploy.yml` (with their own `CLOUDFLARE_*` secrets) until
|
|
107
|
+
Workers Builds onboarding lands. Site repos do still commit `wrangler.jsonc`
|
|
108
|
+
during this interim — `deco-wrangler gen` is gone.
|
|
111
109
|
|
|
112
110
|
## Priority order
|
|
113
111
|
|
|
@@ -142,4 +140,3 @@ order — but only if explicitly identified as urgent.
|
|
|
142
140
|
- When reading a section of the plan or a skill, prefer the plan or skill
|
|
143
141
|
over your memory of past conversations.
|
|
144
142
|
- Do not run `deco-deploy` or any deploy command from agent flows.
|
|
145
|
-
- Do not commit `wrangler.jsonc` to a site repo. See **D6** above.
|
package/CODEOWNERS
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
# CODEOWNERS for decocms/deco-start
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
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
|
|
3
|
+
# Workflows under `.github/workflows/` decide how this package is built,
|
|
4
|
+
# released, and what blocks regeneration sites can trigger. Only the
|
|
5
|
+
# platform team approves changes.
|
|
6
|
+
.github/workflows/ @vibe-dex
|
|
@@ -118,7 +118,9 @@ this plan.
|
|
|
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
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. |
|
|
121
|
-
| 2026-05-07 | **D6.1 — Cloudflare credentials never leave `deco-start`** | Same-day refinement of D6 after the first central deploy on `baggagio-tanstack` failed with `Secret CLOUDFLARE_API_TOKEN is required, but not provided while calling`. The original D6 design used `secrets: inherit` from the storefront stub and required `CLOUDFLARE_*` to live in the `deco-sites` org, which broke the principle that *the only secrets a storefront repo holds are the secrets that go into wrangler secrets, not the ones used to deploy*.
|
|
121
|
+
| 2026-05-07 | **D6.1 — Cloudflare credentials never leave `deco-start`** | Same-day refinement of D6 after the first central deploy on `baggagio-tanstack` failed with `Secret CLOUDFLARE_API_TOKEN is required, but not provided while calling`. The original D6 design used `secrets: inherit` from the storefront stub and required `CLOUDFLARE_*` to live in the `deco-sites` org, which broke the principle that *the only secrets a storefront repo holds are the secrets that go into wrangler secrets, not the ones used to deploy*. First-pass refinement: the central `deploy.yml` / `preview.yml` / `sync-secrets.yml` jobs declared `environment: production` to try to make `${{ secrets.CLOUDFLARE_* }}` resolve from `decocms/deco-start`'s `production` Environment. **Found broken empirically on 2026-05-07** — the deployment registers in the *caller* repo, not the called workflow's repo, so the environment lookup uses the caller's `production` env (auto-created with no secrets). Superseded by D6.2 the same evening. |
|
|
122
|
+
| 2026-05-07 | **D6.2 — App-mediated dispatch + no per-site registry (supersedes D6 + D6.1)** | After D6.1's `environment:` mechanism was empirically shown not to work cross-repo, the architecture pivoted: a `decocms-deployer` GitHub App is installed on `decocms/deco-start` (`actions:write`) and on each storefront repo (`contents:read`, optionally `pull-requests:write`). The storefront caller stub mints a short-lived App-installation token and calls `gh workflow run deploy.yml --repo decocms/deco-start --ref v3 -f site_owner=… -f site_name=…`. The central workflow runs in `decocms/deco-start`'s context, so `CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID` are ordinary repo secrets. For runtime `SECRET_*` values, each storefront has a `<site_name>-secrets` GitHub Environment in `decocms/deco-start` (S1 design); `sync-secrets.yml` binds to that environment and pushes to `wrangler secret put`. The per-site registry under `deploy/sites/<repo>.jsonc` was dropped entirely (Pure C): worker name = repo basename by convention; the App being installed on the storefront repo is the deploy authorization gate; rare per-worker derived fields (like AE dataset name) use `$WORKER_*` substitution tokens in the template. Force-rollback is impossible for production deploys because the central workflow ignores caller-supplied `site_sha` and resolves the storefront's current default-branch HEAD itself. See [`deploy/README.md`](./deploy/README.md) for the full trust model. **Operational migrations required by Pure C:** `miess-01-tanstack` repo's worker shifts from `miess-tanstack` to `miess-01-tanstack` (CF-side cutover); `lebiscuit-tanstack` AE dataset shifts from `deco_metrics_lebiscuit` to `deco_metrics_lebiscuit_tanstack` (orphans old data). |
|
|
123
|
+
| 2026-05-07 | **D6.3 — Revert D6/D6.1/D6.2; deploys move to Cloudflare Workers Builds** | The whole D6 family (centralized GitHub Actions reusable workflows + `decocms-deployer` GitHub App + per-storefront GitHub Environments + central `deploy/wrangler-template.jsonc` + `deco-wrangler` CLI + per-site caller stubs) is being **reverted**. Trigger: GitHub Free orgs do not propagate org-level secrets to private repos, which forced the App private key to live as a per-storefront repo secret in every storefront — that key gives the holder the ability to mint installation tokens that can trigger workflows on `decocms/deco-start`, which in turn have the only Cloudflare credentials in the system. Per-repo distribution + rotation of that key across N customer storefronts didn't scale and concentrated blast radius on one credential. **Replacement (chosen, to be detailed in a follow-up D-record once shipped):** [Cloudflare Workers Builds](https://developers.cloudflare.com/workers/ci-cd/builds/) owns the deploy/preview pipelines per-worker. Verified empirically on `baggagio-tanstack` 2026-05-07: a malicious `wrangler.jsonc` `name` field pointing at a different worker (`americanas-tanstack`) is **ignored** by CF Builds — the deploy lands on the connected worker (`baggagio-tanstack`), CF surfaces a warning banner in the dashboard, and CF auto-opens a PR to fix the config (deco-sites/baggagio-tanstack#34). The dashboard repo<->worker connection is the source of truth; the in-repo config is treated as a secondary input. Per-storefront wiring (one CF dashboard click per worker) is acceptable at our scale; revisit when CF's [git-integration enable API](https://github.com/cloudflare/workers-sdk/issues/12058) lands. The `deco-build` CLI (regenerates `wrangler.jsonc` bindings from a central template) and runtime-secrets management remain to be designed in a separate PR. |
|
|
122
124
|
|
|
123
125
|
The full text of the constitutional rule (loaded into every agent
|
|
124
126
|
session for this repo) lives at
|
|
@@ -1677,15 +1679,14 @@ props. One broken section never takes the page down.
|
|
|
1677
1679
|
`regen-blocks.yml` and its `wrangler.jsonc` lacked `account_id`,
|
|
1678
1680
|
lebiscuit's preview workflow swallowed `wrangler` exit codes, and
|
|
1679
1681
|
casaevideo's `loadtest:tail` referenced a worker name that didn't
|
|
1680
|
-
match its `wrangler.jsonc`. **
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
central registry on demand for local dev and CI alike.
|
|
1682
|
+
match its `wrangler.jsonc`. **Status 2026-05-07: D6 → D6.1 → D6.2
|
|
1683
|
+
reverted via D6.3** in favour of Cloudflare Workers Builds owning
|
|
1684
|
+
the deploy/preview pipelines per-worker. The replacement is being
|
|
1685
|
+
designed in a follow-up; until it ships, the existing per-site
|
|
1686
|
+
inline `deploy.yml` workflows in each storefront remain the
|
|
1687
|
+
operational deploy path (i.e. the original drift problem comes back
|
|
1688
|
+
on the surface for now, but with full single-credential trust
|
|
1689
|
+
isolation per storefront — no shared credentials).
|
|
1689
1690
|
|
|
1690
1691
|
#### Counter-evidence the user-rule asks for
|
|
1691
1692
|
|
package/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/start",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deco framework for TanStack Start - CMS bridge, admin protocol, hooks, schema generation",
|
|
6
6
|
"main": "./src/index.ts",
|
|
7
7
|
"bin": {
|
|
8
8
|
"deco-migrate": "./scripts/migrate.ts",
|
|
9
9
|
"deco-post-cleanup": "./scripts/migrate-post-cleanup.ts",
|
|
10
|
-
"deco-htmx-analyze": "./scripts/htmx-analyze.ts"
|
|
11
|
-
"deco-wrangler": "./scripts/deploy/wrangler-wrapper.mjs"
|
|
10
|
+
"deco-htmx-analyze": "./scripts/htmx-analyze.ts"
|
|
12
11
|
},
|
|
13
12
|
"exports": {
|
|
14
13
|
".": "./src/index.ts",
|
|
@@ -5,7 +5,6 @@ import { log, logPhase } from "./types";
|
|
|
5
5
|
import { generatePackageJson } from "./templates/package-json";
|
|
6
6
|
import { generateTsconfig } from "./templates/tsconfig";
|
|
7
7
|
import { generateViteConfig } from "./templates/vite-config";
|
|
8
|
-
import { generateGithubWorkflows } from "./templates/github-workflows";
|
|
9
8
|
import { generateKnipConfig } from "./templates/knip-config";
|
|
10
9
|
import { generateRoutes } from "./templates/routes";
|
|
11
10
|
import { generateSetup } from "./templates/setup";
|
|
@@ -50,20 +49,21 @@ function writeMultiFile(ctx: MigrationContext, files: Record<string, string>) {
|
|
|
50
49
|
export function scaffold(ctx: MigrationContext): void {
|
|
51
50
|
logPhase("Scaffold");
|
|
52
51
|
|
|
53
|
-
// Root config files. wrangler.jsonc is
|
|
54
|
-
//
|
|
55
|
-
// deploy
|
|
56
|
-
//
|
|
57
|
-
// `deco-wrangler gen` and gitignored.
|
|
52
|
+
// Root config files. wrangler.jsonc is intentionally NOT generated by
|
|
53
|
+
// the migration script -- it's per-site, lives in the site repo, and
|
|
54
|
+
// is wired by hand on first deploy (D6.3 interim state until the
|
|
55
|
+
// Cloudflare Workers Builds onboarding lands).
|
|
58
56
|
writeFile(ctx, "package.json", generatePackageJson(ctx));
|
|
59
57
|
writeFile(ctx, "tsconfig.json", generateTsconfig());
|
|
60
58
|
writeFile(ctx, "vite.config.ts", generateViteConfig(ctx));
|
|
61
59
|
writeFile(ctx, "knip.config.ts", generateKnipConfig());
|
|
62
60
|
writeFile(ctx, ".gitignore", generateGitignore());
|
|
63
61
|
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
|
|
62
|
+
// Deploy / preview pipelines are owned by Cloudflare Workers Builds
|
|
63
|
+
// configured per-worker in the CF dashboard (D6.3). The migration
|
|
64
|
+
// does NOT scaffold deploy/preview/sync-secrets workflows in the site
|
|
65
|
+
// repo; the operator wires the repo<->worker connection in the CF
|
|
66
|
+
// dashboard once after the first push.
|
|
67
67
|
writeFile(ctx, ".prettierrc", JSON.stringify({
|
|
68
68
|
semi: true,
|
|
69
69
|
singleQuote: false,
|
|
@@ -216,9 +216,6 @@ dist/
|
|
|
216
216
|
# Cloudflare Workers
|
|
217
217
|
.wrangler/
|
|
218
218
|
.dev.vars
|
|
219
|
-
# Generated by \`deco-wrangler\` from @decocms/start's central registry.
|
|
220
|
-
# Source of truth lives in decocms/deco-start under deploy/sites/<repo>.jsonc.
|
|
221
|
-
wrangler.jsonc
|
|
222
219
|
|
|
223
220
|
# TanStack Router (auto-generated)
|
|
224
221
|
src/routeTree.gen.ts
|
|
@@ -13,12 +13,10 @@ const REQUIRED_FILES = [
|
|
|
13
13
|
"package.json",
|
|
14
14
|
"tsconfig.json",
|
|
15
15
|
"vite.config.ts",
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
".github/workflows/preview.yml",
|
|
16
|
+
// Deploy / preview / sync-secrets pipelines are owned by Cloudflare
|
|
17
|
+
// Workers Builds (D6.3) -- configured in the CF dashboard, not via
|
|
18
|
+
// GitHub workflow files in the site repo.
|
|
20
19
|
".github/workflows/regen-blocks.yml",
|
|
21
|
-
".github/workflows/sync-secrets.yml",
|
|
22
20
|
"knip.config.ts",
|
|
23
21
|
".prettierrc",
|
|
24
22
|
"src/server.ts",
|
|
@@ -109,15 +109,12 @@ export function generatePackageJson(ctx: MigrationContext): string {
|
|
|
109
109
|
build:
|
|
110
110
|
"npm run generate:blocks && npm run generate:sections && npm run generate:loaders && npm run generate:schema && npm run generate:invoke && tsr generate && vite build",
|
|
111
111
|
preview: "vite preview",
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
deploy:
|
|
119
|
-
"echo 'Production deploys are managed by .github/workflows/deploy.yml on push to main. For an emergency manual deploy run: npx deco-wrangler deploy'; exit 1",
|
|
120
|
-
types: "deco-wrangler types",
|
|
112
|
+
// Deploy is owned by Cloudflare Workers Builds (D6.3); the
|
|
113
|
+
// repo<->worker connection is configured per-worker in the CF
|
|
114
|
+
// dashboard. Local devs use plain `wrangler` against the
|
|
115
|
+
// committed wrangler.jsonc.
|
|
116
|
+
deploy: "wrangler deploy",
|
|
117
|
+
types: "wrangler types",
|
|
121
118
|
typecheck: "tsc --noEmit",
|
|
122
119
|
format: 'prettier --write "src/**/*.{ts,tsx}"',
|
|
123
120
|
"format:check": 'prettier --check "src/**/*.{ts,tsx}"',
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
name: deploy (central)
|
|
2
|
-
|
|
3
|
-
# Reusable workflow that drives `wrangler deploy` for any storefront repo
|
|
4
|
-
# registered under `deploy/sites/<repo-name>.jsonc`.
|
|
5
|
-
#
|
|
6
|
-
# Site identity (worker name, KV bindings, routes, ...) is derived ONLY from
|
|
7
|
-
# `${{ github.repository }}` and the registry checked in to deco-start. The
|
|
8
|
-
# caller cannot pass an `inputs:` block to override the worker name -- this is
|
|
9
|
-
# the trust boundary that prevents one site's commits from deploying on top of
|
|
10
|
-
# another site's worker.
|
|
11
|
-
#
|
|
12
|
-
# Caller usage (in customer repo, `.github/workflows/deploy.yml`):
|
|
13
|
-
#
|
|
14
|
-
# on:
|
|
15
|
-
# push:
|
|
16
|
-
# branches: [main]
|
|
17
|
-
# permissions:
|
|
18
|
-
# contents: write
|
|
19
|
-
# jobs:
|
|
20
|
-
# deploy:
|
|
21
|
-
# uses: decocms/deco-start/.github/workflows/deploy.yml@v2
|
|
22
|
-
# secrets: inherit
|
|
23
|
-
#
|
|
24
|
-
# `secrets: inherit` is intentionally tolerated but UNUSED by the deploy job:
|
|
25
|
-
# `CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID` resolve from the
|
|
26
|
-
# `production` environment in this repo (via `environment:` below), NOT from
|
|
27
|
-
# the caller. That keeps the Cloudflare credential off every storefront repo
|
|
28
|
-
# entirely. The same env binding is repeated in `preview.yml` and
|
|
29
|
-
# `sync-secrets.yml`.
|
|
30
|
-
|
|
31
|
-
on:
|
|
32
|
-
workflow_call: {}
|
|
33
|
-
|
|
34
|
-
permissions:
|
|
35
|
-
contents: write
|
|
36
|
-
|
|
37
|
-
concurrency:
|
|
38
|
-
group: deploy-${{ github.repository }}
|
|
39
|
-
cancel-in-progress: false
|
|
40
|
-
|
|
41
|
-
jobs:
|
|
42
|
-
deploy:
|
|
43
|
-
runs-on: ubuntu-latest
|
|
44
|
-
# Resolves `secrets.CLOUDFLARE_*` against the `production` environment in
|
|
45
|
-
# decocms/deco-start (NOT against the caller's repo secrets). Setup:
|
|
46
|
-
# decocms/deco-start > Settings > Environments > production >
|
|
47
|
-
# CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID.
|
|
48
|
-
environment: production
|
|
49
|
-
steps:
|
|
50
|
-
- name: Checkout caller repo
|
|
51
|
-
uses: actions/checkout@v4
|
|
52
|
-
|
|
53
|
-
- name: Resolve deco-start ref + site identity
|
|
54
|
-
id: meta
|
|
55
|
-
run: |
|
|
56
|
-
# github.workflow_ref looks like
|
|
57
|
-
# decocms/deco-start/.github/workflows/deploy.yml@refs/tags/v1
|
|
58
|
-
# or
|
|
59
|
-
# decocms/deco-start/.github/workflows/deploy.yml@refs/heads/main
|
|
60
|
-
WF_REF="${{ github.workflow_ref }}"
|
|
61
|
-
REF="${WF_REF##*@}"
|
|
62
|
-
REF="${REF#refs/tags/}"
|
|
63
|
-
REF="${REF#refs/heads/}"
|
|
64
|
-
echo "deco_start_ref=$REF" >> "$GITHUB_OUTPUT"
|
|
65
|
-
echo "site_name=${GITHUB_REPOSITORY#*/}" >> "$GITHUB_OUTPUT"
|
|
66
|
-
|
|
67
|
-
- name: Checkout deco-start registry at the same ref
|
|
68
|
-
uses: actions/checkout@v4
|
|
69
|
-
with:
|
|
70
|
-
repository: decocms/deco-start
|
|
71
|
-
ref: ${{ steps.meta.outputs.deco_start_ref }}
|
|
72
|
-
path: .deco-start
|
|
73
|
-
|
|
74
|
-
- uses: actions/setup-node@v4
|
|
75
|
-
with:
|
|
76
|
-
node-version: 22
|
|
77
|
-
|
|
78
|
-
- name: Generate lockfile if missing
|
|
79
|
-
if: hashFiles('package-lock.json') == ''
|
|
80
|
-
run: npm install --package-lock-only
|
|
81
|
-
|
|
82
|
-
- name: Restore npm cache
|
|
83
|
-
uses: actions/cache@v4
|
|
84
|
-
with:
|
|
85
|
-
path: ~/.npm
|
|
86
|
-
key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
|
|
87
|
-
restore-keys: npm-${{ runner.os }}-
|
|
88
|
-
|
|
89
|
-
- name: Sync lockfile and install
|
|
90
|
-
run: |
|
|
91
|
-
npm install --package-lock-only
|
|
92
|
-
if ! git diff --quiet package-lock.json 2>/dev/null; then
|
|
93
|
-
git config user.name "github-actions[bot]"
|
|
94
|
-
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
95
|
-
git add package-lock.json
|
|
96
|
-
git commit -m "chore: sync package-lock.json"
|
|
97
|
-
git push || echo "Lockfile push failed (remote ahead); proceeding with deploy"
|
|
98
|
-
fi
|
|
99
|
-
npm ci
|
|
100
|
-
|
|
101
|
-
- name: Build
|
|
102
|
-
run: npm run build
|
|
103
|
-
|
|
104
|
-
- name: Resolve site manifest
|
|
105
|
-
id: site
|
|
106
|
-
run: node .deco-start/scripts/deploy/resolve-site.mjs
|
|
107
|
-
env:
|
|
108
|
-
DECO_START_PATH: .deco-start
|
|
109
|
-
SITE_NAME: ${{ steps.meta.outputs.site_name }}
|
|
110
|
-
|
|
111
|
-
- name: Generate wrangler.jsonc
|
|
112
|
-
run: node .deco-start/scripts/deploy/build-wrangler-config.mjs
|
|
113
|
-
env:
|
|
114
|
-
DECO_START_PATH: .deco-start
|
|
115
|
-
SITE_NAME: ${{ steps.meta.outputs.site_name }}
|
|
116
|
-
OUTPUT_PATH: ./wrangler.jsonc
|
|
117
|
-
|
|
118
|
-
- name: Deploy to Cloudflare Workers
|
|
119
|
-
run: npx wrangler deploy --var BUILD_HASH:$(git rev-parse --short HEAD)
|
|
120
|
-
env:
|
|
121
|
-
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
122
|
-
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|