@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.
@@ -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 **central** `decocms/deco-start/deploy/wrangler-template.jsonc` (D6); site repos do not commit `wrangler.jsonc`.
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 **canonical wrangler template** at `decocms/deco-start/deploy/wrangler-template.jsonc` (D6) — sites do not configure this themselves.
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). If you ever need to
62
- override `main` for a single site, add it under `deploy/sites/<repo>.jsonc` —
63
- never to a per-site `wrangler.jsonc` (sites don't commit one; see D6).
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. The right place to automate this is the **central** `deco-start/.github/workflows/deploy.yml` (D6) so every site picks it up at once. Do not add site-local deploy.yml steps; site repos hold only ~5-line caller workflows.
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. Central Deploy / Wrangler Config (D6)
215
+ ## 46. Deploy / Wrangler Config (interim, D6.3 in flight)
215
216
 
216
- **Severity**: HIGH — site repos must NOT commit `wrangler.jsonc` or per-site deploy logic. Doing so reintroduces drift.
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
- Per [D6](../../../../.cursor/rules/migration-tooling-policy.mdc), all
219
- storefronts deploy via reusable workflows shipped from
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 goes in the site repo
228
+ ### What changes when Workers Builds onboarding ships
228
229
 
229
- Four ~5-line caller workflow stubs and nothing else:
230
+ When the D6.3 replacement lands, expect:
230
231
 
231
- ```yaml
232
- # .github/workflows/deploy.yml
233
- name: Deploy
234
- on:
235
- push:
236
- branches: [main]
237
- permissions:
238
- contents: write
239
- jobs:
240
- deploy:
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
- `deco-wrangler` is a `bin` shipped from `@decocms/start` that materializes the
280
- canonical config from the central registry, then either exits (`gen` mode) or
281
- execs the real `wrangler` with that config in cwd.
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
- "gen:wrangler": "deco-wrangler gen",
16
- "predev": "deco-wrangler gen",
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–D6), don't ship fast.
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 — Deploy / preview / secrets pipelines: centralize in `deco-start` (signed off 2026-05-07)
80
- All storefronts deploy via reusable workflows under
81
- `decocms/deco-start/.github/workflows/{deploy,preview,sync-secrets,regen-blocks}.yml@v2`.
82
- Per-site overrides live in [`deploy/sites/<repo>.jsonc`](../../deploy/) and
83
- are deep-merged on top of [`deploy/wrangler-template.jsonc`](../../deploy/wrangler-template.jsonc)
84
- at deploy time. **Customer repos do not commit `wrangler.jsonc`** it is
85
- generated locally by `deco-wrangler gen` (a `bin` shipped from
86
- `@decocms/start`) and gitignored. The repo→worker binding is the trust
87
- boundary: the central workflow ignores caller `inputs:` for identity and
88
- derives the site name from `${{ github.repository }}`. `deploy/**` is
89
- CODEOWNERS-protected.
90
-
91
- **Cloudflare credentials live in `decocms/deco-start` only**, in the
92
- `production` GitHub Environment. The reusable workflows declare
93
- `environment: production`, which makes `${{ secrets.CLOUDFLARE_* }}`
94
- resolve from the called repo (deco-start) instead of the caller. A
95
- storefront repo only ever holds its own `SECRET_*` runtime secrets
96
- (pushed to its worker by the central `sync-secrets.yml` via
97
- `secrets: inherit`). Two-tier secret model is the second half of the
98
- trust boundary even a fully compromised storefront repo has no path
99
- to the Cloudflare token.
100
-
101
- **Agent behavior**: when adding or migrating a site, do **not** generate a
102
- per-site `wrangler.jsonc` or per-site `deploy.yml` / `preview.yml` /
103
- `sync-secrets.yml` / `regen-blocks.yml`. The site repo gets the four
104
- ~5-line caller stubs only. Per-site config goes in a PR to
105
- `decocms/deco-start` adding `deploy/sites/<repo>.jsonc`. Do not commit
106
- `wrangler.jsonc` or any wrangler config to a site repo. Do **not** add
107
- `CLOUDFLARE_*` to a storefront repo's secrets the central workflow's
108
- `environment:` binding overrides anything passed from the caller, so an
109
- accidental copy on a site is dead code at best and a credential leak
110
- risk at worst.
79
+ ### D6.3Revert 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
- # `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
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*. Refinement: the central `deploy.yml` / `preview.yml` / `sync-secrets.yml` jobs declare `environment: production`, which makes `${{ secrets.CLOUDFLARE_* }}` resolve from the called repo (`decocms/deco-start`'s `production` GitHub Environment) instead of the caller's repo secrets. Storefronts now hold only their own `SECRET_*` runtime secrets; `sync-secrets` inherits those via `secrets: inherit` and pushes them as worker secrets. The trust boundary becomes two-tier: even a fully compromised storefront repo has no path to the Cloudflare credential, and even a compromised storefront cannot misroute *its own* deploy onto another site's worker because `worker_name` comes from the CODEOWNERS-protected registry, not caller input. See [`deploy/README.md`](./deploy/README.md) "Where Cloudflare credentials live" section. |
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`. **Resolved 2026-05-07 via D6:** all
1681
- workflows + wrangler config are centralized in
1682
- [`deco-start/.github/workflows/`](./.github/workflows/) and
1683
- [`deco-start/deploy/`](./deploy/). New-site onboarding is now: open a
1684
- PR adding `deploy/sites/<repo>.jsonc` to deco-start, then drop the
1685
- ~5-line caller workflows into the new site's repo. No `wrangler.jsonc`
1686
- is committed to the site repo `deco-wrangler gen`
1687
- (a `bin` shipped from `@decocms/start`) materializes it from the
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": "2.30.1",
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 INTENTIONALLY not generated --
54
- // per D6, the canonical wrangler config lives in decocms/deco-start under
55
- // deploy/wrangler-template.jsonc and per-site overrides under
56
- // deploy/sites/<repo>.jsonc; the file is materialized locally by
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
- // Caller workflow stubs that delegate to decocms/deco-start's reusable
65
- // workflows. The customer repo holds no deploy logic of its own.
66
- writeMultiFile(ctx, generateGithubWorkflows());
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
- // wrangler.jsonc is INTENTIONALLY absent -- D6: it lives in decocms/deco-start
17
- // under deploy/sites/<repo>.jsonc and is generated locally by `deco-wrangler gen`.
18
- ".github/workflows/deploy.yml",
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
- // wrangler.jsonc is generated by `deco-wrangler gen` from the central
113
- // registry in @decocms/start (D6). Predev/prebuild ensure it is up to
114
- // date before vite runs the @cloudflare/vite-plugin which reads it.
115
- "gen:wrangler": "deco-wrangler gen",
116
- predev: "deco-wrangler gen",
117
- prebuild: "deco-wrangler gen",
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 }}