forked from noodlapp/noodl
-
Notifications
You must be signed in to change notification settings - Fork 17
197 lines (182 loc) · 8.6 KB
/
Copy pathrelease.yml
File metadata and controls
197 lines (182 loc) · 8.6 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
name: Release
# Signed, published release build for all three platforms, triggered by a
# version tag (e.g. `v0.1.0`). Artifacts are published to GitHub Releases as a
# DRAFT (see `releaseType: draft` in packages/noodl-editor/package.json), so a
# human confirms and publishes before anything reaches users or the auto-update
# feed. See dev-docs/guidelines/RELEASE-PROCESS.md.
#
# Signing/notarisation are gated on repository secrets. If a secret is absent the
# build still succeeds but that platform's artifact is UNSIGNED (macOS: not
# notarised → Gatekeeper warns; Windows: SmartScreen blocks). The draft gate
# means an unsigned artifact can never be shipped by accident — a human sees it
# first. Required secrets are documented in RELEASE-PROCESS.md.
on:
push:
tags:
- 'v*.*.*'
# Manual dispatch for dry-runs. Publishes a draft from whatever ref is chosen;
# the release is named from package.json's version, not the ref.
workflow_dispatch:
concurrency:
# One release run at a time; do NOT cancel in progress — a half-published
# release is worse than a slow one.
group: release
cancel-in-progress: false
permissions:
contents: write # electron-builder needs this to create/update the GitHub Release.
jobs:
release:
name: ${{ matrix.platform }}
runs-on: ${{ matrix.os }}
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
platform: linux-x64
- os: windows-latest
platform: win32-x64
- os: macos-latest
platform: darwin-arm64
# macos-13, the previous Intel image, was retired by GitHub (Dec 2025);
# jobs targeting it queue for 24h and are cancelled — that is what
# "hung" the v0.1.0 darwin-x64 leg. macos-15-intel is the supported
# Intel continuation, keeping natives (dugite git, trampoline) x64.
- os: macos-15-intel
platform: darwin-x64
steps:
- uses: actions/checkout@v4
# electron-builder shells out to python on some paths.
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- uses: ./.github/actions/setup
- name: Build viewer bundles
run: npm run build:editor:_viewer
env:
WORKSPACE_PATH: .
# Everything electron-builder copies via `extraResources`: the backend
# service and the two MCP servers. This path does NOT go through
# scripts/build-editor.ts (which is where those builds used to live), and
# electron-builder only *warns* when an extraResources source is missing —
# so without this step the published app shipped without them and the
# build was green. `build:sidecars` ends in `check-build-artefacts.js
# --built`, which fails the release instead. (MCP-002)
- name: Build packaged sidecars
run: npm run build:sidecars
- name: Build, sign, and publish the editor
run: npm run build:editor:_editor
env:
WORKSPACE_PATH: .
TARGET_PLATFORM: ${{ matrix.platform }}
# Turn signing ON and publishing ON for the release path.
DISABLE_SIGNING: false
PUBLISH_RELEASE: true
# Token electron-builder uses to create/update the draft GitHub Release.
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# --- macOS signing (Developer ID) + notarisation ---
# CSC_LINK: base64-encoded .p12, CSC_KEY_PASSWORD: its password.
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
# --- Windows signing ---
WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
# The two macOS legs each publish a `latest-mac.yml`; whichever uploads
# last wins, so the auto-update feed would only serve one architecture
# (REV-007 known issue; DEBT-007). Preserve each leg's feed file as a
# build artifact so the merge job below can combine them.
# `if-no-files-found: error` is deliberate and costs no false reds: if the
# build step above failed, this step is skipped entirely (the job is
# already red). It fires only when the build *succeeded* and produced no
# feed — which the merge job below would then quietly paper over.
- name: Save per-arch mac update feed
if: startsWith(matrix.platform, 'darwin-')
uses: actions/upload-artifact@v4
with:
name: mac-feed-${{ matrix.platform }}
path: packages/noodl-editor/dist/latest-mac.yml
if-no-files-found: error
# Rebuild latest-mac.yml so its `files` list covers BOTH architectures —
# electron-updater picks the entry matching the running arch. Only meaningful
# on tag pushes (dispatch dry-runs may have no release to upload to).
merge-mac-update-feed:
name: merge mac update feed
needs: release
if: always() && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Download per-arch feeds
uses: actions/download-artifact@v4
with:
pattern: mac-feed-*
path: feeds
- name: Merge feeds
id: merge
run: |
python3 - <<'EOF'
import glob, sys, yaml
paths = sorted(glob.glob('feeds/*/latest-mac.yml'))
if len(paths) < 2:
# This used to exit 0. A green "nothing to merge" on a tag build is
# the quietest possible way to ship a single-architecture update
# feed: every Mac on the other arch stops receiving updates, with
# no symptom on the machine that cut the release.
print(f'ERROR: only {len(paths)} per-arch mac feed(s) found; both are required.')
print('One of the darwin legs did not produce a latest-mac.yml. Read its log before retagging.')
sys.exit(1)
feeds = [yaml.safe_load(open(p)) for p in paths]
merged = feeds[0]
seen = {f['url'] for f in merged.get('files', [])}
for other in feeds[1:]:
for f in other.get('files', []):
if f['url'] not in seen:
merged['files'].append(f)
seen.add(f['url'])
with open('latest-mac.yml', 'w') as out:
yaml.safe_dump(merged, out, sort_keys=False)
print(open('latest-mac.yml').read())
open('merged.flag', 'w').write('yes')
EOF
- name: Upload merged feed to the draft release
run: |
if [ -f merged.flag ]; then
gh release upload "${GITHUB_REF_NAME}" latest-mac.yml --clobber --repo "${GITHUB_REPOSITORY}"
else
echo "Skipping upload - merged feed not produced."
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# F73's general lesson, made into a job: **electron-builder uploads as it goes,
# so a draft with files in it is not evidence of a green run.** The v0.1.0
# Linux leg uploaded its AppImage and then aborted on the `.deb`; the release
# was read as "succeeded, no update feed" for over a week. `fail-fast: false`
# means a failed leg does go red, and that is the only signal there was — one
# nobody looks at when the draft in front of them has files in it.
#
# `if: always()` is load-bearing: the point is to run precisely when something
# above failed, and name what is missing from the draft rather than leaving it
# to be discovered on a tester's machine.
verify-release-assets:
name: verify draft release is complete
needs: [release, merge-mac-update-feed]
if: always() && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Fetch the draft's asset list and update feed
run: |
gh release view "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}" --json assets > assets.json
# Best-effort: a missing feed is one of the things being asserted, so
# a failed download must not fail the step before the check runs.
gh release download "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}" \
--pattern 'latest-mac.yml' --clobber || echo "latest-mac.yml not downloadable"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Assert every expected artifact is on the draft
run: node scripts/check-release-assets.js --assets assets.json --mac-feed latest-mac.yml