chore(release): v0.1.1 #2
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |