@decocms/start 2.30.0 → 2.30.1
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/references/worker-cloudflare.md +18 -3
- package/.cursor/rules/migration-tooling-policy.mdc +15 -1
- package/.github/workflows/deploy.yml +13 -6
- package/.github/workflows/preview.yml +5 -6
- package/.github/workflows/sync-secrets.yml +12 -5
- package/MIGRATION_TOOLING_PLAN.md +1 -0
- package/deploy/README.md +28 -2
- package/package.json +1 -1
|
@@ -287,6 +287,16 @@ execs the real `wrangler` with that config in cwd.
|
|
|
287
287
|
untamperable by user code) and looked up in `deploy/sites/<repo>.jsonc`.
|
|
288
288
|
- A customer cannot misroute their deploy onto another site's worker
|
|
289
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.
|
|
290
300
|
|
|
291
301
|
### Common mistakes (do not do these)
|
|
292
302
|
|
|
@@ -296,9 +306,14 @@ execs the real `wrangler` with that config in cwd.
|
|
|
296
306
|
Add it to `deco-start/.github/workflows/deploy.yml` instead so every site
|
|
297
307
|
picks it up at once.
|
|
298
308
|
- **Hard-coding `account_id` in a site's wrangler config.** It comes from
|
|
299
|
-
`CLOUDFLARE_ACCOUNT_ID` (
|
|
300
|
-
locally). Removing it from JSON is
|
|
301
|
-
accidentally deploying to the wrong
|
|
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.
|
|
302
317
|
- **Setting `worker_name` to anything other than the repo name** without
|
|
303
318
|
a strong reason. The 1:1 binding makes audit (and incident response)
|
|
304
319
|
trivial. Exceptions today: `casaevideo-storefront` -> `casaevideo-tanstack`
|
|
@@ -88,12 +88,26 @@ boundary: the central workflow ignores caller `inputs:` for identity and
|
|
|
88
88
|
derives the site name from `${{ github.repository }}`. `deploy/**` is
|
|
89
89
|
CODEOWNERS-protected.
|
|
90
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
|
+
|
|
91
101
|
**Agent behavior**: when adding or migrating a site, do **not** generate a
|
|
92
102
|
per-site `wrangler.jsonc` or per-site `deploy.yml` / `preview.yml` /
|
|
93
103
|
`sync-secrets.yml` / `regen-blocks.yml`. The site repo gets the four
|
|
94
104
|
~5-line caller stubs only. Per-site config goes in a PR to
|
|
95
105
|
`decocms/deco-start` adding `deploy/sites/<repo>.jsonc`. Do not commit
|
|
96
|
-
`wrangler.jsonc` or any wrangler config to a site repo.
|
|
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.
|
|
97
111
|
|
|
98
112
|
## Priority order
|
|
99
113
|
|
|
@@ -20,14 +20,16 @@ name: deploy (central)
|
|
|
20
20
|
# deploy:
|
|
21
21
|
# uses: decocms/deco-start/.github/workflows/deploy.yml@v2
|
|
22
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`.
|
|
23
30
|
|
|
24
31
|
on:
|
|
25
|
-
workflow_call:
|
|
26
|
-
secrets:
|
|
27
|
-
CLOUDFLARE_API_TOKEN:
|
|
28
|
-
required: true
|
|
29
|
-
CLOUDFLARE_ACCOUNT_ID:
|
|
30
|
-
required: true
|
|
32
|
+
workflow_call: {}
|
|
31
33
|
|
|
32
34
|
permissions:
|
|
33
35
|
contents: write
|
|
@@ -39,6 +41,11 @@ concurrency:
|
|
|
39
41
|
jobs:
|
|
40
42
|
deploy:
|
|
41
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
|
|
42
49
|
steps:
|
|
43
50
|
- name: Checkout caller repo
|
|
44
51
|
uses: actions/checkout@v4
|
|
@@ -20,14 +20,12 @@ name: preview (central)
|
|
|
20
20
|
# preview:
|
|
21
21
|
# uses: decocms/deco-start/.github/workflows/preview.yml@v2
|
|
22
22
|
# secrets: inherit
|
|
23
|
+
#
|
|
24
|
+
# Same secret model as deploy.yml: `CLOUDFLARE_*` resolve from this repo's
|
|
25
|
+
# `production` environment, not from the caller. See deploy.yml for details.
|
|
23
26
|
|
|
24
27
|
on:
|
|
25
|
-
workflow_call:
|
|
26
|
-
secrets:
|
|
27
|
-
CLOUDFLARE_API_TOKEN:
|
|
28
|
-
required: true
|
|
29
|
-
CLOUDFLARE_ACCOUNT_ID:
|
|
30
|
-
required: true
|
|
28
|
+
workflow_call: {}
|
|
31
29
|
|
|
32
30
|
permissions:
|
|
33
31
|
contents: read
|
|
@@ -41,6 +39,7 @@ concurrency:
|
|
|
41
39
|
jobs:
|
|
42
40
|
preview:
|
|
43
41
|
runs-on: ubuntu-latest
|
|
42
|
+
environment: production
|
|
44
43
|
steps:
|
|
45
44
|
- name: Resolve ref
|
|
46
45
|
id: resolve
|
|
@@ -27,6 +27,17 @@ name: sync-secrets (central)
|
|
|
27
27
|
# with:
|
|
28
28
|
# mode: ${{ inputs.mode }}
|
|
29
29
|
# secrets: inherit
|
|
30
|
+
#
|
|
31
|
+
# Two distinct secret classes flow through this job:
|
|
32
|
+
# - `CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID` -> resolved from this
|
|
33
|
+
# repo's `production` environment (via `environment:` below). NEVER from
|
|
34
|
+
# the caller storefront. Same model as deploy.yml / preview.yml.
|
|
35
|
+
# - `SECRET_*` -> inherited from the caller storefront via `secrets: inherit`
|
|
36
|
+
# in the caller stub. These ARE site-owned and get pushed to the worker
|
|
37
|
+
# as runtime secrets (with the `SECRET_` prefix stripped).
|
|
38
|
+
# `secrets: inherit` is therefore REQUIRED on the caller side for this
|
|
39
|
+
# workflow specifically (unlike deploy.yml / preview.yml where it's just
|
|
40
|
+
# tolerated).
|
|
30
41
|
|
|
31
42
|
on:
|
|
32
43
|
workflow_call:
|
|
@@ -36,11 +47,6 @@ on:
|
|
|
36
47
|
required: false
|
|
37
48
|
type: string
|
|
38
49
|
default: "dry-run"
|
|
39
|
-
secrets:
|
|
40
|
-
CLOUDFLARE_API_TOKEN:
|
|
41
|
-
required: true
|
|
42
|
-
CLOUDFLARE_ACCOUNT_ID:
|
|
43
|
-
required: true
|
|
44
50
|
|
|
45
51
|
permissions:
|
|
46
52
|
contents: read
|
|
@@ -52,6 +58,7 @@ concurrency:
|
|
|
52
58
|
jobs:
|
|
53
59
|
sync:
|
|
54
60
|
runs-on: ubuntu-latest
|
|
61
|
+
environment: production
|
|
55
62
|
steps:
|
|
56
63
|
- uses: actions/checkout@v4
|
|
57
64
|
|
|
@@ -118,6 +118,7 @@ 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
122
|
|
|
122
123
|
The full text of the constitutional rule (loaded into every agent
|
|
123
124
|
session for this repo) lives at
|
package/deploy/README.md
CHANGED
|
@@ -28,6 +28,29 @@ no other way to identify a site.
|
|
|
28
28
|
`deploy/**` is CODEOWNERS-protected. Only the platform team can change site
|
|
29
29
|
manifests or the template.
|
|
30
30
|
|
|
31
|
+
### Where Cloudflare credentials live
|
|
32
|
+
|
|
33
|
+
`CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` live in **this repo's
|
|
34
|
+
`production` GitHub Environment**, never in any storefront repo. The reusable
|
|
35
|
+
workflows declare `environment: production` on the deploy / preview /
|
|
36
|
+
sync-secrets jobs, which is what GitHub uses to resolve `secrets.CLOUDFLARE_*`
|
|
37
|
+
against the called repo (deco-start) instead of the caller (the storefront).
|
|
38
|
+
|
|
39
|
+
This is the second half of the trust property: even if a storefront repo were
|
|
40
|
+
fully compromised, the attacker has no path to the Cloudflare token. The
|
|
41
|
+
storefront repo only holds its own `SECRET_*` runtime secrets, which are
|
|
42
|
+
inherited by `sync-secrets.yml` and pushed to its own worker as runtime
|
|
43
|
+
secrets — never reaching another site's worker because the central workflow
|
|
44
|
+
resolves `worker_name` from this registry, not from caller input.
|
|
45
|
+
|
|
46
|
+
| Secret class | Lives in | Reaches worker via |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID` | this repo's `production` environment | central workflow's `environment:` binding (no caller passthrough) |
|
|
49
|
+
| `SECRET_*` runtime secrets (per site) | each storefront repo | `sync-secrets.yml` inherits via `secrets: inherit` and pushes via `wrangler secret put` |
|
|
50
|
+
|
|
51
|
+
To rotate the Cloudflare credentials, edit the environment in this repo only.
|
|
52
|
+
No storefront PR needed.
|
|
53
|
+
|
|
31
54
|
## How wrangler.jsonc is generated
|
|
32
55
|
|
|
33
56
|
At deploy time, the central workflow runs
|
|
@@ -55,8 +78,11 @@ which:
|
|
|
55
78
|
`@v2` major tag (the major-tag advance step lives inline in
|
|
56
79
|
[`.github/workflows/release.yml`](../.github/workflows/release.yml)).
|
|
57
80
|
3. In the new repo, add the four caller workflows from
|
|
58
|
-
[`.github/workflows/`](../.github/workflows/)
|
|
59
|
-
`
|
|
81
|
+
[`.github/workflows/`](../.github/workflows/). **Do not** add
|
|
82
|
+
`CLOUDFLARE_*` to the storefront — those live in this repo's `production`
|
|
83
|
+
environment and reach the runner via the central workflow's `environment:`
|
|
84
|
+
binding. The storefront only needs `SECRET_*` entries for its own worker
|
|
85
|
+
runtime secrets.
|
|
60
86
|
4. Push to `main` and verify the deploy lands on the right worker.
|
|
61
87
|
|
|
62
88
|
## Per-site override schema
|