on master help sign in
Add release workflow: build on every push to master, version = commit time

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LdKDAYoqmGehyHsV42LhJW

commitab0f7da0f504731913a40e71494e8d6e69f30e88 authorClaude <noreply@anthropic.com> dateWed 23 Sep 2026, 16:26 (2 weeks ago) parent0a83dd8
3 files changed+73
.github/workflows/release.yml+63 README.md+4 scripts/build.mjs+6
.github/workflows/release.ymlnew file+63
@@ -0,0 +1,63 @@
1+name: Release
2+
3+# Every push to master builds the extension and publishes it as a GitHub release.
4+# The version is the commit's timestamp in UTC, YYYY.MM.DD.HHMM, used as the tag and release name.
5+on:
6+ push:
7+ branches: [master]
8+
9+permissions:
10+ contents: write
11+
12+concurrency:
13+ group: release-${{ github.sha }}
14+
15+jobs:
16+ release:
17+ runs-on: ubuntu-latest
18+ steps:
19+ - uses: actions/checkout@v4
20+
21+ - uses: actions/setup-node@v4
22+ with:
23+ node-version: 22
24+ cache: npm
25+
26+ - uses: actions/setup-python@v5
27+ with:
28+ python-version: "3.12"
29+
30+ - name: Install dependencies
31+ run: npm ci
32+
33+ - name: Version from commit time
34+ id: version
35+ run: |
36+ version=$(TZ=UTC git log -1 --format=%cd --date=format-local:%Y.%m.%d.%H%M "$GITHUB_SHA")
37+ echo "version=$version" >> "$GITHUB_OUTPUT"
38+ echo "Version $version"
39+
40+ - name: Typecheck and test
41+ run: npm run check
42+
43+ - name: Build
44+ env:
45+ DIAGONAL_VERSION: ${{ steps.version.outputs.version }}
46+ run: |
47+ npm run build
48+ (cd dist && zip -qr "../diagonal-extension-${DIAGONAL_VERSION}.zip" .)
49+ cat dist/manifest.json | head -5
50+
51+ - name: Publish release
52+ env:
53+ GH_TOKEN: ${{ github.token }}
54+ VERSION: ${{ steps.version.outputs.version }}
55+ run: |
56+ asset="diagonal-extension-${VERSION}.zip"
57+ notes="Built from ${GITHUB_SHA::7}. Unzip and load the folder at brave://extensions (Developer mode → Load unpacked)."
58+ if gh release view "$VERSION" >/dev/null 2>&1; then
59+ # Two commits in the same minute share a version: refresh the asset on the existing release.
60+ gh release upload "$VERSION" "$asset" --clobber
61+ else
62+ gh release create "$VERSION" "$asset" --target "$GITHUB_SHA" --title "Diagonal $VERSION" --notes "$notes"
63+ fi
README.md+4
@@ -85,6 +85,10 @@ Layout follows the spec: `src/background/` (engine, naming, organize, tidy, host
8585
8686`host/emoji.txt` is the single emoji list. `npm run gen-emoji` regenerates `src/shared/emoji.gen.ts` from it.
8787
88+### Releases
89+
90+Every push to `master` runs `.github/workflows/release.yml`: it typechecks, runs both test suites, builds with the manifest `version` set to the commit's UTC time as `YYYY.MM.DD.HHMM`, and publishes a GitHub release tagged with that version, with `diagonal-extension-<version>.zip` attached. Two commits in the same minute share a release; the later one replaces its zip. Locally, `DIAGONAL_VERSION=2026.09.23.1619 npm run build` produces the same stamped build.
91+
8892### Pinning a different extension ID
8993
9094`scripts/gen-key.sh` creates `key.pem` if it is missing, writes the public key into `manifest.json`, the ID into `extension-id` and into `ALLOWED_ORIGIN` in `host/diagonal-host.py`, and prints the ID. The shipped ID comes from a key that is not included; you only need the private key to pack a `.crx`. Running the script without a `key.pem` makes a new key and a new ID, so after that rebuild and rerun the host installer.
scripts/build.mjs+6
@@ -24,6 +24,12 @@ function copyStatic() {
2424 console.warn("manifest.json has no pinned key yet: run scripts/gen-key.sh (the extension ID will not be stable)");
2525 delete manifest.key;
2626 }
27+ const stamp = process.env.DIAGONAL_VERSION;
28+ if (stamp) {
29+ // Release builds pass the commit time as YYYY.MM.DD.HHMM (see .github/workflows/release.yml).
30+ if (!/^\d{4}\.\d{2}\.\d{2}\.\d{4}$/.test(stamp)) throw new Error(`DIAGONAL_VERSION must be YYYY.MM.DD.HHMM, got ${stamp}`);
31+ manifest.version = stamp;
32+ }
2733 writeFileSync(join(dist, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
2834 cpSync(join(root, "src/popup/popup.html"), join(dist, "popup.html"));
2935 cpSync(join(root, "src/options/options.html"), join(dist, "options.html"));