15 KiB
Task 7.5: GitHub Actions CI/CD Pipeline
Problem Statement
Currently, building Nodegex for distribution requires:
- Manual builds on each platform (macOS, Windows, Linux)
- Access to a macOS machine for Apple Silicon builds
- Manual code signing
- Manual upload to distribution channels
- No automated testing before release
Goals
- Automated Builds: Push tag → builds start automatically
- All Platforms: macOS (x64 + arm64), Windows (x64), Linux (x64)
- Code Signing: Automatic for all platforms
- GitHub Releases: Automatic creation with all artifacts
- Update Manifests:
latest.yml,latest-mac.yml,latest-linux.ymlgenerated
Architecture
┌──────────────────────────────────────────────────────────────────────────┐
│ GitHub Actions Workflow │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ Tag Push (v1.2.0) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Build Matrix │ │
│ ├─────────────────┬─────────────────┬─────────────────────────────┤ │
│ │ macOS x64 │ macOS arm64 │ Windows x64 │ Linux x64 │ │
│ │ (macos-13) │ (macos-14) │ (windows) │ (ubuntu) │ │
│ │ │ │ │ │ │
│ │ • Build │ • Build │ • Build │ • Build │ │
│ │ • Sign │ • Sign │ • Sign* │ • No sign │ │
│ │ • Notarize │ • Notarize │ │ │ │
│ └─────────────────┴─────────────────┴───────────────┴─────────────┘ │
│ │ │ │ │ │
│ └───────────────────┴─────────────────┴───────────────┘ │
│ │ │
│ ▼ │
│ GitHub Release │
│ • DMG (x64, arm64) │
│ • ZIP (x64, arm64) │
│ • EXE installer │
│ • AppImage │
│ • DEB │
│ • latest*.yml manifests │
│ │
└──────────────────────────────────────────────────────────────────────────┘
Implementation
Phase 1: Create Workflow File
# .github/workflows/release.yml
name: Release
on:
push:
tags:
- 'v*'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# macOS Intel
- os: macos-13
platform: darwin
arch: x64
# macOS Apple Silicon
- os: macos-14
platform: darwin
arch: arm64
# Windows
- os: windows-latest
platform: win32
arch: x64
# Linux
- os: ubuntu-22.04
platform: linux
arch: x64
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build packages
run: npm run build --workspaces --if-present
# macOS: Import certificate and configure signing
- name: Import macOS signing certificate
if: matrix.platform == 'darwin'
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
run: |
# Create temporary keychain
KEYCHAIN_PATH=$RUNNER_TEMP/build.keychain
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Import certificate
echo "$MACOS_CERTIFICATE" | base64 --decode > $RUNNER_TEMP/certificate.p12
security import $RUNNER_TEMP/certificate.p12 \
-k "$KEYCHAIN_PATH" \
-P "$MACOS_CERTIFICATE_PASSWORD" \
-T /usr/bin/codesign \
-T /usr/bin/security
# Allow codesign to access keychain
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain
# Verify certificate
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
# Windows: Import certificate (if signing enabled)
- name: Import Windows signing certificate
if: matrix.platform == 'win32' && secrets.WINDOWS_CERTIFICATE != ''
env:
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
run: |
echo "$env:WINDOWS_CERTIFICATE" | Out-File -FilePath certificate.b64
certutil -decode certificate.b64 certificate.pfx
# Certificate will be used by electron-builder automatically
shell: pwsh
# Build Electron app
- name: Build Electron app
env:
# macOS signing
CSC_LINK: ${{ matrix.platform == 'darwin' && secrets.MACOS_CERTIFICATE || '' }}
CSC_KEY_PASSWORD: ${{ matrix.platform == 'darwin' && secrets.MACOS_CERTIFICATE_PASSWORD || '' }}
# macOS notarization
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
# Windows signing (optional)
WIN_CSC_LINK: ${{ matrix.platform == 'win32' && secrets.WINDOWS_CERTIFICATE || '' }}
WIN_CSC_KEY_PASSWORD: ${{ matrix.platform == 'win32' && secrets.WINDOWS_CERTIFICATE_PASSWORD || '' }}
run: |
cd packages/noodl-editor
npx electron-builder --${{ matrix.platform == 'darwin' && 'mac' || matrix.platform == 'win32' && 'win' || 'linux' }} --${{ matrix.arch }} --publish always
# Upload artifacts to GitHub Release
- name: Upload artifacts
uses: softprops/action-gh-release@v1
with:
files: |
packages/noodl-editor/dist/*.dmg
packages/noodl-editor/dist/*.zip
packages/noodl-editor/dist/*.exe
packages/noodl-editor/dist/*.AppImage
packages/noodl-editor/dist/*.deb
packages/noodl-editor/dist/*.yml
packages/noodl-editor/dist/*.yaml
draft: false
prerelease: ${{ contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
Phase 2: Configure GitHub Secrets
Navigate to: Repository → Settings → Secrets and variables → Actions
Required Secrets:
| Secret | Description | How to Get |
|---|---|---|
MACOS_CERTIFICATE |
Base64-encoded .p12 file | base64 -i certificate.p12 |
MACOS_CERTIFICATE_PASSWORD |
Password for .p12 | Your certificate password |
APPLE_ID |
Apple Developer email | Your Apple ID |
APPLE_APP_SPECIFIC_PASSWORD |
App-specific password | appleid.apple.com → Security |
APPLE_TEAM_ID |
Team ID | Y35J975HXR (from certificate) |
Optional Secrets (for Windows signing):
| Secret | Description |
|---|---|
WINDOWS_CERTIFICATE |
Base64-encoded .pfx file |
WINDOWS_CERTIFICATE_PASSWORD |
Certificate password |
Phase 3: Export macOS Certificate for CI
# 1. Export from Keychain Access
# - Open Keychain Access
# - Find "Developer ID Application: Osborne Solutions"
# - Right-click → Export
# - Save as .p12 with strong password
# 2. Base64 encode
base64 -i "Developer ID Application.p12" | pbcopy
# Paste into MACOS_CERTIFICATE secret
# 3. Generate app-specific password
# - Go to appleid.apple.com
# - Sign In & Security → App-Specific Passwords
# - Generate new password labeled "nodegex-ci"
# - Copy to APPLE_APP_SPECIFIC_PASSWORD secret
Phase 4: Update package.json for CI
{
"build": {
"publish": {
"provider": "github",
"owner": "the-low-code-foundation",
"repo": "opennoodl",
"releaseType": "release"
},
"mac": {
"target": [
{ "target": "dmg", "arch": ["x64", "arm64"] },
{ "target": "zip", "arch": ["x64", "arm64"] }
]
},
"win": {
"target": [
{ "target": "nsis", "arch": ["x64"] }
]
},
"linux": {
"target": [
{ "target": "AppImage", "arch": ["x64"] },
{ "target": "deb", "arch": ["x64"] }
]
}
}
}
Phase 5: Release Process
Step 5.1: Create release
# Update version in package.json
npm version patch # or minor, major
# Push with tags
git push && git push --tags
Step 5.2: Monitor workflow
Go to: Repository → Actions → Release workflow
Each platform builds in parallel (~15-30 minutes total).
Step 5.3: Verify release
- Check GitHub Releases for all artifacts
- Download and test on each platform
- Verify auto-update works from previous version
Phase 6: Version Management
Semantic Versioning:
v1.0.0→ Stable releasev1.1.0-beta.1→ Beta releasev1.1.0-alpha.1→ Alpha release
Pre-release handling:
Tags containing beta or alpha automatically create pre-releases that don't trigger auto-update for stable users.
Phase 7: Update Manifests
electron-builder automatically generates:
# latest-mac.yml
version: 1.2.0
files:
- url: Nodegex-1.2.0-arm64.dmg
sha512: abc123...
size: 150000000
- url: Nodegex-1.2.0-x64.dmg
sha512: def456...
size: 155000000
path: Nodegex-1.2.0-arm64.dmg
sha512: abc123...
releaseDate: '2024-01-15T10:30:00.000Z'
These files tell electron-updater which version is latest and where to download.
Workflow Customizations
Manual Trigger
Add workflow_dispatch for manual runs:
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Version to build'
required: true
Build on PR (Testing)
Create separate workflow for PR testing:
# .github/workflows/build-test.yml
name: Build Test
on:
pull_request:
paths:
- 'packages/noodl-editor/**'
- '.github/workflows/**'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run build --workspaces
- run: cd packages/noodl-editor && npx electron-builder --linux --dir
Caching
Speed up builds with dependency caching:
- uses: actions/cache@v4
with:
path: |
~/.npm
~/.cache/electron
~/.cache/electron-builder
key: ${{ runner.os }}-build-${{ hashFiles('**/package-lock.json') }}
Troubleshooting
macOS: "No identity found"
Certificate not imported correctly:
# Debug step
- name: Debug certificates
if: matrix.platform == 'darwin'
run: |
security list-keychains
security find-identity -v -p codesigning
macOS: Notarization timeout
Apple's servers can be slow. Increase timeout or retry:
// macos-notarize.js
await notarize({
// ...
timeout: 1800000 // 30 minutes
});
Windows: SmartScreen warning
Without EV certificate, SmartScreen shows warning for first ~1000 downloads. Solutions:
- Purchase EV code signing certificate (~$400/year)
- Accept warnings initially (reputation builds over time)
Linux: AppImage won't run
Missing FUSE. Document in release notes:
## Linux Users
AppImage requires FUSE:
\`\`\`bash
sudo apt install libfuse2
\`\`\`
Files to Create/Modify
| File | Action |
|---|---|
.github/workflows/release.yml |
Create |
.github/workflows/build-test.yml |
Create (optional) |
packages/noodl-editor/package.json |
Add publish config |
packages/noodl-editor/build/macos-notarize.js |
Update for CI |
Success Criteria
- ✅
git tag v1.2.0 && git push --tagstriggers workflow - ✅ All 4 build targets complete successfully
- ✅ GitHub Release created with all artifacts
- ✅ Update manifests (latest*.yml) present
- ✅ Existing users see "Update Available" within 1 minute
- ✅ Build time < 30 minutes total
Cost Considerations
GitHub Actions is free for public repositories.
For private repos:
- 2,000 minutes/month free
- Each release uses ~60-90 minutes (all platforms combined)
- ~20-30 releases/month possible on free tier
Security Considerations
- Secrets Protection: Never log secrets or expose in artifacts
- Certificate Security: Rotate certificates before expiration
- Tag Protection: Consider requiring reviews for tags
- Artifact Integrity: SHA512 checksums in update manifests
Future Enhancements
- Changelog Generation: Auto-generate from merged PRs
- Slack/Discord Notifications: Post release announcements
- Download Statistics: Track per-platform adoption
- Rollback Mechanism: Quick revert to previous version