@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.
package/src/sdk/logger.ts CHANGED
@@ -127,11 +127,25 @@ function shouldLog(level: LogLevel): boolean {
127
127
  // ---------------------------------------------------------------------------
128
128
 
129
129
  /**
130
- * Mirrors `@deco/deco/o11y` logger:
131
- * - first arg is the message
130
+ * Strict structured logger. Mirrors `@deco/deco/o11y`:
131
+ * - first arg is a human-readable message string
132
132
  * - optional second arg is a flat attributes object
133
133
  *
134
- * Adapters decide the destination (stdout JSON, OTLP, both, …).
134
+ * Adapters decide the destination (stdout JSON, OTLP, both, …). The
135
+ * contract is intentionally narrow so structured output stays predictable
136
+ * across all sinks.
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * logger.info("checkout started", { orderFormId, items });
141
+ * logger.warn("retrying vtex call", { attempt, host });
142
+ *
143
+ * // For Errors, serialize explicitly into the attrs payload:
144
+ * try { ... } catch (err) {
145
+ * const e = serializeError(err);
146
+ * logger.error(e.message, { error: e, stage: "checkout" });
147
+ * }
148
+ * ```
135
149
  */
136
150
  export interface Logger {
137
151
  debug(msg: string, attrs?: Record<string, unknown>): void;
@@ -140,6 +154,47 @@ export interface Logger {
140
154
  error(msg: string, attrs?: Record<string, unknown>): void;
141
155
  }
142
156
 
157
+ /**
158
+ * Normalised, JSON-safe error shape suitable for inclusion in logger
159
+ * attributes. `serializeError` always returns this shape regardless of
160
+ * what was thrown.
161
+ */
162
+ export interface SerializedError {
163
+ name: string;
164
+ message: string;
165
+ stack?: string;
166
+ }
167
+
168
+ /**
169
+ * Convert any thrown value into a flat, structured object that survives
170
+ * `JSON.stringify` and round-trips cleanly to OTel / Cloudflare Logs.
171
+ * Strict logger sites should call this from their catch blocks rather
172
+ * than passing the Error directly.
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * try { ... } catch (err) {
177
+ * const e = serializeError(err);
178
+ * logger.error(e.message, { error: e });
179
+ * }
180
+ * ```
181
+ */
182
+ export function serializeError(err: unknown): SerializedError {
183
+ if (err instanceof Error) {
184
+ return { name: err.name, message: err.message, stack: err.stack };
185
+ }
186
+ if (err && typeof err === "object") {
187
+ let body: string;
188
+ try {
189
+ body = JSON.stringify(err);
190
+ } catch {
191
+ body = String(err);
192
+ }
193
+ return { name: "NonError", message: body };
194
+ }
195
+ return { name: "NonError", message: String(err) };
196
+ }
197
+
143
198
  function emit(level: LogLevel, msg: string, attrs?: Record<string, unknown>): void {
144
199
  if (!shouldLog(level)) return;
145
200
  try {
@@ -31,7 +31,7 @@
31
31
  * export default defineConfig({ plugins: [decoVitePlugin(), ...] });
32
32
  * ```
33
33
  */
34
- import { readFileSync, existsSync } from "node:fs";
34
+ import { existsSync, readFileSync } from "node:fs";
35
35
 
36
36
  // Bare-specifier stubs resolved by ID before Vite touches them.
37
37
  /** @type {Record<string, string>} */
@@ -44,6 +44,23 @@ const CLIENT_STUBS = {
44
44
  "tanstack-start-injected-head-scripts:v": "\0stub:tanstack-head-scripts",
45
45
  };
46
46
 
47
+ // SSR-only stubs. Same mechanism as CLIENT_STUBS but applied to the worker
48
+ // SSR build instead of the browser build.
49
+ /** @type {Record<string, string>} */
50
+ const SSR_STUBS = {
51
+ // `@opentelemetry/resources` (transitively pulled in by sdk-logs /
52
+ // sdk-metrics / exporter-* OTel packages — five copies in node_modules due
53
+ // to OTel monorepo peer-dep version pinning) statically imports bare `fs`
54
+ // inside its node-platform machine-id detectors. We never call those
55
+ // detectors — `instrumentWorker` builds the OTel Resource from explicit
56
+ // attributes only — but Vite's CF Workers SSR resolver still walks the
57
+ // re-export barrel and chokes on the bare `fs` specifier (workerd's
58
+ // `nodejs_compat` only exposes the prefixed `node:fs`, not the legacy
59
+ // bare form). Stub it; the static import resolves and the unreachable
60
+ // detector code is never executed.
61
+ fs: "\0stub:bare-fs",
62
+ };
63
+
47
64
  // Minimal stub source for each virtual module.
48
65
  /** @type {Record<string, string>} */
49
66
  const STUB_SOURCE = {
@@ -72,13 +89,18 @@ const STUB_SOURCE = {
72
89
  "export default { AsyncLocalStorage: _ALS, AsyncResource, executionAsyncId, createHook };",
73
90
  ].join("\n"),
74
91
 
75
- "\0stub:tanstack-head-scripts":
76
- "export const injectedHeadScripts = undefined;",
92
+ "\0stub:tanstack-head-scripts": "export const injectedHeadScripts = undefined;",
77
93
 
78
94
  // The admin schema bundle is server-only — the client receives pre-resolved
79
95
  // blocks via the SSR payload. Stubbing it on the client cuts a large module
80
96
  // (typically 0.5-5 MB) out of the browser bundle.
81
97
  "\0stub:meta-gen": "export default {};",
98
+
99
+ // Bare `fs` shim — see SSR_STUBS comment above for the rationale. Surfaces
100
+ // just enough of `import { promises as fs } from 'fs'` to satisfy static
101
+ // module resolution; method calls would throw, but the OTel detector code
102
+ // path is unreachable from `instrumentWorker`.
103
+ "\0stub:bare-fs": "export const promises = {}; export default { promises };",
82
104
  };
83
105
 
84
106
  /** @returns {import("vite").PluginOption} */
@@ -89,6 +111,9 @@ export function decoVitePlugin() {
89
111
  enforce: "pre",
90
112
 
91
113
  resolveId(id, importer, options) {
114
+ // SSR-only stubs — must be checked first since the client guard below
115
+ // returns undefined for everything that hasn't matched yet on SSR.
116
+ if (options?.ssr && SSR_STUBS[id]) return SSR_STUBS[id];
92
117
  // Server builds keep the real modules.
93
118
  if (options?.ssr) return undefined;
94
119
  // Bare-specifier exact-match stubs (react-dom/server, node:stream, etc.).
@@ -98,10 +123,7 @@ export function decoVitePlugin() {
98
123
  // plugin works whether `setup.ts` imports the .json directly (current)
99
124
  // or a future variant routes through a generated .ts wrapper.
100
125
  // Requires `importer` so we don't accidentally stub the entry module.
101
- if (
102
- importer &&
103
- (id.endsWith("meta.gen.json") || id.endsWith("meta.gen.ts"))
104
- ) {
126
+ if (importer && (id.endsWith("meta.gen.json") || id.endsWith("meta.gen.ts"))) {
105
127
  return "\0stub:meta-gen";
106
128
  }
107
129
  return undefined;
@@ -154,7 +176,6 @@ export function decoVitePlugin() {
154
176
  const siteName = process.env.DECO_SITE_NAME;
155
177
  const envName = process.env.DECO_ENV_NAME;
156
178
  if (siteName && envName) {
157
-
158
179
  // Daemon files are .ts and live inside node_modules. Node's
159
180
  // experimental strip-types refuses to transpile node_modules, so
160
181
  // a plain dynamic `import()` blows up under `vite dev`. Use tsx's
@@ -214,18 +235,12 @@ export function decoVitePlugin() {
214
235
  rollupOptions: {
215
236
  output: {
216
237
  manualChunks(id) {
217
- if (
218
- id.includes("node_modules/react-dom") ||
219
- id.includes("node_modules/react/")
220
- ) {
238
+ if (id.includes("node_modules/react-dom") || id.includes("node_modules/react/")) {
221
239
  return "vendor-react";
222
240
  }
223
241
 
224
242
  // TanStack Router — client-side router (always needed)
225
- if (
226
- id.includes("@tanstack/react-router") ||
227
- id.includes("@tanstack/router-core")
228
- ) {
243
+ if (id.includes("@tanstack/react-router") || id.includes("@tanstack/router-core")) {
229
244
  return "vendor-router";
230
245
  }
231
246
 
@@ -275,8 +290,7 @@ export function decoVitePlugin() {
275
290
  configEnvironment(name, env) {
276
291
  if (name === "ssr" || name === "client") {
277
292
  env.optimizeDeps = env.optimizeDeps || {};
278
- env.optimizeDeps.esbuildOptions =
279
- env.optimizeDeps.esbuildOptions || {};
293
+ env.optimizeDeps.esbuildOptions = env.optimizeDeps.esbuildOptions || {};
280
294
  env.optimizeDeps.esbuildOptions.jsx = "automatic";
281
295
  env.optimizeDeps.esbuildOptions.jsxImportSource = "react";
282
296
  }
@@ -0,0 +1,54 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { decoVitePlugin } from "./plugin.js";
3
+
4
+ /**
5
+ * The Vite plugin's `resolveId` / `load` hooks are pure functions over their
6
+ * inputs, so we can exercise them without spinning up a Vite environment.
7
+ */
8
+
9
+ function getPlugin() {
10
+ const result = decoVitePlugin();
11
+ // decoVitePlugin returns a single plugin object today, but the type is
12
+ // `PluginOption` which permits arrays — handle both.
13
+ return Array.isArray(result) ? result[0] : result;
14
+ }
15
+
16
+ describe("decoVitePlugin SSR stubs", () => {
17
+ it("rewrites bare `fs` to a virtual module on SSR", () => {
18
+ const p = getPlugin();
19
+ const id = p.resolveId.call({}, "fs", undefined, { ssr: true });
20
+ expect(id).toBe("\0stub:bare-fs");
21
+ });
22
+
23
+ it("does NOT rewrite bare `fs` on client (browser builds don't import fs)", () => {
24
+ const p = getPlugin();
25
+ const id = p.resolveId.call({}, "fs", undefined, { ssr: false });
26
+ expect(id).toBeUndefined();
27
+ });
28
+
29
+ it("loads an empty surface for the bare-fs virtual module", () => {
30
+ const p = getPlugin();
31
+ const src = p.load.call({}, "\0stub:bare-fs", { ssr: true });
32
+ expect(src).toContain("export const promises = {}");
33
+ expect(src).toContain("export default");
34
+ });
35
+
36
+ it("does not interfere with real SSR modules", () => {
37
+ const p = getPlugin();
38
+ expect(p.resolveId.call({}, "@decocms/start/cms", undefined, { ssr: true })).toBeUndefined();
39
+ });
40
+ });
41
+
42
+ describe("decoVitePlugin client stubs (regression guard)", () => {
43
+ it("still rewrites node:async_hooks on the client build", () => {
44
+ const p = getPlugin();
45
+ const id = p.resolveId.call({}, "node:async_hooks", undefined, { ssr: false });
46
+ expect(id).toBe("\0stub:node-async-hooks");
47
+ });
48
+
49
+ it("does not rewrite client stubs on SSR", () => {
50
+ const p = getPlugin();
51
+ const id = p.resolveId.call({}, "react-dom/server", undefined, { ssr: true });
52
+ expect(id).toBeUndefined();
53
+ });
54
+ });
@@ -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"