Skip to content

Commit a01ab5d

Browse files
committed
feat(release): add nightly channel with channel-aware build pipeline
- introduce shared LightcodeChannel module with stable/nightly variants and config parity tests - add reusable _build workflow and release-nightly workflow; rename release.yml to "Release (stable)" - replace electron-builder.yml with channel-aware electron-builder.config.cjs and shared CJS helper - generate nightly icons via scripts/make-nightly-icon.mjs and bundle build/icon-nightly.* - thread channel through main/preload/renderer bridges, Sentry tags, PostHog properties, app name, and user data dir - wire channel into tsdown/vite builds and bump version to 0.9.5
1 parent 57082f8 commit a01ab5d

35 files changed

Lines changed: 982 additions & 323 deletions

.github/workflows/_build.yml

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
name: Build (reusable)
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
tag:
7+
description: "Tag to check out and build from (e.g. v1.0.1 or v1.0.1-nightly.202605160530)"
8+
required: true
9+
type: string
10+
channel:
11+
description: "Lightcode release channel (stable | nightly)"
12+
required: true
13+
type: string
14+
sentry_environment:
15+
description: "Value to send as SENTRY_ENVIRONMENT"
16+
required: false
17+
type: string
18+
default: production
19+
secrets:
20+
MAC_CSC_LINK:
21+
required: false
22+
MAC_CSC_KEY_PASSWORD:
23+
required: false
24+
APPLE_ID:
25+
required: false
26+
APPLE_APP_SPECIFIC_PASSWORD:
27+
required: false
28+
APPLE_TEAM_ID:
29+
required: false
30+
SENTRY_AUTH_TOKEN:
31+
required: false
32+
33+
permissions:
34+
contents: read
35+
36+
jobs:
37+
build:
38+
strategy:
39+
fail-fast: false
40+
matrix:
41+
include:
42+
- os: windows-latest
43+
artifact: dist-windows
44+
- os: ubuntu-latest
45+
artifact: dist-linux
46+
- os: macos-latest
47+
artifact: dist-mac
48+
49+
runs-on: ${{ matrix.os }}
50+
51+
steps:
52+
- name: Checkout tag
53+
uses: actions/checkout@v6
54+
with:
55+
ref: ${{ inputs.tag }}
56+
57+
- name: Setup pnpm
58+
uses: pnpm/action-setup@v6
59+
60+
- name: Setup Node.js
61+
uses: actions/setup-node@v6
62+
with:
63+
node-version: "24"
64+
cache: pnpm
65+
66+
- name: Cache Electron binary
67+
uses: actions/cache@v5
68+
with:
69+
path: ~/.cache/electron
70+
key: electron-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
71+
72+
- name: Install dependencies
73+
run: pnpm install --frozen-lockfile
74+
75+
- name: Build app
76+
run: pnpm run build
77+
shell: bash
78+
env:
79+
LIGHTCODE_CHANNEL: ${{ inputs.channel }}
80+
SENTRY_DSN: ${{ vars.SENTRY_DSN }}
81+
SENTRY_ENVIRONMENT: ${{ inputs.sentry_environment }}
82+
POSTHOG_ENABLED: ${{ vars.POSTHOG_ENABLED || '1' }}
83+
POSTHOG_KEY: ${{ vars.POSTHOG_KEY }}
84+
POSTHOG_HOST: ${{ vars.POSTHOG_HOST || 'https://us.i.posthog.com' }}
85+
POSTHOG_ENABLE_DEV: "0"
86+
87+
- name: Upload Sentry source maps
88+
if: matrix.os == 'ubuntu-latest'
89+
run: |
90+
# Local `pnpm run dist` cleans source maps before packaging. Release CI
91+
# uploads them from the build output first, then removes them below.
92+
if [ -z "$SENTRY_AUTH_TOKEN" ] || [ -z "$SENTRY_ORG" ] || [ -z "$SENTRY_PROJECT" ]; then
93+
echo "Sentry secrets are not configured; skipping source map upload."
94+
exit 0
95+
fi
96+
pnpm run sentry:sourcemaps
97+
shell: bash
98+
env:
99+
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
100+
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
101+
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}
102+
103+
- name: Remove packaged source maps
104+
run: pnpm run clean:sourcemaps
105+
shell: bash
106+
107+
- name: Package
108+
run: |
109+
if [ "${{ matrix.os }}" = "windows-latest" ]; then
110+
pnpm run prepare:package-assets
111+
pnpm exec electron-builder --win
112+
else
113+
if [ "${{ matrix.os }}" = "macos-latest" ]; then
114+
pnpm run prepare:agent-plugins
115+
pnpm exec electron-builder --mac
116+
else
117+
pnpm run prepare:package-assets
118+
pnpm exec electron-builder --linux
119+
fi
120+
fi
121+
shell: bash
122+
env:
123+
LIGHTCODE_CHANNEL: ${{ inputs.channel }}
124+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
125+
# macOS Developer ID signing (base64-encoded .p12 + password).
126+
# No-op on Windows/Linux runners.
127+
CSC_LINK: ${{ secrets.MAC_CSC_LINK }}
128+
CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }}
129+
# macOS notarization via Apple ID + app-specific password.
130+
APPLE_ID: ${{ secrets.APPLE_ID }}
131+
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
132+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
133+
134+
- name: Upload built artifacts
135+
uses: actions/upload-artifact@v7
136+
with:
137+
name: ${{ matrix.artifact }}
138+
path: |
139+
release/Lightcode-*.exe
140+
release/Lightcode-*.dmg
141+
release/Lightcode-*.zip
142+
release/Lightcode-*.AppImage
143+
release/Lightcode-*.deb
144+
release/latest*.yml
145+
release/*.blockmap
146+
if-no-files-found: error
147+
retention-days: 7
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
name: Release (nightly)
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
dry_run:
7+
description: "Dry run — skip tag push and build (for testing)"
8+
required: false
9+
type: boolean
10+
default: false
11+
12+
permissions:
13+
contents: write
14+
15+
jobs:
16+
# ── Step 1: Read master version, compute timestamped tag, push ───
17+
prepare:
18+
runs-on: ubuntu-latest
19+
outputs:
20+
tag: ${{ steps.resolve.outputs.tag }}
21+
version: ${{ steps.resolve.outputs.version }}
22+
base_version: ${{ steps.resolve.outputs.base_version }}
23+
24+
steps:
25+
- name: Checkout master
26+
uses: actions/checkout@v6
27+
with:
28+
ref: master
29+
fetch-depth: 0
30+
ssh-key: ${{ secrets.RELEASE_DEPLOY_KEY }}
31+
32+
- name: Setup Node.js
33+
uses: actions/setup-node@v6
34+
with:
35+
node-version: "24"
36+
37+
- name: Setup pnpm
38+
uses: pnpm/action-setup@v6
39+
40+
- name: Resolve nightly version
41+
id: resolve
42+
run: |
43+
BASE=$(node -p "require('./package.json').version")
44+
if ! echo "$BASE" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
45+
echo "::error::master's package.json version must be plain X.Y.Z (got '$BASE'). The previous stable release likely failed to push the patch bump."
46+
exit 1
47+
fi
48+
TIMESTAMP=$(date -u +%Y%m%d%H%M)
49+
VERSION="${BASE}-nightly.${TIMESTAMP}"
50+
TAG="v${VERSION}"
51+
if git rev-parse "$TAG" >/dev/null 2>&1; then
52+
echo "::error::Tag $TAG already exists (try again in a minute)."
53+
exit 1
54+
fi
55+
echo "base_version=$BASE" >> "$GITHUB_OUTPUT"
56+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
57+
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
58+
echo "Resolved nightly tag: $TAG"
59+
60+
- name: Stage nightly commit (not pushed to master)
61+
run: |
62+
node -e "
63+
const fs = require('fs');
64+
const p = JSON.parse(fs.readFileSync('package.json', 'utf8'));
65+
p.version = '${{ steps.resolve.outputs.version }}';
66+
fs.writeFileSync('package.json', JSON.stringify(p, null, 2) + '\n');
67+
"
68+
git config user.name "github-actions[bot]"
69+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
70+
git add package.json
71+
git commit -m "release: ${{ steps.resolve.outputs.tag }}"
72+
git tag "${{ steps.resolve.outputs.tag }}"
73+
74+
- name: Push tag only
75+
if: ${{ !inputs.dry_run }}
76+
run: |
77+
# Push the tag without updating master — the transient commit is
78+
# reachable only via the tag. master remains at plain X.Y.Z.
79+
git push origin "${{ steps.resolve.outputs.tag }}"
80+
81+
# ── Step 2: Build installers for each platform ───────────────────
82+
build:
83+
needs: prepare
84+
if: ${{ !inputs.dry_run }}
85+
uses: ./.github/workflows/_build.yml
86+
with:
87+
tag: ${{ needs.prepare.outputs.tag }}
88+
channel: nightly
89+
sentry_environment: nightly
90+
secrets:
91+
MAC_CSC_LINK: ${{ secrets.MAC_CSC_LINK }}
92+
MAC_CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }}
93+
APPLE_ID: ${{ secrets.APPLE_ID }}
94+
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
95+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
96+
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
97+
98+
# ── Step 3: Create GitHub prerelease ─────────────────────────────
99+
release:
100+
needs: [prepare, build]
101+
if: ${{ !inputs.dry_run }}
102+
runs-on: ubuntu-latest
103+
104+
steps:
105+
- name: Download all build artifacts
106+
uses: actions/download-artifact@v8
107+
with:
108+
path: release-assets
109+
pattern: dist-*
110+
merge-multiple: true
111+
112+
- name: List release assets
113+
run: ls -lh release-assets
114+
115+
- name: Create GitHub prerelease
116+
uses: softprops/action-gh-release@v2
117+
with:
118+
tag_name: ${{ needs.prepare.outputs.tag }}
119+
name: Lightcode Nightly ${{ needs.prepare.outputs.tag }}
120+
prerelease: true
121+
make_latest: false
122+
generate_release_notes: true
123+
fail_on_unmatched_files: true
124+
files: release-assets/*
125+
env:
126+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

0 commit comments

Comments
 (0)