-
Notifications
You must be signed in to change notification settings - Fork 0
153 lines (144 loc) · 8.06 KB
/
Copy pathrelease.yml
File metadata and controls
153 lines (144 loc) · 8.06 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
name: Build release apps
# Hybrid release model (see docs/RELEASING.md): CI does the heavy, awkward part — building BOTH
# macOS arches on their NATIVE runners (you can't easily build x64 on an Apple-silicon Mac) — with
# NO signing secrets. It produces unsigned .app bundles and attaches them to a DRAFT release. You
# then sign + notarize + staple LOCALLY (your Developer ID cert never leaves your machine), attach
# the notarized dmgs, delete the UNSIGNED-*.app.zip assets, and publish.
on:
push:
tags: ["v*"] # e.g. git tag v0.1.0 && git push --tags
workflow_dispatch:
inputs:
vscode_tag:
description: "Override the pinned VS Code tag (blank = scripts/bootstrap.sh default)"
required: false
default: ""
permissions:
contents: write # create/update the draft release
# Action pins run on the Node 24 runtime. GitHub deprecated Node 20 on the runners (2025-09-19) and
# was force-running the old v4 pins on Node 24 with a warning per step; these majors target node24
# natively, so the shim (and the warning) is gone. They need Actions Runner >= 2.327.1 — the hosted
# images are well past that (2.335.1 as of the v0.9.2 build) and self-update, so no action needed.
# The majors were chosen against how THIS workflow uses them, not blindly:
# · setup-node v6 limited automatic caching to npm — not used here; npm caching is the explicit
# actions/cache step below, so the change is a no-op for us.
# · checkout v7 blocks fork checkouts for pull_request_target/workflow_run — neither is a trigger.
# · upload-artifact v7 added unzipped "direct uploads" behind `archive:` — opt-in, default unchanged.
# · download-artifact v8 now ERRORS on a digest mismatch (was a warning). Deliberate: a corrupted
# app bundle must not reach a release we then sign and notarize. Recoverable by re-running the job.
jobs:
# Cheap gate: run the extension unit tests before spending ~1h of macOS build minutes.
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: "24"
- name: Extension unit tests
# `shopt` is a bash builtin, so pin the shell instead of relying on the runner default (bash on
# Linux/macOS, but pwsh on Windows — where this step would break if the job were ever copied).
# Explicit `shell: bash` also upgrades the default `bash -e` to
# `bash --noprofile --norc -eo pipefail`, so a stray profile file can't perturb the gate either.
shell: bash
# DISCOVERS suites — it used to `cd extensions/levelcode-ai`, so levelcode-updater's tests never
# ran here, including the one guarding the updater's Download button against serving a raw
# .app.zip. Globbing every extension means a new suite is gated the moment it is added, with no
# list here to keep in sync. Requires are file-relative, so running from the repo root is fine.
run: |
shopt -s nullglob
count=0
for t in extensions/*/test/*.test.js; do
echo "── $t"
node "$t" # `-e` (from `shell: bash` above) aborts the job on the first failure
count=$((count + 1))
done
# A zero-match glob would otherwise report success and gate nothing — the exact failure this
# step is fixing. Fail loudly instead.
if [ "$count" -eq 0 ]; then
echo "::error::No suites matched extensions/*/test/*.test.js — the gate would pass vacuously."
exit 1
fi
echo "──────── $count test files passed ────────"
build:
name: Build ${{ matrix.arch }}
needs: test
strategy:
fail-fast: false
matrix:
include:
- { runner: macos-14, arch: arm64 } # Apple Silicon (native)
# macos-15-intel is GitHub's last native x86_64 image (macos-13 was retired 2025-12-04;
# jobs still requesting it hang forever "Waiting for a runner…" until the 24h queue timeout).
# It's a premium/large runner (bills ~2× minutes) and Intel support ends Fall 2027 — after
# that the x64 build must cross-compile on an arm64 runner. See docs/RELEASING.md §7.
- { runner: macos-15-intel, arch: x64 } # Intel (native)
runs-on: ${{ matrix.runner }}
# Authenticate npm postinstall downloads (notably @vscode/ripgrep, which fetches a prebuilt binary
# from GitHub releases). Without a token they use GitHub's 60/hr ANONYMOUS limit and 403 on busy
# release days; GITHUB_TOKEN raises that to 5000/hr. Read-only default token — no extra perms needed.
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# No LevelCode build runs Playwright browser tests, but an upstream extension (copilot) pulls in
# playwright-core, whose postinstall downloads a Chromium from cdn.playwright.dev. That network hop
# flaked the x64 build (getaddrinfo ENOTFOUND) while arm64 passed the SAME commit — so skip it and
# the whole class of flake goes away. Same spirit as the GITHUB_TOKEN mitigation above.
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: "24" # bootstrap.sh checks the .nvmrc major; 24.x satisfies the 1.126 pin
- name: Cache npm downloads
uses: actions/cache@v6
with:
path: ~/.npm
key: npm-${{ matrix.arch }}-${{ hashFiles('scripts/bootstrap.sh') }}
restore-keys: npm-${{ matrix.arch }}-
- name: Bootstrap Code-OSS + branding + deps
run: ./scripts/bootstrap.sh
env:
VSCODE_TAG: ${{ github.event.inputs.vscode_tag }}
- name: Build LevelCode.app (${{ matrix.arch }}, proprietary stripped)
run: ./scripts/build-macos.sh ${{ matrix.arch }}
- name: Zip the unsigned app
run: ditto -c -k --sequesterRsrc --keepParent "VSCode-darwin-${{ matrix.arch }}/LevelCode.app" "UNSIGNED-LevelCode-${{ matrix.arch }}.app.zip"
- name: Upload app artifact
uses: actions/upload-artifact@v7
with:
name: UNSIGNED-LevelCode-${{ matrix.arch }}
path: UNSIGNED-LevelCode-${{ matrix.arch }}.app.zip
if-no-files-found: error
retention-days: 14
draft-release:
name: Draft release
needs: build
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8
with:
path: apps
merge-multiple: true
- name: Create / refresh the draft release with the UNSIGNED apps
uses: softprops/action-gh-release@v2
with:
draft: true
name: LevelCode ${{ github.ref_name }}
tag_name: ${{ github.ref_name }}
files: apps/*.zip
fail_on_unmatched_files: true
body: |
**Draft — not for release as-is.** The attached `UNSIGNED-LevelCode-<arch>.app.zip`
files are CI build artifacts with **no Developer ID signature or notarization**.
To finish the release **locally** (your signing cert never touches CI):
1. `gh release download ${{ github.ref_name }} --pattern 'UNSIGNED-*.app.zip'`
2. For each arch — unzip into `VSCode-darwin-<arch>/`, then
`CODESIGN_IDENTITY="Developer ID Application: …" NOTARY_PROFILE=levelcode-notary ./scripts/make-dmg.sh <arch>`
(signs → notarizes → staples → `LevelCode-<arch>.dmg` **and** `LevelCode-<arch>.app.zip`).
3. `gh release upload ${{ github.ref_name }} LevelCode-arm64.dmg LevelCode-x64.dmg LevelCode-arm64.app.zip LevelCode-x64.app.zip`
— the `.dmg`s are for humans, the `.app.zip`s are the auto-update feed assets (`docs/AUTO-UPDATE.md`).
Upload exactly these four; the `.app.zip.sha256` files `make-dmg.sh` writes stay **local**
(the feed reads GitHub's own asset `digest`, never a sidecar).
4. **Delete the `UNSIGNED-*.app.zip` assets**, add real notes, and publish.
Full runbook: `docs/RELEASING.md`.