@decocms/start 2.29.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.
Files changed (31) hide show
  1. package/.agents/skills/deco-to-tanstack-migration/SKILL.md +1 -1
  2. package/.agents/skills/deco-to-tanstack-migration/references/vtex-commerce.md +5 -1
  3. package/.agents/skills/deco-to-tanstack-migration/references/worker-cloudflare.md +122 -10
  4. package/.agents/skills/deco-to-tanstack-migration/templates/package-json.md +5 -1
  5. package/.cursor/rules/migration-tooling-policy.mdc +36 -2
  6. package/.github/workflows/deploy.yml +122 -0
  7. package/.github/workflows/preview.yml +142 -0
  8. package/.github/workflows/regen-blocks.yml +56 -0
  9. package/.github/workflows/release.yml +26 -0
  10. package/.github/workflows/sync-secrets.yml +180 -0
  11. package/CODEOWNERS +16 -0
  12. package/MIGRATION_TOOLING_PLAN.md +17 -4
  13. package/deploy/README.md +111 -0
  14. package/deploy/sites/als-tanstack.jsonc +7 -0
  15. package/deploy/sites/americanas-tanstack.jsonc +4 -0
  16. package/deploy/sites/baggagio-tanstack.jsonc +4 -0
  17. package/deploy/sites/casaevideo-storefront.jsonc +11 -0
  18. package/deploy/sites/lebiscuit-tanstack.jsonc +19 -0
  19. package/deploy/sites/miess-01-tanstack.jsonc +8 -0
  20. package/deploy/wrangler-template.jsonc +28 -0
  21. package/package.json +3 -2
  22. package/scripts/deploy/build-wrangler-config.mjs +49 -0
  23. package/scripts/deploy/jsonc.mjs +76 -0
  24. package/scripts/deploy/resolve-site.mjs +58 -0
  25. package/scripts/deploy/site-registry.mjs +142 -0
  26. package/scripts/deploy/wrangler-wrapper.mjs +126 -0
  27. package/scripts/migrate/phase-scaffold.ts +13 -3
  28. package/scripts/migrate/phase-verify.ts +6 -1
  29. package/scripts/migrate/templates/github-workflows.ts +98 -0
  30. package/scripts/migrate/templates/package-json.ts +9 -2
  31. package/scripts/migrate/templates/wrangler.ts +0 -30
@@ -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`
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`.
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
@@ -36,7 +36,11 @@ This lets the core component render while gracefully degrading features that dep
36
36
 
37
37
  ## 7. VTEX API Auth on Cloudflare Workers
38
38
 
39
- Env vars must be set via `wrangler secret put` or `.dev.vars`, not `.env`.
39
+ Env vars are stored as `SECRET_*` GitHub repo secrets (e.g. `SECRET_VTEX_APP_KEY`)
40
+ and pushed to the worker via the centralized `Sync worker secrets` workflow
41
+ (D6). Locally, use `.dev.vars` (gitignored) for development. Do not run
42
+ `npx wrangler secret put` per-site — the central workflow keeps GitHub and
43
+ Cloudflare in sync.
40
44
 
41
45
 
42
46
  ## 8. Cookie Handling
@@ -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. Point `wrangler.jsonc` to this file instead of `@tanstack/react-start/server-entry`.
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.
41
41
 
42
42
  ```typescript
43
43
  // src/worker-entry.ts
@@ -57,13 +57,10 @@ export default createDecoWorkerEntry(serverEntry, {
57
57
  });
58
58
  ```
59
59
 
60
- ```jsonc
61
- // wrangler.jsonc -- MUST point to custom entry, NOT the default
62
- {
63
- "main": "./src/worker-entry.ts",
64
- // NOT: "main": "@tanstack/react-start/server-entry"
65
- }
66
- ```
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).
67
64
 
68
65
  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.
69
66
 
@@ -136,9 +133,14 @@ Imports like `import Color from "npm:colorjs.io"` use the Deno-specific `npm:` p
136
133
  After deploying a new build to Cloudflare Workers, the edge cache may still serve old HTML that references previous JS bundle hashes. This causes module import failures.
137
134
 
138
135
  **Fix**: After every deploy, purge the cache:
139
- 1. Set a `PURGE_TOKEN` secret: `npx wrangler secret put PURGE_TOKEN`
136
+ 1. Set a `PURGE_TOKEN` secret. Add `SECRET_PURGE_TOKEN` to the site repo's
137
+ GitHub Secrets, then trigger the centralized `Sync worker secrets`
138
+ workflow (`workflow_dispatch` → `apply`). This pushes it to the Cloudflare
139
+ worker via `wrangler secret put PURGE_TOKEN`. **Do not** run
140
+ `npx wrangler secret put` manually per-site — the central workflow keeps
141
+ GitHub and Cloudflare in sync.
140
142
  2. Call the purge endpoint: `POST /_cache/purge` with `Authorization: Bearer <token>` and body `{"paths":["/"]}`
141
- 3. Automate this in CI/CD (see the deploy.yml workflow)
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.
142
144
 
143
145
 
