@decocms/start 3.0.0 → 4.1.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,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
- }
@@ -1,47 +0,0 @@
1
- #!/usr/bin/env node
2
- // build-wrangler-config.mjs
3
- //
4
- // Materializes a `wrangler.jsonc` from the canonical template, with
5
- // $WORKER_* tokens substituted. 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
- // WORKER_NAME - the Cloudflare worker name (= storefront repo basename
11
- // by convention; passed by the central CI workflows)
12
- // OUTPUT_PATH - where to write the merged wrangler.jsonc
13
- // (e.g. "./wrangler.jsonc" in the caller checkout)
14
-
15
- import { writeFileSync } from "node:fs";
16
- import { resolve } from "node:path";
17
- import { applyWorkerName, loadTemplate } from "./site-registry.mjs";
18
-
19
- function fail(message) {
20
- console.error(`::error::${message}`);
21
- process.exit(1);
22
- }
23
-
24
- const decoStartPath = process.env.DECO_START_PATH;
25
- const workerName = process.env.WORKER_NAME;
26
- const outputPath = process.env.OUTPUT_PATH;
27
-
28
- if (!decoStartPath) fail("DECO_START_PATH env var is required");
29
- if (!workerName) fail("WORKER_NAME env var is required");
30
- if (!outputPath) fail("OUTPUT_PATH env var is required");
31
-
32
- let merged;
33
- try {
34
- merged = applyWorkerName(loadTemplate(decoStartPath), workerName);
35
- } catch (err) {
36
- fail(err instanceof Error ? err.message : String(err));
37
- }
38
-
39
- const header = `// AUTOGENERATED by @decocms/start at deploy time.
40
- // Do not edit -- changes will be overwritten on the next deploy.
41
- // Source: decocms/deco-start deploy/wrangler-template.jsonc + worker name "${workerName}"
42
- `;
43
-
44
- const body = JSON.stringify(merged, null, 2);
45
- writeFileSync(resolve(outputPath), `${header}${body}\n`);
46
-
47
- console.log(`Wrote ${outputPath} (worker "${workerName}")`);
@@ -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
- }
@@ -1,95 +0,0 @@
1
- // Template loader + token substitution for the canonical wrangler config.
2
- //
3
- // There is no per-site "registry" anymore: every site's wrangler config is
4
- // produced from `deploy/wrangler-template.jsonc` plus the worker name (which
5
- // equals the storefront repo basename by convention). To accommodate fields
6
- // that must vary deterministically per worker, the template can use these
7
- // substitution tokens, which are replaced at config-build time:
8
- //
9
- // $WORKER_NAME -> worker name verbatim (e.g. "als-tanstack")
10
- // $WORKER_UNDERSCORE -> worker name, `-` -> `_` (e.g. "als_tanstack")
11
- //
12
- // Trust model: callers cannot pass a fabricated worker name to the central
13
- // CI workflows -- the deploy is gated by the `decocms-deployer` GitHub App
14
- // being installed on the target storefront repo. If the App isn't installed
15
- // there, the App-token mint fails and the deploy never starts.
16
-
17
- import { existsSync } from "node:fs";
18
- import { join } from "node:path";
19
- import { readJsoncFile } from "./jsonc.mjs";
20
-
21
- /**
22
- * @param {string} decoStartPath
23
- * @returns {string}
24
- */
25
- export function templatePath(decoStartPath) {
26
- return join(decoStartPath, "deploy", "wrangler-template.jsonc");
27
- }
28
-
29
- /**
30
- * @param {string} decoStartPath
31
- * @returns {Record<string, unknown>}
32
- */
33
- export function loadTemplate(decoStartPath) {
34
- const path = templatePath(decoStartPath);
35
- if (!existsSync(path)) {
36
- throw new Error(`wrangler-template.jsonc not found at ${path}.`);
37
- }
38
- const raw = readJsoncFile(path);
39
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
40
- throw new Error(`Template at ${path} must be a JSON object.`);
41
- }
42
- return /** @type {Record<string, unknown>} */ (raw);
43
- }
44
-
45
- /**
46
- * Recursively replace `$WORKER_*` tokens in any string value of an
47
- * object/array tree. Returns a new tree.
48
- *
49
- * @param {unknown} value
50
- * @param {Record<string, string>} replacements
51
- * @returns {unknown}
52
- */
53
- function substituteTokens(value, replacements) {
54
- if (typeof value === "string") {
55
- let out = value;
56
- for (const [token, repl] of Object.entries(replacements)) {
57
- out = out.split(token).join(repl);
58
- }
59
- return out;
60
- }
61
- if (Array.isArray(value)) {
62
- return value.map((v) => substituteTokens(v, replacements));
63
- }
64
- if (value && typeof value === "object") {
65
- const out = /** @type {Record<string, unknown>} */ ({});
66
- for (const [k, v] of Object.entries(value)) {
67
- out[k] = substituteTokens(v, replacements);
68
- }
69
- return out;
70
- }
71
- return value;
72
- }
73
-
74
- /**
75
- * Produce the wrangler config object by substituting `$WORKER_*` tokens in
76
- * the template and prepending `name`.
77
- *
78
- * @param {Record<string, unknown>} template
79
- * @param {string} workerName
80
- * @returns {Record<string, unknown>}
81
- */
82
- export function applyWorkerName(template, workerName) {
83
- if (typeof workerName !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(workerName)) {
84
- throw new Error(
85
- `Invalid worker name: ${JSON.stringify(workerName)}. Use lowercase, hyphen-separated.`,
86
- );
87
- }
88
- const substituted = /** @type {Record<string, unknown>} */ (
89
- substituteTokens(template, {
90
- $WORKER_UNDERSCORE: workerName.replace(/-/g, "_"),
91
- $WORKER_NAME: workerName,
92
- })
93
- );
94
- return { name: workerName, ...substituted };
95
- }
@@ -1,118 +0,0 @@
1
- #!/usr/bin/env node
2
- // deco-wrangler
3
- //
4
- // Local-dev wrapper around `wrangler` for storefront repos that no longer
5
- // commit a `wrangler.jsonc`. Generates the canonical config from
6
- // `@decocms/start`'s template into `./wrangler.jsonc` (gitignored), then
7
- // optionally execs the real `wrangler` with that config in cwd.
8
- //
9
- // Usage from a customer repo (after `npm i -D @decocms/start@latest`):
10
- //
11
- // npx deco-wrangler gen # generate ./wrangler.jsonc and exit
12
- // # (used by predev/prebuild/prepare hooks)
13
- // npx deco-wrangler tail # tail prod logs (worker name resolved automatically)
14
- // npx deco-wrangler types # regenerate worker-configuration.d.ts
15
- // npx deco-wrangler deploy # discouraged in dev; deploys go via CI
16
- // npx deco-wrangler --help # passes through to wrangler
17
- //
18
- // All non-`gen` invocations also (re)generate ./wrangler.jsonc first so the
19
- // file is always in sync with the template before wrangler runs. The
20
- // `@cloudflare/vite-plugin` and the `wrangler` CLI both auto-discover
21
- // ./wrangler.jsonc in cwd, so no extra config plumbing is required.
22
- //
23
- // Worker name resolves in this order:
24
- //
25
- // 1. DECO_WORKER_NAME env var (explicit override)
26
- // 2. git remote `origin` URL parsed for the GitHub repo basename
27
- // 3. package.json `name` field
28
- //
29
- // By convention worker name == storefront repo basename. The CI deploys use
30
- // the same convention. Local dev never deploys -- this wrapper is purely for
31
- // generating a config that matches what the central workflow will produce.
32
-
33
- import { execSync, spawnSync } from "node:child_process";
34
- import { readFileSync, writeFileSync } from "node:fs";
35
- import { dirname, resolve } from "node:path";
36
- import { fileURLToPath } from "node:url";
37
- import { applyWorkerName, loadTemplate } from "./site-registry.mjs";
38
-
39
- const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
40
- // scripts/deploy/wrangler-wrapper.mjs -> deco-start root
41
- const DECO_START_PATH = resolve(SCRIPT_DIR, "..", "..");
42
-
43
- function eprintln(msg) {
44
- process.stderr.write(`[deco-wrangler] ${msg}\n`);
45
- }
46
-
47
- function tryGitRemoteWorkerName() {
48
- try {
49
- const url = execSync("git remote get-url origin", {
50
- stdio: ["ignore", "pipe", "ignore"],
51
- })
52
- .toString()
53
- .trim();
54
- // matches both git@github.com:org/repo(.git) and https://github.com/org/repo(.git)
55
- const m = url.match(/github\.com[:/][^/]+\/([^/]+?)(?:\.git)?$/);
56
- return m?.[1];
57
- } catch {
58
- return undefined;
59
- }
60
- }
61
-
62
- function tryPackageJsonName() {
63
- try {
64
- const pkg = JSON.parse(readFileSync(resolve(process.cwd(), "package.json"), "utf8"));
65
- return typeof pkg.name === "string" ? pkg.name : undefined;
66
- } catch {
67
- return undefined;
68
- }
69
- }
70
-
71
- function resolveWorkerName() {
72
- const envName = process.env.DECO_WORKER_NAME?.trim();
73
- if (envName) return { name: envName, source: "DECO_WORKER_NAME env var" };
74
- const gitName = tryGitRemoteWorkerName();
75
- if (gitName) return { name: gitName, source: "git remote origin" };
76
- const pkgName = tryPackageJsonName();
77
- if (pkgName) return { name: pkgName, source: "package.json name" };
78
- return undefined;
79
- }
80
-
81
- const resolved = resolveWorkerName();
82
- if (!resolved) {
83
- eprintln(
84
- "Could not determine worker name. Set DECO_WORKER_NAME or run from a repo with a github.com 'origin' remote.",
85
- );
86
- process.exit(1);
87
- }
88
-
89
- let merged;
90
- try {
91
- merged = applyWorkerName(loadTemplate(DECO_START_PATH), resolved.name);
92
- } catch (err) {
93
- eprintln(err instanceof Error ? err.message : String(err));
94
- eprintln(`Worker name "${resolved.name}" was inferred from ${resolved.source}.`);
95
- process.exit(1);
96
- }
97
-
98
- const outputPath = resolve(process.cwd(), "wrangler.jsonc");
99
- const header = `// AUTOGENERATED by deco-wrangler -- DO NOT COMMIT.
100
- // Add wrangler.jsonc to .gitignore. Regenerated on every \`deco-wrangler\` run.
101
- // Source: @decocms/start deploy/wrangler-template.jsonc + worker name "${resolved.name}"
102
- `;
103
- writeFileSync(outputPath, `${header}${JSON.stringify(merged, null, 2)}\n`);
104
-
105
- eprintln(`Resolved worker name "${resolved.name}" (via ${resolved.source})`);
106
- eprintln(`Generated ${outputPath}`);
107
-
108
- const argv = process.argv.slice(2);
109
- // `gen` mode: just write the config and exit. Used by package.json
110
- // predev/prebuild/prepare hooks to keep wrangler.jsonc in sync with the
111
- // template without invoking wrangler itself.
112
- if (argv[0] === "gen" || argv[0] === "generate") {
113
- process.exit(0);
114
- }
115
-
116
- const wranglerArgs = ["wrangler", ...argv];
117
- const result = spawnSync("npx", wranglerArgs, { stdio: "inherit" });
118
- process.exit(result.status ?? 1);