@decocms/start 3.0.0 → 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.
@@ -1,141 +0,0 @@
1
- name: deploy (central)
2
-
3
- # Reusable workflow that drives `wrangler deploy` for any storefront repo.
4
- # Worker name is the storefront repo basename by convention; there is no
5
- # per-site registry. The deploy is gated by the `decocms-deployer` GitHub App
6
- # being installed on the target storefront repo -- the App-token mint fails
7
- # (and the deploy never starts) if the App isn't installed there.
8
- #
9
- # v3 architecture (D6.2): triggered via `workflow_dispatch` from the storefront,
10
- # authenticated as the `decocms-deployer` GitHub App. The deploy runs IN THIS
11
- # REPO'S CONTEXT, so `CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID` resolve
12
- # from this repo's plain repo secrets and never leave decocms/deco-start.
13
- #
14
- # Caller usage (in the storefront repo, `.github/workflows/deploy.yml`):
15
- #
16
- # on:
17
- # push:
18
- # branches: [main]
19
- # permissions:
20
- # contents: read
21
- # jobs:
22
- # trigger:
23
- # runs-on: ubuntu-latest
24
- # steps:
25
- # - uses: actions/create-github-app-token@v1
26
- # id: app-token
27
- # with:
28
- # app-id: ${{ secrets.DECOCMS_DEPLOYER_APP_ID }}
29
- # private-key: ${{ secrets.DECOCMS_DEPLOYER_APP_PRIVATE_KEY }}
30
- # owner: decocms
31
- # repositories: deco-start
32
- # - env:
33
- # GH_TOKEN: ${{ steps.app-token.outputs.token }}
34
- # run: |
35
- # gh workflow run deploy.yml \
36
- # --repo decocms/deco-start \
37
- # --ref v3 \
38
- # -f site_owner=${GITHUB_REPOSITORY%%/*} \
39
- # -f site_name=${GITHUB_REPOSITORY##*/}
40
-
41
- on:
42
- workflow_dispatch:
43
- inputs:
44
- site_owner:
45
- description: "GitHub org of the storefront (e.g. deco-sites). Defaults to deco-sites."
46
- type: string
47
- required: false
48
- default: deco-sites
49
- site_name:
50
- description: "Storefront repo basename. Becomes the Cloudflare worker name."
51
- type: string
52
- required: true
53
-
54
- permissions:
55
- contents: read
56
-
57
- concurrency:
58
- group: deploy-${{ inputs.site_owner }}-${{ inputs.site_name }}
59
- cancel-in-progress: false
60
-
61
- jobs:
62
- deploy:
63
- runs-on: ubuntu-latest
64
- steps:
65
- - name: Checkout deco-start (template + scripts)
66
- uses: actions/checkout@v4
67
-
68
- - name: Mint App token for storefront checkout
69
- id: app-token
70
- uses: actions/create-github-app-token@v1
71
- with:
72
- app-id: ${{ secrets.DECOCMS_DEPLOYER_APP_ID }}
73
- private-key: ${{ secrets.DECOCMS_DEPLOYER_APP_PRIVATE_KEY }}
74
- owner: ${{ inputs.site_owner }}
75
- repositories: ${{ inputs.site_name }}
76
-
77
- # SECURITY: production deploys IGNORE any caller-supplied sha. The deploy
78
- # always targets the storefront's CURRENT default-branch HEAD. This means
79
- # an attacker with push to repo A who triggers a deploy of repo B can
80
- # only force a no-op redeploy of B's current main -- they cannot select
81
- # an arbitrary historical commit (no force-rollback attack).
82
- - name: Resolve target sha (storefront default branch HEAD)
83
- id: target
84
- env:
85
- GH_TOKEN: ${{ steps.app-token.outputs.token }}
86
- SITE_REPO: ${{ inputs.site_owner }}/${{ inputs.site_name }}
87
- run: |
88
- set -euo pipefail
89
- DEFAULT_BRANCH=$(gh api "repos/$SITE_REPO" --jq .default_branch)
90
- SHA=$(gh api "repos/$SITE_REPO/branches/$DEFAULT_BRANCH" --jq .commit.sha)
91
- echo "ref=$DEFAULT_BRANCH" >> "$GITHUB_OUTPUT"
92
- echo "sha=$SHA" >> "$GITHUB_OUTPUT"
93
- echo "::notice::Deploying $SITE_REPO @ $DEFAULT_BRANCH ($SHA)"
94
-
95
- - name: Checkout storefront at default-branch HEAD
96
- uses: actions/checkout@v4
97
- with:
98
- repository: ${{ inputs.site_owner }}/${{ inputs.site_name }}
99
- ref: ${{ steps.target.outputs.sha }}
100
- token: ${{ steps.app-token.outputs.token }}
101
- path: site
102
- fetch-depth: 1
103
-
104
- - uses: actions/setup-node@v4
105
- with:
106
- node-version: 22
107
-
108
- - name: Restore npm cache
109
- uses: actions/cache@v4
110
- with:
111
- path: ~/.npm
112
- key: npm-${{ runner.os }}-${{ hashFiles('site/package-lock.json') }}
113
- restore-keys: npm-${{ runner.os }}-
114
-
115
- - name: Install dependencies
116
- working-directory: site
117
- run: |
118
- if [ ! -f package-lock.json ]; then
119
- npm install --package-lock-only
120
- fi
121
- npm ci
122
-
123
- - name: Build
124
- working-directory: site
125
- run: npm run build
126
-
127
- - name: Generate wrangler.jsonc from template
128
- working-directory: site
129
- env:
130
- DECO_START_PATH: "${{ github.workspace }}"
131
- WORKER_NAME: ${{ inputs.site_name }}
132
- OUTPUT_PATH: "./wrangler.jsonc"
133
- run: node "$DECO_START_PATH/scripts/deploy/build-wrangler-config.mjs"
134
-
135
- - name: Deploy to Cloudflare Workers
136
- working-directory: site
137
- env:
138
- CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
139
- CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
140
- BUILD_HASH: ${{ steps.target.outputs.sha }}
141
- run: npx wrangler deploy --var "BUILD_HASH:${BUILD_HASH:0:7}"
@@ -1,200 +0,0 @@
1
- name: preview (central)
2
-
3
- # Reusable workflow that uploads a preview Worker version (alias) for any
4
- # storefront repo. Worker name is the storefront repo basename by convention;
5
- # there is no per-site registry. The preview is gated by the `decocms-deployer`
6
- # GitHub App being installed on the target storefront repo.
7
- #
8
- # v3 architecture (D6.2): triggered via `workflow_dispatch` from the storefront,
9
- # authenticated as the `decocms-deployer` GitHub App. CF secrets resolve from
10
- # this repo's plain repo secrets and never leave decocms/deco-start.
11
- #
12
- # Caller usage (in the storefront repo, `.github/workflows/preview.yml`):
13
- #
14
- # on:
15
- # pull_request:
16
- # types: [opened, synchronize, reopened]
17
- # push:
18
- # branches: ['env/**']
19
- # permissions:
20
- # contents: read
21
- # jobs:
22
- # trigger:
23
- # runs-on: ubuntu-latest
24
- # steps:
25
- # - id: meta
26
- # run: |
27
- # if [ "${{ github.event_name }}" = "pull_request" ]; then
28
- # echo "alias=pr-${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT"
29
- # echo "sha=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT"
30
- # else
31
- # REF="${GITHUB_REF#refs/heads/env/}"
32
- # echo "alias=$(echo "$REF" | sed 's|[^a-z0-9-]|-|g')" >> "$GITHUB_OUTPUT"
33
- # echo "sha=${{ github.sha }}" >> "$GITHUB_OUTPUT"
34
- # fi
35
- # - uses: actions/create-github-app-token@v1
36
- # id: app-token
37
- # with:
38
- # app-id: ${{ secrets.DECOCMS_DEPLOYER_APP_ID }}
39
- # private-key: ${{ secrets.DECOCMS_DEPLOYER_APP_PRIVATE_KEY }}
40
- # owner: decocms
41
- # repositories: deco-start
42
- # - env:
43
- # GH_TOKEN: ${{ steps.app-token.outputs.token }}
44
- # run: |
45
- # gh workflow run preview.yml \
46
- # --repo decocms/deco-start \
47
- # --ref v3 \
48
- # -f site_owner=${GITHUB_REPOSITORY%%/*} \
49
- # -f site_name=${GITHUB_REPOSITORY##*/} \
50
- # -f site_sha=${{ steps.meta.outputs.sha }} \
51
- # -f alias=${{ steps.meta.outputs.alias }} \
52
- # -f pr_number=${{ github.event.pull_request.number || '' }}
53
- #
54
- # Note on forks: pull_request runs from forked repos cannot access repo
55
- # secrets (incl. DECOCMS_DEPLOYER_APP_*), so they cannot trigger previews.
56
-
57
- on:
58
- workflow_dispatch:
59
- inputs:
60
- site_owner:
61
- description: "GitHub org of the storefront. Defaults to deco-sites."
62
- type: string
63
- required: false
64
- default: deco-sites
65
- site_name:
66
- description: "Storefront repo basename. Becomes the Cloudflare worker name."
67
- type: string
68
- required: true
69
- site_sha:
70
- description: "Commit sha to build & preview. Trusted as-is (preview alias has no production blast radius)."
71
- type: string
72
- required: true
73
- alias:
74
- description: "Preview alias name (e.g. pr-123, feature-foo). Must match wrangler alias rules."
75
- type: string
76
- required: true
77
- pr_number:
78
- description: "Optional PR number on the storefront repo. If set, the preview URL is commented back on the PR."
79
- type: string
80
- required: false
81
- default: ""
82
-
83
- permissions:
84
- contents: read
85
-
86
- concurrency:
87
- group: preview-${{ inputs.site_owner }}-${{ inputs.site_name }}-${{ inputs.alias }}
88
- cancel-in-progress: true
89
-
90
- jobs:
91
- preview:
92
- runs-on: ubuntu-latest
93
- steps:
94
- - name: Checkout deco-start (template + scripts)
95
- uses: actions/checkout@v4
96
-
97
- - name: Validate alias format
98
- env:
99
- ALIAS: ${{ inputs.alias }}
100
- run: |
101
- set -euo pipefail
102
- if ! echo "$ALIAS" | grep -Eq '^[a-z0-9][a-z0-9-]{0,62}$'; then
103
- echo "::error::Invalid alias '$ALIAS'. Must be lowercase alphanumeric/hyphens, max 63 chars, start with alphanumeric."
104
- exit 1
105
- fi
106
-
107
- - name: Mint App token for storefront checkout
108
- id: app-token
109
- uses: actions/create-github-app-token@v1
110
- with:
111
- app-id: ${{ secrets.DECOCMS_DEPLOYER_APP_ID }}
112
- private-key: ${{ secrets.DECOCMS_DEPLOYER_APP_PRIVATE_KEY }}
113
- owner: ${{ inputs.site_owner }}
114
- repositories: ${{ inputs.site_name }}
115
-
116
- - name: Checkout storefront at requested sha
117
- uses: actions/checkout@v4
118
- with:
119
- repository: ${{ inputs.site_owner }}/${{ inputs.site_name }}
120
- ref: ${{ inputs.site_sha }}
121
- token: ${{ steps.app-token.outputs.token }}
122
- path: site
123
- fetch-depth: 1
124
-
125
- - uses: actions/setup-node@v4
126
- with:
127
- node-version: 22
128
-
129
- - name: Restore npm cache
130
- uses: actions/cache@v4
131
- with:
132
- path: ~/.npm
133
- key: npm-${{ runner.os }}-${{ hashFiles('site/package-lock.json') }}
134
- restore-keys: npm-${{ runner.os }}-
135
-
136
- - name: Install dependencies
137
- working-directory: site
138
- run: |
139
- if [ ! -f package-lock.json ]; then
140
- npm install --package-lock-only
141
- fi
142
- npm ci
143
-
144
- - name: Build
145
- working-directory: site
146
- run: npm run build
147
-
148
- - name: Generate wrangler.jsonc from template
149
- working-directory: site
150
- env:
151
- DECO_START_PATH: "${{ github.workspace }}"
152
- WORKER_NAME: ${{ inputs.site_name }}
153
- OUTPUT_PATH: "./wrangler.jsonc"
154
- run: node "$DECO_START_PATH/scripts/deploy/build-wrangler-config.mjs"
155
-
156
- - name: Upload preview version
157
- id: deploy
158
- working-directory: site
159
- env:
160
- CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
161
- CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
162
- ALIAS: ${{ inputs.alias }}
163
- run: |
164
- set +e
165
- OUTPUT=$(npx wrangler versions upload --preview-alias "$ALIAS" 2>&1)
166
- EXIT_CODE=$?
167
- set -e
168
- echo "$OUTPUT"
169
- if [ $EXIT_CODE -ne 0 ]; then
170
- echo "::error::wrangler versions upload failed with exit code $EXIT_CODE"
171
- exit $EXIT_CODE
172
- fi
173
- PREVIEW_URL=$(echo "$OUTPUT" | grep 'Version Preview URL:' | sed 's/.*Version Preview URL: //')
174
- ALIAS_URL=$(echo "$OUTPUT" | grep 'Version Preview Alias URL:' | sed 's/.*Version Preview Alias URL: //')
175
- echo "preview_url=${PREVIEW_URL}" >> "$GITHUB_OUTPUT"
176
- echo "alias_url=${ALIAS_URL}" >> "$GITHUB_OUTPUT"
177
-
178
- - name: Mint App token for PR comment
179
- id: comment-token
180
- if: inputs.pr_number != ''
181
- uses: actions/create-github-app-token@v1
182
- with:
183
- app-id: ${{ secrets.DECOCMS_DEPLOYER_APP_ID }}
184
- private-key: ${{ secrets.DECOCMS_DEPLOYER_APP_PRIVATE_KEY }}
185
- owner: ${{ inputs.site_owner }}
186
- repositories: ${{ inputs.site_name }}
187
- permission-pull-requests: write
188
-
189
- - name: Comment preview URL on storefront PR
190
- if: inputs.pr_number != ''
191
- env:
192
- GH_TOKEN: ${{ steps.comment-token.outputs.token }}
193
- REPO: ${{ inputs.site_owner }}/${{ inputs.site_name }}
194
- PR_NUMBER: ${{ inputs.pr_number }}
195
- PREVIEW_URL: ${{ steps.deploy.outputs.preview_url }}
196
- ALIAS_URL: ${{ steps.deploy.outputs.alias_url }}
197
- run: |
198
- set -euo pipefail
199
- BODY=$(printf '### Preview deployed\n\n| | URL |\n|---|---|\n| **Version** | %s |\n| **Alias** | %s |\n' "$PREVIEW_URL" "$ALIAS_URL")
200
- gh pr comment "$PR_NUMBER" --repo "$REPO" --body "$BODY"
@@ -1,171 +0,0 @@
1
- name: sync-secrets (central)
2
-
3
- # Reusable workflow. Reconciles the per-storefront `SECRET_*` values stored in
4
- # this repo's `<site_name>-secrets` GitHub Environment with the Cloudflare
5
- # Worker's runtime secrets (with the `SECRET_` prefix stripped).
6
- #
7
- # Examples:
8
- # SECRET_STRIPE_KEY in deco-start env -> STRIPE_KEY on the worker
9
- #
10
- # v3 architecture (D6.2 / S1): SECRET_* values live in deco-start environments
11
- # named `<site_name>-secrets` (e.g. `baggagio-tanstack-secrets`). Site teams
12
- # get per-environment edit/approve permissions via GitHub Environment
13
- # protection rules, enforced by GitHub itself. CF credentials never leave
14
- # decocms/deco-start, AND site secrets never leave decocms/deco-start either.
15
- # The storefront repo holds zero credentials.
16
- #
17
- # Defaults to dry-run; the caller must pass `mode=apply` to actually write.
18
- # Orphans on the worker (present on worker but not in the env as SECRET_*) are
19
- # warned about, never deleted.
20
- #
21
- # Caller usage (in the storefront repo, `.github/workflows/sync-secrets.yml`):
22
- #
23
- # on:
24
- # workflow_dispatch:
25
- # inputs:
26
- # mode:
27
- # type: choice
28
- # options: [dry-run, apply]
29
- # default: dry-run
30
- # permissions:
31
- # contents: read
32
- # jobs:
33
- # trigger:
34
- # runs-on: ubuntu-latest
35
- # steps:
36
- # - uses: actions/create-github-app-token@v1
37
- # id: app-token
38
- # with:
39
- # app-id: ${{ secrets.DECOCMS_DEPLOYER_APP_ID }}
40
- # private-key: ${{ secrets.DECOCMS_DEPLOYER_APP_PRIVATE_KEY }}
41
- # owner: decocms
42
- # repositories: deco-start
43
- # - env:
44
- # GH_TOKEN: ${{ steps.app-token.outputs.token }}
45
- # run: |
46
- # gh workflow run sync-secrets.yml \
47
- # --repo decocms/deco-start \
48
- # --ref v3 \
49
- # -f site_name=${GITHUB_REPOSITORY##*/} \
50
- # -f mode=${{ inputs.mode }}
51
-
52
- on:
53
- workflow_dispatch:
54
- inputs:
55
- site_name:
56
- description: "Storefront repo basename. Becomes the Cloudflare worker name AND the environment name (`<site_name>-secrets`)."
57
- type: string
58
- required: true
59
- mode:
60
- description: "dry-run = print diff only | apply = set secrets on worker"
61
- type: string
62
- required: false
63
- default: "dry-run"
64
-
65
- permissions:
66
- contents: read
67
-
68
- concurrency:
69
- group: sync-secrets-${{ inputs.site_name }}
70
- cancel-in-progress: false
71
-
72
- jobs:
73
- sync:
74
- runs-on: ubuntu-latest
75
- # Dynamic per-storefront environment. Site teams get edit/approve
76
- # permissions on their own environment via GitHub Environment protection
77
- # rules (set up in this repo's Settings -> Environments).
78
- environment: ${{ inputs.site_name }}-secrets
79
- steps:
80
- - name: Checkout deco-start (template + scripts)
81
- uses: actions/checkout@v4
82
-
83
- - name: Generate wrangler.jsonc (so wrangler knows which worker to target)
84
- env:
85
- DECO_START_PATH: "."
86
- WORKER_NAME: ${{ inputs.site_name }}
87
- OUTPUT_PATH: "./wrangler.jsonc"
88
- run: node scripts/deploy/build-wrangler-config.mjs
89
-
90
- - uses: actions/setup-node@v4
91
- with:
92
- node-version: 22
93
-
94
- - name: Install wrangler
95
- run: npm install --no-save wrangler@4
96
-
97
- - name: Plan and (optionally) apply
98
- env:
99
- CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
100
- CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
101
- # ALL_SECRETS includes both repo secrets (CLOUDFLARE_*) AND
102
- # environment secrets (SECRET_*) because we're bound to the env.
103
- ALL_SECRETS: ${{ toJSON(secrets) }}
104
- MODE: ${{ inputs.mode }}
105
- run: |
106
- set -euo pipefail
107
-
108
- desired_json=$(printf '%s' "$ALL_SECRETS" | jq '
109
- to_entries
110
- | map(select(.key | startswith("SECRET_")))
111
- | map({ key: (.key | sub("^SECRET_"; "")), value: .value })
112
- | from_entries
113
- ')
114
-
115
- desired_names=$(printf '%s' "$desired_json" | jq -r 'keys[]' | sort)
116
-
117
- while IFS= read -r name; do
118
- [ -z "$name" ] && continue
119
- if ! [[ "$name" =~ ^[A-Z][A-Z0-9_]{0,63}$ ]]; then
120
- echo "::error::Invalid worker secret name derived from SECRET_${name}: must match ^[A-Z][A-Z0-9_]{0,63}$"
121
- exit 1
122
- fi
123
- done <<< "$desired_names"
124
-
125
- existing=$(npx wrangler secret list --format=json | jq -r '.[].name' | sort)
126
-
127
- to_set="$desired_names"
128
- orphans=$(comm -23 <(printf '%s\n' "$existing") <(printf '%s\n' "$desired_names") || true)
129
-
130
- {
131
- echo "## Diff for ${{ inputs.site_name }}"
132
- echo ""
133
- echo "### Will set (from SECRET_* in \`${{ inputs.site_name }}-secrets\` environment):"
134
- if [ -z "$to_set" ]; then
135
- echo " (none -- environment has no SECRET_* values)"
136
- else
137
- echo "$to_set" | sed 's/^/ + /'
138
- fi
139
- echo ""
140
- echo "### Orphans (on worker, not in env as SECRET_*):"
141
- if [ -z "$orphans" ]; then
142
- echo " (none)"
143
- else
144
- echo "$orphans" | sed 's/^/ ! /'
145
- echo ""
146
- echo " These will NOT be deleted automatically. To remove manually:"
147
- echo "$orphans" | sed 's|^| npx wrangler secret delete "|; s|$|" --force|'
148
- fi
149
- } | tee -a "$GITHUB_STEP_SUMMARY"
150
-
151
- if [ -n "$orphans" ]; then
152
- echo "::warning::${orphans//$'\n'/, } exist on the worker but not in env as SECRET_*. Not deleting."
153
- fi
154
-
155
- if [ "$MODE" = "dry-run" ]; then
156
- echo "::notice::dry-run mode -- no changes applied"
157
- exit 0
158
- fi
159
-
160
- if [ -z "$to_set" ]; then
161
- echo "Nothing to apply."
162
- exit 0
163
- fi
164
-
165
- printf '%s' "$desired_json" | jq -r 'to_entries[] | "\(.key)\t\(.value)"' \
166
- | while IFS=$'\t' read -r name value; do
167
- echo "Setting $name..."
168
- printf '%s' "$value" | npx wrangler secret put "$name"
169
- done
170
-
171
- echo "::notice::Applied $(echo "$to_set" | wc -l | tr -d ' ') secrets."
package/deploy/README.md DELETED
@@ -1,121 +0,0 @@
1
- # `deploy/` — central wrangler template
2
-
3
- This directory holds **`wrangler-template.jsonc`** — the canonical wrangler config
4
- that every storefront on the platform inherits. It is consumed by the reusable
5
- GitHub workflows under [`.github/workflows/`](../.github/workflows/)
6
- (`deploy.yml`, `preview.yml`, `sync-secrets.yml`) and by the local
7
- `deco-wrangler` CLI.
8
-
9
- There is **no per-site registry**. Worker name is the storefront repo basename
10
- by convention (`deco-sites/baggagio-tanstack` → worker `baggagio-tanstack`).
11
- Anything that must vary deterministically per worker (like the Analytics Engine
12
- dataset name) is encoded as a substitution token in the template — see
13
- [Substitution tokens](#substitution-tokens) below.
14
-
15
- ## Trust model
16
-
17
- The deploy is gated by the `decocms-deployer` **GitHub App** being installed on
18
- the target storefront repo:
19
-
20
- 1. The storefront's caller workflow mints a short-lived App-installation token.
21
- 2. It calls `gh workflow run deploy.yml --repo decocms/deco-start -f site_owner=… -f site_name=…`.
22
- 3. The central deploy workflow runs **in this repo's context** and itself mints
23
- another short-lived App-installation token to check out the storefront. If
24
- the App isn't installed on `<site_owner>/<site_name>`, the mint fails and
25
- the deploy never starts.
26
- 4. The central workflow then runs build + `wrangler deploy` using
27
- `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` from this repo's plain
28
- repo secrets.
29
-
30
- Properties this gives:
31
-
32
- - **CF credentials never leave decocms/deco-start.** The storefront repo holds
33
- zero Cloudflare credentials — it only has the GitHub App credentials, which
34
- can be used solely to trigger workflows on this repo.
35
- - **Worker naming is convention-based and not customer-controlled.** A
36
- customer with push access to their own storefront cannot rename the worker
37
- their deploy lands on (the central workflow always uses
38
- `inputs.site_name` as the worker name; modifying the caller stub to pass a
39
- different `site_name` would also require the App to be installed on that
40
- other repo).
41
- - **Force-rollback is impossible for production.** The central deploy
42
- workflow ignores any caller-supplied sha and always resolves the
43
- storefront's current default-branch HEAD itself. The worst a compromised
44
- storefront can do across tenants is trigger a no-op redeploy of another
45
- storefront's current main.
46
-
47
- `deploy/` and `scripts/deploy/` and the central workflow files are
48
- CODEOWNERS-protected — only the platform team approves changes.
49
-
50
- ### Where Cloudflare credentials live
51
-
52
- | Secret class | Lives in | How it reaches the worker |
53
- |---|---|---|
54
- | `CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID` | this repo's **repo secrets** | central workflow runs in this repo's context, env-var resolves natively |
55
- | `DECOCMS_DEPLOYER_APP_ID` / `DECOCMS_DEPLOYER_APP_PRIVATE_KEY` | this repo's repo secrets AND deco-sites org-level secrets | mints short-lived installation tokens for both directions of the dispatch flow |
56
- | `SECRET_*` runtime secrets (per site) | this repo's `<site_name>-secrets` GitHub Environment | `sync-secrets.yml` binds to that environment, reads `SECRET_*` from `${{ secrets }}`, runs `wrangler secret put` |
57
-
58
- To rotate Cloudflare credentials, edit them in this repo only. To rotate a
59
- runtime secret for one storefront, edit the corresponding environment in this
60
- repo only. No storefront PR needed for either.
61
-
62
- ## How `wrangler.jsonc` is generated
63
-
64
- At deploy time, the central workflow runs
65
- [`scripts/deploy/build-wrangler-config.mjs`](../scripts/deploy/build-wrangler-config.mjs),
66
- which:
67
-
68
- 1. Loads `deploy/wrangler-template.jsonc`.
69
- 2. Substitutes `$WORKER_*` tokens (see below) using the worker name passed by
70
- the central workflow (= storefront repo basename).
71
- 3. Writes the result to `./wrangler.jsonc` in the storefront checkout, with
72
- `name` injected as the first key.
73
-
74
- `account_id` is never written to JSON — wrangler reads it from
75
- `CLOUDFLARE_ACCOUNT_ID` (env var in CI; `wrangler login` locally). This way a
76
- typo cannot misroute a deploy to a different Cloudflare account.
77
-
78
- ### Substitution tokens
79
-
80
- Any string in the template containing one of these literals is replaced at
81
- build time:
82
-
83
- | Token | Replacement | Example use |
84
- |---|---|---|
85
- | `$WORKER_NAME` | worker name verbatim | rare; mostly available for parity |
86
- | `$WORKER_UNDERSCORE` | worker name with `-` → `_` | `analytics_engine_datasets[].dataset` (must be a valid Postgres-style identifier) |
87
-
88
- To add a new derived field, add the token wherever it makes sense in
89
- `wrangler-template.jsonc`. Anything not in the substitution table appears
90
- verbatim in the generated config.
91
-
92
- ## Adding a new site
93
-
94
- 1. Install the `decocms-deployer` GitHub App on the new storefront repo
95
- (Settings → Integrations → GitHub Apps in the deco-sites org).
96
- 2. Add the four caller workflow stubs to the new repo (copy from any existing
97
- storefront's `.github/workflows/{deploy,preview,sync-secrets,regen-blocks}.yml`).
98
- 3. Add `wrangler.jsonc` to the new repo's `.gitignore` and add the
99
- `gen:wrangler` / `predev` / `prebuild` / `types` scripts to `package.json`
100
- so local dev still works (use any existing storefront as a template).
101
- 4. If the site needs runtime secrets, create a new environment in this repo
102
- named `<repo-basename>-secrets` and add the `SECRET_*` values there. Set
103
- environment protection rules to grant the site team self-service access to
104
- their own environment.
105
- 5. Push to `main` and verify the deploy lands on a worker named after the repo.
106
-
107
- ## Migrating an existing site whose worker name doesn't match its repo
108
-
109
- Two cases to be aware of:
110
-
111
- - **Worker rename.** The worker created by the first deploy will use the repo
112
- basename. If an old worker exists with a different name (e.g.
113
- `miess-01-tanstack` repo whose old worker was `miess-tanstack`), you'll need
114
- a manual cutover: deploy the new worker, re-attach custom domain routes via
115
- the Cloudflare dashboard, copy any wrangler secrets, then delete the old
116
- worker. There is intentionally no per-site override for this — these cases
117
- are rare and best resolved at the CF layer.
118
- - **AE dataset rename.** The dataset name is derived from worker name, so a
119
- worker rename also changes the AE dataset. Old data remains queryable under
120
- the old dataset name; new data goes to the new name. Update Grafana panels
121
- and saved queries accordingly.
@@ -1,46 +0,0 @@
1
- // Canonical wrangler.jsonc template for every storefront on the platform.
2
- //
3
- // There is no per-site registry. Worker name == storefront repo basename by
4
- // convention; the central deploy workflows pass `WORKER_NAME` to the build
5
- // script, which substitutes `$WORKER_*` tokens in this template and writes
6
- // the result to `wrangler.jsonc` in the caller checkout.
7
- //
8
- // Substitution tokens (see scripts/deploy/site-registry.mjs):
9
- // $WORKER_NAME -> worker name verbatim (e.g. "als-tanstack")
10
- // $WORKER_UNDERSCORE -> worker name, `-` -> `_` (e.g. "als_tanstack")
11
- //
12
- // To upgrade compatibility flags, observability, KV bindings, or any field
13
- // that should change for every site at once, change this file and tag a new
14
- // deco-start release.
15
- //
16
- // Notes on what is INTENTIONALLY missing here:
17
- // - "name" -- always derived from the per-site `worker_name`.
18
- // - "account_id" -- never lives in the JSON. Wrangler reads it from the
19
- // CLOUDFLARE_ACCOUNT_ID env var in CI and from local config otherwise.
20
- // This way, a typo in JSON cannot misroute a deploy.
21
- // - "routes" -- production custom domains are managed in the Cloudflare
22
- // dashboard, not in JSON. Avoids drift between JSON and CF reality and
23
- // means a site going live doesn't need a deco-start PR.
24
- //
25
- // To upgrade compatibility flags, observability, or worker-entry path across
26
- // every site at once, change this file and tag a new deco-start release.
27
- {
28
- "compatibility_date": "2026-02-14",
29
- "compatibility_flags": ["nodejs_compat", "no_handle_cross_request_promise_resolution"],
30
- "main": "./src/worker-entry.ts",
31
- "workers_dev": true,
32
- "preview_urls": true,
33
- "kv_namespaces": [
34
- { "binding": "SITES_KV", "id": "ad0b74fc4d9341c9af9149c4ab85132f" }
35
- ],
36
- "version_metadata": { "binding": "CF_VERSION_METADATA" },
37
- "analytics_engine_datasets": [
38
- { "binding": "DECO_METRICS", "dataset": "deco_metrics_$WORKER_UNDERSCORE" }
39
- ],
40
- "observability": {
41
- "logs": {
42
- "enabled": true,
43
- "invocation_logs": true
44
- }
45
- }
46
- }