144
146
  ## 44. Runtime Module Import Kills Lazy-Loaded Sections
@@ -207,3 +209,113 @@ Or in project `.npmrc` with an env var (for CI):
207
209
  ```
208
210
 
209
211
  **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
+
214
+ ## 46. Central Deploy / Wrangler Config (D6)
215
+
216
+ **Severity**: HIGH — site repos must NOT commit `wrangler.jsonc` or per-site deploy logic. Doing so reintroduces drift.
217
+
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.
226
+
227
+ ### What goes in the site repo
228
+
229
+ Four ~5-line caller workflow stubs and nothing else:
230
+
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
+ ```
278
+
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).
@@ -12,7 +12,11 @@
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
- "deploy": "wrangler deploy"
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"
16
20
  },
17
21
  "dependencies": {
18
22
  "@decocms/apps": "^0.20.1",
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Constitutional decisions for the deco-start migration-tooling effort (D1–D5, priorities, process). Always loaded.
2
+ description: Constitutional decisions for the deco-start migration-tooling effort (D1–D6, 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–D5), don't ship fast.
17
+ explicitly (write a D-record like D1–D6), 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,6 +76,39 @@ 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.
111
+
79
112
  ## Priority order
80
113
 
81
114
  Work proceeds in this order. Higher priorities don't block on lower ones,
@@ -109,3 +142,4 @@ order — but only if explicitly identified as urgent.
109
142
  - When reading a section of the plan or a skill, prefer the plan or skill
110
143
  over your memory of past conversations.
111
144
  - 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.
@@ -0,0 +1,122 @@
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 }}
@@ -0,0 +1,142 @@
1
+ name: preview (central)
2
+
3
+ # Reusable workflow that uploads a preview version (alias) for any storefront
4
+ # repo registered under `deploy/sites/<repo-name>.jsonc`.
5
+ #
6
+ # Caller usage (in customer repo, `.github/workflows/preview.yml`):
7
+ #
8
+ # on:
9
+ # repository_dispatch:
10
+ # types: [preview-deploy]
11
+ # pull_request:
12
+ # types: [opened, synchronize, reopened]
13
+ # push:
14
+ # branches: ['env/**']
15
+ # permissions:
16
+ # contents: read
17
+ # pull-requests: write
18
+ # statuses: write
19
+ # jobs:
20
+ # preview:
21
+ # uses: decocms/deco-start/.github/workflows/preview.yml@v2
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.
26
+
27
+ on:
28
+ workflow_call: {}
29
+
30
+ permissions:
31
+ contents: read
32
+ pull-requests: write
33
+ statuses: write
34
+
35
+ concurrency:
36
+ group: preview-${{ github.repository }}-${{ github.event.client_payload.ref || github.head_ref || github.ref_name }}
37
+ cancel-in-progress: true
38
+
39
+ jobs:
40
+ preview:
41
+ runs-on: ubuntu-latest
42
+ environment: production
43
+ steps:
44
+ - name: Resolve ref
45
+ id: resolve
46
+ run: |
47
+ if [ "${{ github.event_name }}" = "repository_dispatch" ]; then
48
+ echo "ref=${{ github.event.client_payload.ref }}" >> "$GITHUB_OUTPUT"
49
+ else
50
+ echo "ref=${{ github.head_ref || github.ref_name }}" >> "$GITHUB_OUTPUT"
51
+ fi
52
+
53
+ - uses: actions/checkout@v4
54
+ with:
55
+ ref: ${{ steps.resolve.outputs.ref }}
56
+
57
+ - name: Resolve deco-start ref + site identity
58
+ id: meta
59
+ run: |
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: Compute preview alias
79
+ id: alias
80
+ run: |
81
+ REF="${{ steps.resolve.outputs.ref }}"
82
+ if echo "$REF" | grep -q '^env/'; then
83
+ ALIAS=$(echo "$REF" | sed 's|^env/||')
84
+ elif [ "${{ github.event_name }}" = "pull_request" ]; then
85
+ ALIAS="pr-${{ github.event.pull_request.number }}"
86
+ else
87
+ ALIAS=$(echo "$REF" | sed 's|[^a-z0-9-]|-|g')
88
+ fi
89
+ echo "alias=$ALIAS" >> "$GITHUB_OUTPUT"
90
+
91
+ - name: Install dependencies
92
+ run: npm install
93
+
94
+ - name: Build
95
+ run: npm run build
96
+
97
+ - name: Resolve site manifest
98
+ id: site
99
+ run: node .deco-start/scripts/deploy/resolve-site.mjs
100
+ env:
101
+ DECO_START_PATH: .deco-start
102
+ SITE_NAME: ${{ steps.meta.outputs.site_name }}
103
+
104
+ - name: Generate wrangler.jsonc
105
+ run: node .deco-start/scripts/deploy/build-wrangler-config.mjs
106
+ env:
107
+ DECO_START_PATH: .deco-start
108
+ SITE_NAME: ${{ steps.meta.outputs.site_name }}
109
+ OUTPUT_PATH: ./wrangler.jsonc
110
+
111
+ - name: Upload preview version
112
+ id: deploy
113
+ run: |
114
+ set +e
115
+ OUTPUT=$(npx wrangler versions upload --preview-alias ${{ steps.alias.outputs.alias }} 2>&1)
116
+ EXIT_CODE=$?
117
+ set -e
118
+ echo "$OUTPUT"
119
+ if [ $EXIT_CODE -ne 0 ]; then
120
+ echo "::error::wrangler versions upload failed with exit code $EXIT_CODE"
121
+ exit $EXIT_CODE
122
+ fi
123
+ PREVIEW_URL=$(echo "$OUTPUT" | grep 'Version Preview URL:' | sed 's/.*Version Preview URL: //')
124
+ ALIAS_URL=$(echo "$OUTPUT" | grep 'Version Preview Alias URL:' | sed 's/.*Version Preview Alias URL: //')
125
+ echo "preview_url=${PREVIEW_URL}" >> "$GITHUB_OUTPUT"
126
+ echo "alias_url=${ALIAS_URL}" >> "$GITHUB_OUTPUT"
127
+ env:
128
+ CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
129
+ CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
130
+
131
+ - name: Comment preview URL on PR
132
+ if: github.event_name == 'pull_request'
133
+ uses: marocchino/sticky-pull-request-comment@v2
134
+ with:
135
+ header: preview-url
136
+ message: |
137
+ ### Preview deployed
138
+
139
+ | | URL |
140
+ |---|---|
141
+ | **Version** | ${{ steps.deploy.outputs.preview_url }} |
142
+ | **Alias** | ${{ steps.deploy.outputs.alias_url }} |
@@ -0,0 +1,56 @@
1
+ name: regen-blocks (central)
2
+
3
+ # Reusable workflow. Regenerates `src/server/cms/blocks.gen.json` and commits
4
+ # the result back to the caller branch. Runs in the caller's runner context;
5
+ # uses bun because that's what the existing `generate:blocks` script expects.
6
+ #
7
+ # Caller usage (in customer repo, `.github/workflows/regen-blocks.yml`):
8
+ #
9
+ # on:
10
+ # push:
11
+ # branches: [main]
12
+ # paths: [".deco/blocks/**"]
13
+ # permissions:
14
+ # contents: write
15
+ # jobs:
16
+ # regen:
17
+ # uses: decocms/deco-start/.github/workflows/regen-blocks.yml@v2
18
+ # secrets: inherit
19
+
20
+ on:
21
+ workflow_call: {}
22
+
23
+ permissions:
24
+ contents: write
25
+
26
+ concurrency:
27
+ group: regen-blocks-${{ github.repository }}
28
+ cancel-in-progress: false
29
+
30
+ jobs:
31
+ regen:
32
+ runs-on: ubuntu-latest
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+
36
+ - uses: oven-sh/setup-bun@v2
37
+ with:
38
+ bun-version: latest
39
+
40
+ - name: Install deps
41
+ run: bun install --frozen-lockfile
42
+
43
+ - name: Regenerate blocks.gen.json
44
+ run: bun run generate:blocks
45
+
46
+ - name: Commit and push if changed
47
+ run: |
48
+ if git diff --quiet src/server/cms/blocks.gen.json; then
49
+ echo "blocks.gen.json already up to date — nothing to commit."
50
+ exit 0
51
+ fi
52
+ git config user.name "github-actions[bot]"
53
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
54
+ git add src/server/cms/blocks.gen.json
55
+ git commit -m "chore: regenerate blocks.gen.json after .deco sync"
56
+ git push
@@ -29,3 +29,29 @@ jobs:
29
29
  run: npx semantic-release
