-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchrome-extension-create-package.yml
More file actions
199 lines (186 loc) · 9.26 KB
/
Copy pathchrome-extension-create-package.yml
File metadata and controls
199 lines (186 loc) · 9.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
name: "Chrome extension: Create Package (reusable)"
# Reusable workflow of the chrome-extension-release standard — the logic behind
# every extension repo's release "create-package" job. Vendored into the
# extension repo by the chrome-extension-release pack (not hosted in Claudinite);
# the repo's single `chrome-extension-release.yml` orchestrator calls it locally
# (uses: ./.github/workflows/chrome-extension-create-package.yml) and passes NO
# repo values. workflow_call ONLY. All repo-specific values come from the
# caller's optional `.github/release.config` (read by the read-release-config
# composite action) plus the standard's defaults — the zip name is derived from
# the repo name, so a single-package repo needs no config at all.
#
# Builds and publishes a GitHub Release of the extension at the version that is
# currently committed in the extension manifest. It does NOT change the version —
# bumping is a deliberate, human step (ask Claude to "bump version"; it edits the
# manifest and package.json on a branch and lands on main via a normal PR — see
# the chrome-extension release standard in Claudinite).
#
# If the manifest version already matches the latest published release, the run
# is a clean no-op — so pushes to main that aren't version bumps don't cut a
# release. (The orchestrator triggers this on every push to main and relies on
# that no-op, so it needs no per-repo manifest path in its trigger.)
#
# The `ref` input exists for the daily auto-release
# (chrome-extension-daily-release.yml), which calls this after pushing its
# patch-bump commit: that run was triggered before the bump commit existed, so
# the default checkout (github.sha) would build the pre-bump tree and no-op on
# the old version.
#
# The calling job must grant `permissions: contents: write, issues: write`
# (release publishing + the failure reporter).
on:
workflow_call:
inputs:
ref:
description: "Commit to release (blank = the commit that triggered the run)"
required: false
type: string
build_env:
description: >-
Optional KEY=VALUE lines exported to the test/build steps (a repo that
bakes release config from repository variables into its build passes
them here). Every listed key must have a non-empty value or the run
fails — so a mis-configured zip can't ship.
required: false
type: string
default: ""
permissions:
contents: write
# The implicit run shell has no pipefail; several steps pipe.
defaults:
run:
shell: bash
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
# Empty (every caller but the daily-release) = the default
# github.sha checkout.
ref: ${{ inputs.ref }}
# Resolve manifest_path / package_json_path / setup_command / test_command /
# build_command / zip_path from the caller's .github/release.config (all
# optional) + defaults; zip_name is derived from the repo name.
- id: cfg
uses: ./.github/actions/read-release-config
- uses: actions/setup-node@v5
with:
node-version: 22
# npm caching needs a lockfile; a repo that skips install has none.
cache: ${{ steps.cfg.outputs.setup_command != '' && 'npm' || '' }}
- name: Install dependencies
if: steps.cfg.outputs.setup_command != ''
run: ${{ steps.cfg.outputs.setup_command }}
# Export the caller's release config and refuse to proceed on an empty
# value — this is the generic form of "fail fast rather than ship a
# placeholder-configured zip to the store".
- name: Export and require the build env
if: inputs.build_env != ''
run: |
while IFS= read -r line; do
[ -z "$line" ] && continue
key="${line%%=*}"; value="${line#*=}"
if [ -z "$value" ]; then
echo "Required build env var '$key' is empty — set it (Settings → Secrets and variables → Actions → Variables) before releasing." >&2
exit 1
fi
echo "$key=$value" >> "$GITHUB_ENV"
done <<'BUILD_ENV_EOF'
${{ inputs.build_env }}
BUILD_ENV_EOF
# The version to release is whatever's committed in the manifest (the
# store's source of truth, kept in sync with package.json). When it already
# matches the latest published release (or a matching tag exists), there's
# nothing to release: skip cleanly (release=false) rather than fail, so a
# non-bump push doesn't turn the run red. A drift between the manifest and
# package.json, or a malformed version, is a real mistake and fails the run.
- name: Resolve and guard version
id: version
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MANIFEST_PATH: ${{ steps.cfg.outputs.manifest_path }}
PACKAGE_JSON_PATH: ${{ steps.cfg.outputs.package_json_path }}
run: |
version="$(node -p "require('./$MANIFEST_PATH').version")"
if ! echo "$version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "$MANIFEST_PATH version '$version' is not X.Y.Z." >&2
exit 1
fi
pkg_version="$(node -p "require('./$PACKAGE_JSON_PATH').version")"
if [ "$pkg_version" != "$version" ]; then
echo "$PACKAGE_JSON_PATH ($pkg_version) and $MANIFEST_PATH ($version) disagree — bump both together." >&2
exit 1
fi
latest="$(gh release view --json tagName -q .tagName 2>/dev/null | sed 's/^v//' || true)"
if { [ -n "$latest" ] && [ "$latest" = "$version" ]; } || git rev-parse "v$version" >/dev/null 2>&1; then
echo "v$version is already released; nothing to do." | tee -a "$GITHUB_STEP_SUMMARY"
echo "release=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "release=true" >> "$GITHUB_OUTPUT"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tag=v$version" >> "$GITHUB_OUTPUT"
echo "Releasing v$version" >> "$GITHUB_STEP_SUMMARY"
# The same gates CI runs on every PR.
- name: Run the full test gate
if: steps.version.outputs.release == 'true'
run: ${{ steps.cfg.outputs.test_command }}
# The build is universal across every extension repo (a monorepo wires a
# root `build` script that delegates), so it is mandated here, not a config
# key. TWO zips come off the one tested tree, differing ONLY in the API
# pointer that build_env's API_BASE_URL injects:
# * <zip>-prod.zip — the FULL injected config (API_BASE_URL → prod). This
# is the store's source of truth: the publish workflow downloads THIS
# asset. It is never the release's headline download.
# * <zip> — the SAME build with API_BASE_URL cleared, so config.mjs keeps
# its committed default (dev, by the standard's "no env ⇒ dev" rule). It
# is the release's headline asset and the permanent
# …/releases/latest/download/<zip> URL — so what a human downloads and
# loads unpacked talks to DEV, never prod; only the store submission is
# prod-pointed. (API_BASE_URL is the standard's one dev/prod pointer key;
# the signing key + client id stay injected in both, so the dev download
# keeps a stable id and working OAuth.)
- name: Build the release zips (dev headline download + prod store artifact)
id: build
if: steps.version.outputs.release == 'true'
env:
ZIP_NAME: ${{ steps.cfg.outputs.zip_name }}
run: |
npm run build
prod_zip="dist/${ZIP_NAME%.zip}-prod.zip"
mv "dist/$ZIP_NAME" "$prod_zip"
env -u API_BASE_URL npm run build
ls -l dist
echo "prod_zip=$prod_zip" >> "$GITHUB_OUTPUT"
# Publish the Release. action-gh-release creates the tag (vX.Y.Z) at the
# released commit when it doesn't exist — pinned explicitly to the commit
# we checked out and built, so a workflow_call `ref` is tagged rather than
# whatever main's HEAD is by then. That's a tag, not a push to main, so
# branch protection on main doesn't apply. Both zips are attached: the dev
# headline zip and the prod zip the store publish consumes.
- name: Publish GitHub Release
if: steps.version.outputs.release == 'true'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.tag }}
target_commitish: ${{ inputs.ref || github.sha }}
files: |
${{ steps.cfg.outputs.zip_path }}
${{ steps.build.outputs.prod_zip }}
generate_release_notes: true
# Auto-runs on a bump-merge to main with no human watching, so a silent failure
# here means the release never gets cut and the "bump version" flow stalls
# unnoticed. Surface it as a tracking issue. (A clean no-op release=false run is
# success, not failure, so it never reaches here.) The issue is keyed on the
# standard's job name, stable across repos and callers.
report-failure:
needs: release
if: failure()
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: ./.github/actions/report-failure
with:
workflow: "Release: Create Package"