@decocms/start 2.30.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/deco-to-tanstack-migration/SKILL.md +1 -1
- package/.agents/skills/deco-to-tanstack-migration/references/worker-cloudflare.md +31 -107
- package/.agents/skills/deco-to-tanstack-migration/templates/package-json.md +2 -5
- package/.cursor/rules/migration-tooling-policy.mdc +32 -35
- package/CODEOWNERS +4 -14
- package/MIGRATION_TOOLING_PLAN.md +11 -10
- package/package.json +2 -3
- package/scripts/migrate/phase-scaffold.ts +9 -12
- package/scripts/migrate/phase-verify.ts +3 -5
- package/scripts/migrate/templates/package-json.ts +6 -9
- package/.github/workflows/deploy.yml +0 -122
- package/.github/workflows/preview.yml +0 -142
- package/.github/workflows/sync-secrets.yml +0 -180
- package/deploy/README.md +0 -111
- 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/deploy/wrangler-template.jsonc +0 -28
- package/scripts/deploy/build-wrangler-config.mjs +0 -49
- package/scripts/deploy/jsonc.mjs +0 -76
- package/scripts/deploy/resolve-site.mjs +0 -58
- package/scripts/deploy/site-registry.mjs +0 -142
- package/scripts/deploy/wrangler-wrapper.mjs +0 -126
- package/scripts/migrate/templates/github-workflows.ts +0 -98
|
@@ -1,142 +0,0 @@
|
|
|
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 }} |
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
name: sync-secrets (central)
|
|
2
|
-
|
|
3
|
-
# Reusable workflow. Reconciles the caller's `SECRET_*` GitHub repo secrets
|
|
4
|
-
# with the Cloudflare Worker's runtime secrets (with the prefix stripped).
|
|
5
|
-
#
|
|
6
|
-
# Examples:
|
|
7
|
-
# SECRET_STRIPE_KEY in GitHub -> STRIPE_KEY on the worker
|
|
8
|
-
#
|
|
9
|
-
# Defaults to dry-run; the caller must pass `mode: apply` to actually write.
|
|
10
|
-
# Orphans on the worker (present on worker but not in GitHub as SECRET_*) are
|
|
11
|
-
# warned about, never deleted.
|
|
12
|
-
#
|
|
13
|
-
# Caller usage (in customer repo, `.github/workflows/sync-secrets.yml`):
|
|
14
|
-
#
|
|
15
|
-
# on:
|
|
16
|
-
# workflow_dispatch:
|
|
17
|
-
# inputs:
|
|
18
|
-
# mode:
|
|
19
|
-
# description: "dry-run | apply"
|
|
20
|
-
# required: true
|
|
21
|
-
# default: "dry-run"
|
|
22
|
-
# type: choice
|
|
23
|
-
# options: [dry-run, apply]
|
|
24
|
-
# jobs:
|
|
25
|
-
# sync:
|
|
26
|
-
# uses: decocms/deco-start/.github/workflows/sync-secrets.yml@v2
|
|
27
|
-
# with:
|
|
28
|
-
# mode: ${{ inputs.mode }}
|
|
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).
|
|
41
|
-
|
|
42
|
-
on:
|
|
43
|
-
workflow_call:
|
|
44
|
-
inputs:
|
|
45
|
-
mode:
|
|
46
|
-
description: "dry-run = print diff only | apply = set secrets on worker"
|
|
47
|
-
required: false
|
|
48
|
-
type: string
|
|
49
|
-
default: "dry-run"
|
|
50
|
-
|
|
51
|
-
permissions:
|
|
52
|
-
contents: read
|
|
53
|
-
|
|
54
|
-
concurrency:
|
|
55
|
-
group: sync-secrets-${{ github.repository }}
|
|
56
|
-
cancel-in-progress: false
|
|
57
|
-
|
|
58
|
-
jobs:
|
|
59
|
-
sync:
|
|
60
|
-
runs-on: ubuntu-latest
|
|
61
|
-
environment: production
|
|
62
|
-
steps:
|
|
63
|
-
- uses: actions/checkout@v4
|
|
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
|
|
76
|
-
uses: actions/checkout@v4
|
|
77
|
-
with:
|
|
78
|
-
repository: decocms/deco-start
|
|
79
|
-
ref: ${{ steps.meta.outputs.deco_start_ref }}
|
|
80
|
-
path: .deco-start
|
|
81
|
-
|
|
82
|
-
- uses: actions/setup-node@v4
|
|
83
|
-
with:
|
|
84
|
-
node-version: 22
|
|
85
|
-
|
|
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
|
-
- name: Install wrangler
|
|
101
|
-
run: npm install --no-save wrangler@4
|
|
102
|
-
|
|
103
|
-
- name: Plan and (optionally) apply
|
|
104
|
-
env:
|
|
105
|
-
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
106
|
-
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
107
|
-
ALL_SECRETS: ${{ toJSON(secrets) }}
|
|
108
|
-
MODE: ${{ inputs.mode }}
|
|
109
|
-
run: |
|
|
110
|
-
set -euo pipefail
|
|
111
|
-
|
|
112
|
-
# Build desired-state map from SECRET_* entries.
|
|
113
|
-
desired_json=$(printf '%s' "$ALL_SECRETS" | jq '
|
|
114
|
-
to_entries
|
|
115
|
-
| map(select(.key | startswith("SECRET_")))
|
|
116
|
-
| map({ key: (.key | sub("^SECRET_"; "")), value: .value })
|
|
117
|
-
| from_entries
|
|
118
|
-
')
|
|
119
|
-
|
|
120
|
-
desired_names=$(printf '%s' "$desired_json" | jq -r 'keys[]' | sort)
|
|
121
|
-
|
|
122
|
-
# Validate names before any side effect.
|
|
123
|
-
while IFS= read -r name; do
|
|
124
|
-
[ -z "$name" ] && continue
|
|
125
|
-
if ! [[ "$name" =~ ^[A-Z][A-Z0-9_]{0,63}$ ]]; then
|
|
126
|
-
echo "::error::Invalid worker secret name derived from SECRET_${name}: must match ^[A-Z][A-Z0-9_]{0,63}$"
|
|
127
|
-
exit 1
|
|
128
|
-
fi
|
|
129
|
-
done <<< "$desired_names"
|
|
130
|
-
|
|
131
|
-
# Snapshot what's currently on the worker.
|
|
132
|
-
existing=$(npx wrangler secret list --format=json | jq -r '.[].name' | sort)
|
|
133
|
-
|
|
134
|
-
# Diff
|
|
135
|
-
to_set="$desired_names"
|
|
136
|
-
orphans=$(comm -23 <(printf '%s\n' "$existing") <(printf '%s\n' "$desired_names") || true)
|
|
137
|
-
|
|
138
|
-
{
|
|
139
|
-
echo "## Diff"
|
|
140
|
-
echo ""
|
|
141
|
-
echo "### Will set (from SECRET_* in GitHub):"
|
|
142
|
-
if [ -z "$to_set" ]; then
|
|
143
|
-
echo " (none)"
|
|
144
|
-
else
|
|
145
|
-
echo "$to_set" | sed 's/^/ + /'
|
|
146
|
-
fi
|
|
147
|
-
echo ""
|
|
148
|
-
echo "### Orphans (on worker, not in GitHub as SECRET_*):"
|
|
149
|
-
if [ -z "$orphans" ]; then
|
|
150
|
-
echo " (none)"
|
|
151
|
-
else
|
|
152
|
-
echo "$orphans" | sed 's/^/ ! /'
|
|
153
|
-
echo ""
|
|
154
|
-
echo " These will NOT be deleted automatically. To remove manually:"
|
|
155
|
-
echo "$orphans" | sed 's|^| npx wrangler secret delete "|; s|$|" --force|'
|
|
156
|
-
fi
|
|
157
|
-
} | tee -a "$GITHUB_STEP_SUMMARY"
|
|
158
|
-
|
|
159
|
-
if [ -n "$orphans" ]; then
|
|
160
|
-
echo "::warning::${orphans//$'\n'/, } exist on the worker but not in GitHub as SECRET_*. Not deleting."
|
|
161
|
-
fi
|
|
162
|
-
|
|
163
|
-
if [ "$MODE" = "dry-run" ]; then
|
|
164
|
-
echo "::notice::dry-run mode -- no changes applied"
|
|
165
|
-
exit 0
|
|
166
|
-
fi
|
|
167
|
-
|
|
168
|
-
# Apply
|
|
169
|
-
if [ -z "$to_set" ]; then
|
|
170
|
-
echo "Nothing to apply."
|
|
171
|
-
exit 0
|
|
172
|
-
fi
|
|
173
|
-
|
|
174
|
-
printf '%s' "$desired_json" | jq -r 'to_entries[] | "\(.key)\t\(.value)"' \
|
|
175
|
-
| while IFS=$'\t' read -r name value; do
|
|
176
|
-
echo "Setting $name..."
|
|
177
|
-
printf '%s' "$value" | npx wrangler secret put "$name"
|
|
178
|
-
done
|
|
179
|
-
|
|
180
|
-
echo "::notice::Applied $(echo "$to_set" | wc -l | tr -d ' ') secrets."
|
package/deploy/README.md
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
# `deploy/` — central deploy registry
|
|
2
|
-
|
|
3
|
-
This directory is the single source of truth for **what gets deployed where**
|
|
4
|
-
across every storefront on the platform. It is consumed by the reusable GitHub
|
|
5
|
-
workflows under [`.github/workflows/`](../.github/workflows/) (`deploy.yml`,
|
|
6
|
-
`preview.yml`, `sync-secrets.yml`) and by the local `deco-wrangler` CLI.
|
|
7
|
-
|
|
8
|
-
## Files
|
|
9
|
-
|
|
10
|
-
| File | Purpose |
|
|
11
|
-
|------|---------|
|
|
12
|
-
| `wrangler-template.jsonc` | Canonical wrangler config that every site inherits. Compatibility flags, worker-entry path, observability — everything that is the same for every site. |
|
|
13
|
-
| `sites/<repo-name>.jsonc` | Per-site overrides. Only the keys that genuinely vary per-site live here (`worker_name` always; `routes`, `kv_namespaces`, `analytics_engine_datasets`, `version_metadata` when used). |
|
|
14
|
-
|
|
15
|
-
The repository name (the part of `${{ github.repository }}` after the `/`) is
|
|
16
|
-
the lookup key. `als-tanstack` deploys via `sites/als-tanstack.jsonc`. There is
|
|
17
|
-
no other way to identify a site.
|
|
18
|
-
|
|
19
|
-
## Trust model
|
|
20
|
-
|
|
21
|
-
- Customer caller workflows pass **no inputs** to the central reusable workflow.
|
|
22
|
-
- The central workflow derives the site name from `${{ github.repository }}`
|
|
23
|
-
(set by GitHub, untamperable by user code) and looks up
|
|
24
|
-
`sites/<repo-name>.jsonc` from this registry.
|
|
25
|
-
- A customer cannot misroute a deploy onto another customer's worker because
|
|
26
|
-
they can't write to `decocms/deco-start`.
|
|
27
|
-
|
|
28
|
-
`deploy/**` is CODEOWNERS-protected. Only the platform team can change site
|
|
29
|
-
manifests or the template.
|
|
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
|
-
|
|
54
|
-
## How wrangler.jsonc is generated
|
|
55
|
-
|
|
56
|
-
At deploy time, the central workflow runs
|
|
57
|
-
[`scripts/deploy/build-wrangler-config.mjs`](../scripts/deploy/build-wrangler-config.mjs),
|
|
58
|
-
which:
|
|
59
|
-
|
|
60
|
-
1. Loads `deploy/wrangler-template.jsonc` (canonical defaults).
|
|
61
|
-
2. Loads `deploy/sites/<site>.jsonc` (per-site overrides).
|
|
62
|
-
3. Deep-merges: site overrides win. `worker_name` becomes wrangler's `name`.
|
|
63
|
-
Arrays are replaced, not concatenated.
|
|
64
|
-
4. Writes the result to `./wrangler.jsonc` in the caller checkout.
|
|
65
|
-
|
|
66
|
-
`account_id` is never written to JSON — wrangler reads it from
|
|
67
|
-
`CLOUDFLARE_ACCOUNT_ID` (env var in CI; `wrangler login` locally).
|
|
68
|
-
|
|
69
|
-
## Adding a new site
|
|
70
|
-
|
|
71
|
-
1. Open a PR to this repo adding `deploy/sites/<new-repo>.jsonc`:
|
|
72
|
-
```jsonc
|
|
73
|
-
{
|
|
74
|
-
"worker_name": "<new-repo>" // can differ from repo name if needed
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
2. After merge, the next `v2.x.y` semantic-release publish auto-moves the
|
|
78
|
-
`@v2` major tag (the major-tag advance step lives inline in
|
|
79
|
-
[`.github/workflows/release.yml`](../.github/workflows/release.yml)).
|
|
80
|
-
3. In the new repo, add the four caller workflows from
|
|
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.
|
|
86
|
-
4. Push to `main` and verify the deploy lands on the right worker.
|
|
87
|
-
|
|
88
|
-
## Per-site override schema
|
|
89
|
-
|
|
90
|
-
```jsonc
|
|
91
|
-
{
|
|
92
|
-
"worker_name": "string (required, immutable)",
|
|
93
|
-
"routes": [ // optional
|
|
94
|
-
{ "pattern": "www.example.com/*", "zone_name": "decocdn.com" }
|
|
95
|
-
],
|
|
96
|
-
"kv_namespaces": [ // optional
|
|
97
|
-
{ "binding": "SITES_KV", "id": "<cf-kv-id>" }
|
|
98
|
-
],
|
|
99
|
-
"analytics_engine_datasets": [ // optional
|
|
100
|
-
{ "binding": "DECO_METRICS", "dataset": "deco_metrics_<site>" }
|
|
101
|
-
],
|
|
102
|
-
"version_metadata": { // optional
|
|
103
|
-
"binding": "CF_VERSION_METADATA"
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
All other wrangler keys (compatibility flags, `main`, observability, etc.) come
|
|
109
|
-
from the template — do not duplicate them per-site. If a per-site override is
|
|
110
|
-
genuinely needed for one of those keys, add it to the schema and document the
|
|
111
|
-
reason here.
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
// Per-site overrides for `als-tanstack` (immutable repo->worker binding).
|
|
2
|
-
// Renaming `worker_name` is a breaking change: it points the next deploy at a
|
|
3
|
-
// different Cloudflare worker. Only the platform team can change this file
|
|
4
|
-
// (CODEOWNERS-protected).
|
|
5
|
-
{
|
|
6
|
-
"worker_name": "als-tanstack"
|
|
7
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// Per-site overrides for `casaevideo-storefront`.
|
|
2
|
-
//
|
|
3
|
-
// NOTE: `kv_namespaces[].id` is shared with `lebiscuit-tanstack`. Verify this
|
|
4
|
-
// is intentional before promoting this registry to v1. R4 in the central-deploy
|
|
5
|
-
// plan adds a CI check that flags shared KV ids as a copy-paste smell.
|
|
6
|
-
{
|
|
7
|
-
"worker_name": "casaevideo-tanstack",
|
|
8
|
-
"kv_namespaces": [
|
|
9
|
-
{ "binding": "SITES_KV", "id": "ad0b74fc4d9341c9af9149c4ab85132f" }
|
|
10
|
-
]
|
|
11
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// Per-site overrides for `lebiscuit-tanstack`.
|
|
2
|
-
//
|
|
3
|
-
// NOTE: `kv_namespaces[].id` is shared with `casaevideo-storefront`. See the
|
|
4
|
-
// note in casaevideo's site file. R4 will add CI validation for this.
|
|
5
|
-
//
|
|
6
|
-
// `version_metadata` and `analytics_engine_datasets` are consumed by
|
|
7
|
-
// @decocms/start's `instrumentWorker`:
|
|
8
|
-
// - version_metadata populates `service.version` on the OTel resource.
|
|
9
|
-
// - analytics_engine_datasets is the dual-emit metrics target.
|
|
10
|
-
{
|
|
11
|
-
"worker_name": "lebiscuit-tanstack",
|
|
12
|
-
"kv_namespaces": [
|
|
13
|
-
{ "binding": "SITES_KV", "id": "ad0b74fc4d9341c9af9149c4ab85132f" }
|
|
14
|
-
],
|
|
15
|
-
"version_metadata": { "binding": "CF_VERSION_METADATA" },
|
|
16
|
-
"analytics_engine_datasets": [
|
|
17
|
-
{ "binding": "DECO_METRICS", "dataset": "deco_metrics_lebiscuit" }
|
|
18
|
-
]
|
|
19
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// Per-site overrides for `miess-01-tanstack`.
|
|
2
|
-
//
|
|
3
|
-
// Worker name keeps the historical `miess-tanstack` (no "-01") so the deploy
|
|
4
|
-
// continues to land on the existing Cloudflare worker. The repo name has the
|
|
5
|
-
// `-01-` suffix; the worker name does not.
|
|
6
|
-
{
|
|
7
|
-
"worker_name": "miess-tanstack"
|
|
8
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// Canonical wrangler.jsonc template for every storefront on the platform.
|
|
2
|
-
//
|
|
3
|
-
// Per-site overrides live in `deploy/sites/<repo-name>.jsonc`. The deploy
|
|
4
|
-
// workflow deep-merges site overrides on top of this template at runtime and
|
|
5
|
-
// writes the result to `wrangler.jsonc` in the caller checkout. Site overrides
|
|
6
|
-
// always win. Arrays are replaced (not concatenated).
|
|
7
|
-
//
|
|
8
|
-
// Notes on what is INTENTIONALLY missing here:
|
|
9
|
-
// - "name" -- always derived from the per-site `worker_name`.
|
|
10
|
-
// - "account_id" -- never lives in the JSON. Wrangler reads it from the
|
|
11
|
-
// CLOUDFLARE_ACCOUNT_ID env var in CI and from local config otherwise.
|
|
12
|
-
// This way, a typo in JSON cannot misroute a deploy.
|
|
13
|
-
//
|
|
14
|
-
// To upgrade compatibility flags, observability, or worker-entry path across
|
|
15
|
-
// every site at once, change this file and tag a new deco-start release.
|
|
16
|
-
{
|
|
17
|
-
"compatibility_date": "2026-02-14",
|
|
18
|
-
"compatibility_flags": ["nodejs_compat", "no_handle_cross_request_promise_resolution"],
|
|
19
|
-
"main": "./src/worker-entry.ts",
|
|
20
|
-
"workers_dev": true,
|
|
21
|
-
"preview_urls": true,
|
|
22
|
-
"observability": {
|
|
23
|
-
"logs": {
|
|
24
|
-
"enabled": true,
|
|
25
|
-
"invocation_logs": true
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// build-wrangler-config.mjs
|
|
3
|
-
//
|
|
4
|
-
// Materializes a `wrangler.jsonc` from the canonical template + the site's
|
|
5
|
-
// per-site overrides. The output file is what `wrangler deploy` /
|
|
6
|
-
// `wrangler versions upload` / `wrangler secret put` will read.
|
|
7
|
-
//
|
|
8
|
-
// Required env:
|
|
9
|
-
// DECO_START_PATH - path to a checked-out deco-start (e.g. ".deco-start")
|
|
10
|
-
// SITE_NAME - the registry key (== caller repo name)
|
|
11
|
-
// OUTPUT_PATH - where to write the merged wrangler.jsonc
|
|
12
|
-
// (e.g. "./wrangler.jsonc" in the caller checkout)
|
|
13
|
-
|
|
14
|
-
import { writeFileSync } from "node:fs";
|
|
15
|
-
import { resolve } from "node:path";
|
|
16
|
-
import { loadSiteManifest, loadTemplate, mergeWithTemplate } from "./site-registry.mjs";
|
|
17
|
-
|
|
18
|
-
function fail(message) {
|
|
19
|
-
console.error(`::error::${message}`);
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const decoStartPath = process.env.DECO_START_PATH;
|
|
24
|
-
const siteName = process.env.SITE_NAME;
|
|
25
|
-
const outputPath = process.env.OUTPUT_PATH;
|
|
26
|
-
|
|
27
|
-
if (!decoStartPath) fail("DECO_START_PATH env var is required");
|
|
28
|
-
if (!siteName) fail("SITE_NAME env var is required");
|
|
29
|
-
if (!outputPath) fail("OUTPUT_PATH env var is required");
|
|
30
|
-
|
|
31
|
-
let merged;
|
|
32
|
-
try {
|
|
33
|
-
const template = loadTemplate(decoStartPath);
|
|
34
|
-
const manifest = loadSiteManifest(decoStartPath, siteName);
|
|
35
|
-
merged = mergeWithTemplate(template, manifest);
|
|
36
|
-
} catch (err) {
|
|
37
|
-
fail(err instanceof Error ? err.message : String(err));
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const header = `// AUTOGENERATED by @decocms/start at deploy time.
|
|
41
|
-
// Do not edit -- changes will be overwritten on the next deploy.
|
|
42
|
-
// Source: decocms/deco-start deploy/sites/${siteName}.jsonc
|
|
43
|
-
// + decocms/deco-start deploy/wrangler-template.jsonc
|
|
44
|
-
`;
|
|
45
|
-
|
|
46
|
-
const body = JSON.stringify(merged, null, 2);
|
|
47
|
-
writeFileSync(resolve(outputPath), `${header}${body}\n`);
|
|
48
|
-
|
|
49
|
-
console.log(`Wrote ${outputPath} for site "${siteName}" (worker "${merged.name}")`);
|
package/scripts/deploy/jsonc.mjs
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
// Minimal JSONC -> JSON parser used by the deploy scripts.
|
|
2
|
-
//
|
|
3
|
-
// Strips // line comments and /* block comments */ outside of string literals
|
|
4
|
-
// and tolerates trailing commas in objects/arrays. Dependency-free so the
|
|
5
|
-
// deploy scripts can run with vanilla `node` in CI.
|
|
6
|
-
|
|
7
|
-
import { readFileSync } from "node:fs";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @param {string} input
|
|
11
|
-
* @returns {string}
|
|
12
|
-
*/
|
|
13
|
-
function stripComments(input) {
|
|
14
|
-
let out = "";
|
|
15
|
-
let i = 0;
|
|
16
|
-
let inStr = false;
|
|
17
|
-
let strChar = "";
|
|
18
|
-
while (i < input.length) {
|
|
19
|
-
const c = input[i];
|
|
20
|
-
const n = input[i + 1];
|
|
21
|
-
if (inStr) {
|
|
22
|
-
out += c;
|
|
23
|
-
if (c === "\\" && i + 1 < input.length) {
|
|
24
|
-
out += input[i + 1];
|
|
25
|
-
i += 2;
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
if (c === strChar) inStr = false;
|
|
29
|
-
i++;
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
if (c === '"') {
|
|
33
|
-
inStr = true;
|
|
34
|
-
strChar = c;
|
|
35
|
-
out += c;
|
|
36
|
-
i++;
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (c === "/" && n === "/") {
|
|
40
|
-
while (i < input.length && input[i] !== "\n") i++;
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
if (c === "/" && n === "*") {
|
|
44
|
-
i += 2;
|
|
45
|
-
while (i < input.length && !(input[i] === "*" && input[i + 1] === "/")) i++;
|
|
46
|
-
i += 2;
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
out += c;
|
|
50
|
-
i++;
|
|
51
|
-
}
|
|
52
|
-
return out;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* @param {string} text
|
|
57
|
-
* @returns {unknown}
|
|
58
|
-
*/
|
|
59
|
-
export function parseJsonc(text) {
|
|
60
|
-
const stripped = stripComments(text).replace(/,\s*([}\]])/g, "$1");
|
|
61
|
-
return JSON.parse(stripped);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* @param {string} path
|
|
66
|
-
* @returns {unknown}
|
|
67
|
-
*/
|
|
68
|
-
export function readJsoncFile(path) {
|
|
69
|
-
const raw = readFileSync(path, "utf8");
|
|
70
|
-
try {
|
|
71
|
-
return parseJsonc(raw);
|
|
72
|
-
} catch (err) {
|
|
73
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
74
|
-
throw new Error(`Failed to parse JSONC at ${path}: ${message}`);
|
|
75
|
-
}
|
|
76
|
-
}
|