30
30
  env:
31
31
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32
+
33
+ # Caller workflows in storefront repos pin to a moveable major tag (`@v2`),
34
+ # GitHub Actions convention. After every release we fast-forward that major
35
+ # tag to the latest exact tag in the same series. Idempotent: if no new
36
+ # release happened, this just re-points to the existing latest, which is a
37
+ # no-op force-push.
38
+ #
39
+ # Why not a separate workflow on `push: tags`? GitHub suppresses workflow
40
+ # triggers caused by the auto-issued `GITHUB_TOKEN` (anti-recursion guard),
41
+ # and `@semantic-release/git` pushes tags using exactly that token, so a
42
+ # listener would never fire. Inlining keeps the lifecycle visible in one
43
+ # job log.
44
+ - name: Advance moveable major tag
45
+ run: |
46
+ git fetch --tags --force
47
+ LATEST=$(git tag -l 'v*.*.*' --sort=-v:refname | head -n 1)
48
+ if [ -z "$LATEST" ]; then
49
+ echo "::notice::no v*.*.* tags yet; nothing to advance"
50
+ exit 0
51
+ fi
52
+ MAJOR=$(echo "$LATEST" | sed -E 's/^(v[0-9]+).*/\1/')
53
+ git config user.name "github-actions[bot]"
54
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
55
+ git tag -f "$MAJOR" "$LATEST"
56
+ git push origin "$MAJOR" --force
57
+ echo "::notice::Moved $MAJOR -> $LATEST"