@decocms/start 2.30.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/deco-to-tanstack-migration/references/worker-cloudflare.md +90 -63
- package/.cursor/rules/migration-tooling-policy.mdc +41 -28
- package/.github/workflows/deploy.yml +89 -70
- package/.github/workflows/preview.yml +137 -79
- package/.github/workflows/sync-secrets.yml +61 -70
- package/CODEOWNERS +6 -8
- package/MIGRATION_TOOLING_PLAN.md +14 -9
- package/deploy/README.md +100 -90
- package/deploy/wrangler-template.jsonc +22 -4
- package/package.json +1 -1
- package/scripts/deploy/build-wrangler-config.mjs +10 -12
- package/scripts/deploy/site-registry.mjs +52 -99
- package/scripts/deploy/wrangler-wrapper.mjs +22 -30
- package/scripts/migrate/phase-scaffold.ts +6 -6
- package/scripts/migrate/phase-verify.ts +2 -2
- package/scripts/migrate/templates/github-workflows.ts +86 -25
- package/deploy/sites/als-tanstack.jsonc +0 -7
- package/deploy/sites/americanas-tanstack.jsonc +0 -4
- package/deploy/sites/baggagio-tanstack.jsonc +0 -4
- package/deploy/sites/casaevideo-storefront.jsonc +0 -11
- package/deploy/sites/lebiscuit-tanstack.jsonc +0 -19
- package/deploy/sites/miess-01-tanstack.jsonc +0 -8
- package/scripts/deploy/resolve-site.mjs +0 -58
|
@@ -1,118 +1,168 @@
|
|
|
1
1
|
name: preview (central)
|
|
2
2
|
|
|
3
|
-
# Reusable workflow that uploads a preview version (alias) for any
|
|
4
|
-
# repo
|
|
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.
|
|
5
7
|
#
|
|
6
|
-
#
|
|
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`):
|
|
7
13
|
#
|
|
8
14
|
# on:
|
|
9
|
-
# repository_dispatch:
|
|
10
|
-
# types: [preview-deploy]
|
|
11
15
|
# pull_request:
|
|
12
16
|
# types: [opened, synchronize, reopened]
|
|
13
17
|
# push:
|
|
14
18
|
# branches: ['env/**']
|
|
15
19
|
# permissions:
|
|
16
20
|
# contents: read
|
|
17
|
-
# pull-requests: write
|
|
18
|
-
# statuses: write
|
|
19
21
|
# jobs:
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
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 || '' }}
|
|
23
53
|
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
54
|
+
# Note on forks: pull_request runs from forked repos cannot access repo
|
|
55
|
+
# secrets (incl. DECOCMS_DEPLOYER_APP_*), so they cannot trigger previews.
|
|
26
56
|
|
|
27
57
|
on:
|
|
28
|
-
|
|
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: ""
|
|
29
82
|
|
|
30
83
|
permissions:
|
|
31
84
|
contents: read
|
|
32
|
-
pull-requests: write
|
|
33
|
-
statuses: write
|
|
34
85
|
|
|
35
86
|
concurrency:
|
|
36
|
-
group: preview-${{
|
|
87
|
+
group: preview-${{ inputs.site_owner }}-${{ inputs.site_name }}-${{ inputs.alias }}
|
|
37
88
|
cancel-in-progress: true
|
|
38
89
|
|
|
39
90
|
jobs:
|
|
40
91
|
preview:
|
|
41
92
|
runs-on: ubuntu-latest
|
|
42
|
-
environment: production
|
|
43
93
|
steps:
|
|
44
|
-
- name:
|
|
45
|
-
|
|
94
|
+
- name: Checkout deco-start (template + scripts)
|
|
95
|
+
uses: actions/checkout@v4
|
|
96
|
+
|
|
97
|
+
- name: Validate alias format
|
|
98
|
+
env:
|
|
99
|
+
ALIAS: ${{ inputs.alias }}
|
|
46
100
|
run: |
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
51
105
|
fi
|
|
52
106
|
|
|
53
|
-
-
|
|
107
|
+
- name: Mint App token for storefront checkout
|
|
108
|
+
id: app-token
|
|
109
|
+
uses: actions/create-github-app-token@v1
|
|
54
110
|
with:
|
|
55
|
-
|
|
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 }}
|
|
56
115
|
|
|
57
|
-
- name:
|
|
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
|
|
116
|
+
- name: Checkout storefront at requested sha
|
|
68
117
|
uses: actions/checkout@v4
|
|
69
118
|
with:
|
|
70
|
-
repository:
|
|
71
|
-
ref: ${{
|
|
72
|
-
|
|
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
|
|
73
124
|
|
|
74
125
|
- uses: actions/setup-node@v4
|
|
75
126
|
with:
|
|
76
127
|
node-version: 22
|
|
77
128
|
|
|
78
|
-
- name:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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"
|
|
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 }}-
|
|
90
135
|
|
|
91
136
|
- name: Install dependencies
|
|
92
|
-
|
|
137
|
+
working-directory: site
|
|
138
|
+
run: |
|
|
139
|
+
if [ ! -f package-lock.json ]; then
|
|
140
|
+
npm install --package-lock-only
|
|
141
|
+
fi
|
|
142
|
+
npm ci
|
|
93
143
|
|
|
94
144
|
- name: Build
|
|
145
|
+
working-directory: site
|
|
95
146
|
run: npm run build
|
|
96
147
|
|
|
97
|
-
- name:
|
|
98
|
-
|
|
99
|
-
run: node .deco-start/scripts/deploy/resolve-site.mjs
|
|
148
|
+
- name: Generate wrangler.jsonc from template
|
|
149
|
+
working-directory: site
|
|
100
150
|
env:
|
|
101
|
-
DECO_START_PATH: .
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
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"
|
|
110
155
|
|
|
111
156
|
- name: Upload preview version
|
|
112
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 }}
|
|
113
163
|
run: |
|
|
114
164
|
set +e
|
|
115
|
-
OUTPUT=$(npx wrangler versions upload --preview-alias $
|
|
165
|
+
OUTPUT=$(npx wrangler versions upload --preview-alias "$ALIAS" 2>&1)
|
|
116
166
|
EXIT_CODE=$?
|
|
117
167
|
set -e
|
|
118
168
|
echo "$OUTPUT"
|
|
@@ -124,19 +174,27 @@ jobs:
|
|
|
124
174
|
ALIAS_URL=$(echo "$OUTPUT" | grep 'Version Preview Alias URL:' | sed 's/.*Version Preview Alias URL: //')
|
|
125
175
|
echo "preview_url=${PREVIEW_URL}" >> "$GITHUB_OUTPUT"
|
|
126
176
|
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
177
|
|
|
131
|
-
- name:
|
|
132
|
-
|
|
133
|
-
|
|
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
|
|
134
182
|
with:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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,102 +1,96 @@
|
|
|
1
1
|
name: sync-secrets (central)
|
|
2
2
|
|
|
3
|
-
# Reusable workflow. Reconciles the
|
|
4
|
-
#
|
|
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).
|
|
5
6
|
#
|
|
6
7
|
# Examples:
|
|
7
|
-
# SECRET_STRIPE_KEY in
|
|
8
|
+
# SECRET_STRIPE_KEY in deco-start env -> STRIPE_KEY on the worker
|
|
8
9
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
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
|
|
11
19
|
# warned about, never deleted.
|
|
12
20
|
#
|
|
13
|
-
# Caller usage (in
|
|
21
|
+
# Caller usage (in the storefront repo, `.github/workflows/sync-secrets.yml`):
|
|
14
22
|
#
|
|
15
23
|
# on:
|
|
16
24
|
# workflow_dispatch:
|
|
17
25
|
# inputs:
|
|
18
26
|
# mode:
|
|
19
|
-
# description: "dry-run | apply"
|
|
20
|
-
# required: true
|
|
21
|
-
# default: "dry-run"
|
|
22
27
|
# type: choice
|
|
23
28
|
# options: [dry-run, apply]
|
|
29
|
+
# default: dry-run
|
|
30
|
+
# permissions:
|
|
31
|
+
# contents: read
|
|
24
32
|
# jobs:
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
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 }}
|
|
41
51
|
|
|
42
52
|
on:
|
|
43
|
-
|
|
53
|
+
workflow_dispatch:
|
|
44
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
|
|
45
59
|
mode:
|
|
46
60
|
description: "dry-run = print diff only | apply = set secrets on worker"
|
|
47
|
-
required: false
|
|
48
61
|
type: string
|
|
62
|
+
required: false
|
|
49
63
|
default: "dry-run"
|
|
50
64
|
|
|
51
65
|
permissions:
|
|
52
66
|
contents: read
|
|
53
67
|
|
|
54
68
|
concurrency:
|
|
55
|
-
group: sync-secrets-${{
|
|
69
|
+
group: sync-secrets-${{ inputs.site_name }}
|
|
56
70
|
cancel-in-progress: false
|
|
57
71
|
|
|
58
72
|
jobs:
|
|
59
73
|
sync:
|
|
60
74
|
runs-on: ubuntu-latest
|
|
61
|
-
environment
|
|
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
|
|
62
79
|
steps:
|
|
63
|
-
-
|
|
64
|
-
|
|
65
|
-
- name: Resolve deco-start ref + site identity
|
|
66
|
-
id: meta
|
|
67
|
-
run: |
|
|
68
|
-
WF_REF="${{ github.workflow_ref }}"
|
|
69
|
-
REF="${WF_REF##*@}"
|
|
70
|
-
REF="${REF#refs/tags/}"
|
|
71
|
-
REF="${REF#refs/heads/}"
|
|
72
|
-
echo "deco_start_ref=$REF" >> "$GITHUB_OUTPUT"
|
|
73
|
-
echo "site_name=${GITHUB_REPOSITORY#*/}" >> "$GITHUB_OUTPUT"
|
|
74
|
-
|
|
75
|
-
- name: Checkout deco-start registry at the same ref
|
|
80
|
+
- name: Checkout deco-start (template + scripts)
|
|
76
81
|
uses: actions/checkout@v4
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
81
89
|
|
|
82
90
|
- uses: actions/setup-node@v4
|
|
83
91
|
with:
|
|
84
92
|
node-version: 22
|
|
85
93
|
|
|
86
|
-
- name: Resolve site manifest
|
|
87
|
-
id: site
|
|
88
|
-
run: node .deco-start/scripts/deploy/resolve-site.mjs
|
|
89
|
-
env:
|
|
90
|
-
DECO_START_PATH: .deco-start
|
|
91
|
-
SITE_NAME: ${{ steps.meta.outputs.site_name }}
|
|
92
|
-
|
|
93
|
-
- name: Generate wrangler.jsonc
|
|
94
|
-
run: node .deco-start/scripts/deploy/build-wrangler-config.mjs
|
|
95
|
-
env:
|
|
96
|
-
DECO_START_PATH: .deco-start
|
|
97
|
-
SITE_NAME: ${{ steps.meta.outputs.site_name }}
|
|
98
|
-
OUTPUT_PATH: ./wrangler.jsonc
|
|
99
|
-
|
|
100
94
|
- name: Install wrangler
|
|
101
95
|
run: npm install --no-save wrangler@4
|
|
102
96
|
|
|
@@ -104,12 +98,13 @@ jobs:
|
|
|
104
98
|
env:
|
|
105
99
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
106
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.
|
|
107
103
|
ALL_SECRETS: ${{ toJSON(secrets) }}
|
|
108
104
|
MODE: ${{ inputs.mode }}
|
|
109
105
|
run: |
|
|
110
106
|
set -euo pipefail
|
|
111
107
|
|
|
112
|
-
# Build desired-state map from SECRET_* entries.
|
|
113
108
|
desired_json=$(printf '%s' "$ALL_SECRETS" | jq '
|
|
114
109
|
to_entries
|
|
115
110
|
| map(select(.key | startswith("SECRET_")))
|
|
@@ -119,7 +114,6 @@ jobs:
|
|
|
119
114
|
|
|
120
115
|
desired_names=$(printf '%s' "$desired_json" | jq -r 'keys[]' | sort)
|
|
121
116
|
|
|
122
|
-
# Validate names before any side effect.
|
|
123
117
|
while IFS= read -r name; do
|
|
124
118
|
[ -z "$name" ] && continue
|
|
125
119
|
if ! [[ "$name" =~ ^[A-Z][A-Z0-9_]{0,63}$ ]]; then
|
|
@@ -128,24 +122,22 @@ jobs:
|
|
|
128
122
|
fi
|
|
129
123
|
done <<< "$desired_names"
|
|
130
124
|
|
|
131
|
-
# Snapshot what's currently on the worker.
|
|
132
125
|
existing=$(npx wrangler secret list --format=json | jq -r '.[].name' | sort)
|
|
133
126
|
|
|
134
|
-
# Diff
|
|
135
127
|
to_set="$desired_names"
|
|
136
128
|
orphans=$(comm -23 <(printf '%s\n' "$existing") <(printf '%s\n' "$desired_names") || true)
|
|
137
129
|
|
|
138
130
|
{
|
|
139
|
-
echo "## Diff"
|
|
131
|
+
echo "## Diff for ${{ inputs.site_name }}"
|
|
140
132
|
echo ""
|
|
141
|
-
echo "### Will set (from SECRET_* in
|
|
133
|
+
echo "### Will set (from SECRET_* in \`${{ inputs.site_name }}-secrets\` environment):"
|
|
142
134
|
if [ -z "$to_set" ]; then
|
|
143
|
-
echo " (none)"
|
|
135
|
+
echo " (none -- environment has no SECRET_* values)"
|
|
144
136
|
else
|
|
145
137
|
echo "$to_set" | sed 's/^/ + /'
|
|
146
138
|
fi
|
|
147
139
|
echo ""
|
|
148
|
-
echo "### Orphans (on worker, not in
|
|
140
|
+
echo "### Orphans (on worker, not in env as SECRET_*):"
|
|
149
141
|
if [ -z "$orphans" ]; then
|
|
150
142
|
echo " (none)"
|
|
151
143
|
else
|
|
@@ -157,7 +149,7 @@ jobs:
|
|
|
157
149
|
} | tee -a "$GITHUB_STEP_SUMMARY"
|
|
158
150
|
|
|
159
151
|
if [ -n "$orphans" ]; then
|
|
160
|
-
echo "::warning::${orphans//$'\n'/, } exist on the worker but not in
|
|
152
|
+
echo "::warning::${orphans//$'\n'/, } exist on the worker but not in env as SECRET_*. Not deleting."
|
|
161
153
|
fi
|
|
162
154
|
|
|
163
155
|
if [ "$MODE" = "dry-run" ]; then
|
|
@@ -165,7 +157,6 @@ jobs:
|
|
|
165
157
|
exit 0
|
|
166
158
|
fi
|
|
167
159
|
|
|
168
|
-
# Apply
|
|
169
160
|
if [ -z "$to_set" ]; then
|
|
170
161
|
echo "Nothing to apply."
|
|
171
162
|
exit 0
|
package/CODEOWNERS
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
# CODEOWNERS for decocms/deco-start
|
|
2
2
|
#
|
|
3
|
-
# `deploy
|
|
4
|
-
#
|
|
5
|
-
#
|
|
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.
|
|
3
|
+
# `deploy/wrangler-template.jsonc` is the canonical wrangler config every
|
|
4
|
+
# storefront inherits. A bad PR here can change every site's runtime config in
|
|
5
|
+
# one shot. Only the platform team approves changes.
|
|
8
6
|
#
|
|
9
|
-
# The central reusable workflows are in the same
|
|
10
|
-
# how every site is built and deployed.
|
|
11
|
-
deploy/
|
|
7
|
+
# The central reusable workflows under `.github/workflows/` are in the same
|
|
8
|
+
# trust boundary: they decide how every site is built and deployed.
|
|
9
|
+
deploy/ @vibe-dex
|
|
12
10
|
.github/workflows/deploy.yml @vibe-dex
|
|
13
11
|
.github/workflows/preview.yml @vibe-dex
|
|
14
12
|
.github/workflows/sync-secrets.yml @vibe-dex
|
|
@@ -118,7 +118,8 @@ this plan.
|
|
|
118
118
|
| 2026-05-01 | **D4 — Site-local apps: local by default, promote at 3** | Site-specific apps live in `src/apps/local/` until ≥3 sites use them, then promote to `@decocms/apps`. |
|
|
119
119
|
| 2026-05-01 | **D5 — Failed migrations: rm -rf and re-run** | No `--restart` mode. Half-migrated sites are throwaways. Failure modes get documented in skills, not encoded as escape hatches. |
|
|
120
120
|
| 2026-05-07 | **D6 — Deploy / preview / secrets pipelines: centralize in `deco-start`** | At 6 sites the "1-minute copy" of `deploy.yml` / `preview.yml` / `wrangler.jsonc` had already produced unintended drift (lebiscuit missing 2 workflows, miess missing `account_id`, casaevideo's `loadtest:tail` worker name out of sync with its wrangler config). All sites now consume reusable workflows from `decocms/deco-start@v2` and a per-site registry under [`deploy/sites/<repo>.jsonc`](./deploy/) deep-merged on top of [`deploy/wrangler-template.jsonc`](./deploy/wrangler-template.jsonc) at deploy time. Customer repos hold only ~5-line caller workflows; `wrangler.jsonc` is generated and gitignored. The repo→worker binding is the trust boundary that prevents one site's commits from misrouting onto another site's worker (the central workflow ignores caller `inputs:` for identity and derives the site name from `${{ github.repository }}`). See [`deploy/README.md`](./deploy/README.md) for the contract. |
|
|
121
|
-
| 2026-05-07 | **D6.1 — Cloudflare credentials never leave `deco-start`** | Same-day refinement of D6 after the first central deploy on `baggagio-tanstack` failed with `Secret CLOUDFLARE_API_TOKEN is required, but not provided while calling`. The original D6 design used `secrets: inherit` from the storefront stub and required `CLOUDFLARE_*` to live in the `deco-sites` org, which broke the principle that *the only secrets a storefront repo holds are the secrets that go into wrangler secrets, not the ones used to deploy*.
|
|
121
|
+
| 2026-05-07 | **D6.1 — Cloudflare credentials never leave `deco-start`** | Same-day refinement of D6 after the first central deploy on `baggagio-tanstack` failed with `Secret CLOUDFLARE_API_TOKEN is required, but not provided while calling`. The original D6 design used `secrets: inherit` from the storefront stub and required `CLOUDFLARE_*` to live in the `deco-sites` org, which broke the principle that *the only secrets a storefront repo holds are the secrets that go into wrangler secrets, not the ones used to deploy*. First-pass refinement: the central `deploy.yml` / `preview.yml` / `sync-secrets.yml` jobs declared `environment: production` to try to make `${{ secrets.CLOUDFLARE_* }}` resolve from `decocms/deco-start`'s `production` Environment. **Found broken empirically on 2026-05-07** — the deployment registers in the *caller* repo, not the called workflow's repo, so the environment lookup uses the caller's `production` env (auto-created with no secrets). Superseded by D6.2 the same evening. |
|
|
122
|
+
| 2026-05-07 | **D6.2 — App-mediated dispatch + no per-site registry (supersedes D6 + D6.1)** | After D6.1's `environment:` mechanism was empirically shown not to work cross-repo, the architecture pivoted: a `decocms-deployer` GitHub App is installed on `decocms/deco-start` (`actions:write`) and on each storefront repo (`contents:read`, optionally `pull-requests:write`). The storefront caller stub mints a short-lived App-installation token and calls `gh workflow run deploy.yml --repo decocms/deco-start --ref v3 -f site_owner=… -f site_name=…`. The central workflow runs in `decocms/deco-start`'s context, so `CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID` are ordinary repo secrets. For runtime `SECRET_*` values, each storefront has a `<site_name>-secrets` GitHub Environment in `decocms/deco-start` (S1 design); `sync-secrets.yml` binds to that environment and pushes to `wrangler secret put`. The per-site registry under `deploy/sites/<repo>.jsonc` was dropped entirely (Pure C): worker name = repo basename by convention; the App being installed on the storefront repo is the deploy authorization gate; rare per-worker derived fields (like AE dataset name) use `$WORKER_*` substitution tokens in the template. Force-rollback is impossible for production deploys because the central workflow ignores caller-supplied `site_sha` and resolves the storefront's current default-branch HEAD itself. See [`deploy/README.md`](./deploy/README.md) for the full trust model. **Operational migrations required by Pure C:** `miess-01-tanstack` repo's worker shifts from `miess-tanstack` to `miess-01-tanstack` (CF-side cutover); `lebiscuit-tanstack` AE dataset shifts from `deco_metrics_lebiscuit` to `deco_metrics_lebiscuit_tanstack` (orphans old data). |
|
|
122
123
|
|
|
123
124
|
The full text of the constitutional rule (loaded into every agent
|
|
124
125
|
session for this repo) lives at
|
|
@@ -1677,15 +1678,19 @@ props. One broken section never takes the page down.
|
|
|
1677
1678
|
`regen-blocks.yml` and its `wrangler.jsonc` lacked `account_id`,
|
|
1678
1679
|
lebiscuit's preview workflow swallowed `wrangler` exit codes, and
|
|
1679
1680
|
casaevideo's `loadtest:tail` referenced a worker name that didn't
|
|
1680
|
-
match its `wrangler.jsonc`. **Resolved 2026-05-07 via D6
|
|
1681
|
-
workflows + wrangler config are centralized in
|
|
1681
|
+
match its `wrangler.jsonc`. **Resolved 2026-05-07 via D6 → D6.1 →
|
|
1682
|
+
D6.2:** all workflows + wrangler config are centralized in
|
|
1682
1683
|
[`deco-start/.github/workflows/`](./.github/workflows/) and
|
|
1683
|
-
[`deco-start/deploy/`](./deploy/)
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
is
|
|
1687
|
-
|
|
1688
|
-
|
|
1684
|
+
[`deco-start/deploy/`](./deploy/), and storefront caller stubs use
|
|
1685
|
+
a `decocms-deployer` GitHub App to trigger them via `workflow_dispatch`
|
|
1686
|
+
with no Cloudflare credentials in the storefront repo. New-site
|
|
1687
|
+
onboarding is now: install the `decocms-deployer` App on the new
|
|
1688
|
+
storefront repo, drop the ~15-line caller workflows in, push to main.
|
|
1689
|
+
No `deploy/sites/<repo>.jsonc` PR is needed — worker name is the
|
|
1690
|
+
repo basename by convention. No `wrangler.jsonc` is committed to the
|
|
1691
|
+
site repo — `deco-wrangler gen` (a `bin` shipped from `@decocms/start`)
|
|
1692
|
+
materializes it from the central template on demand for local dev
|
|
1693
|
+
and CI alike.
|
|
1689
1694
|
|
|
1690
1695
|
#### Counter-evidence the user-rule asks for
|
|
1691
1696
|
|