Files
OpenNoodl/dev-docs/tasks/phase-8-distribution/TASK-7.5-github-actions.md

468 lines
15 KiB
Markdown

# 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
1. **Automated Builds**: Push tag → builds start automatically
2. **All Platforms**: macOS (x64 + arm64), Windows (x64), Linux (x64)
3. **Code Signing**: Automatic for all platforms
4. **GitHub Releases**: Automatic creation with all artifacts
5. **Update Manifests**: `latest.yml`, `latest-mac.yml`, `latest-linux.yml` generated
## 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
```yaml
# .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
```bash
# 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
```json
{
"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**
```bash
# 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**
1. Check GitHub Releases for all artifacts
2. Download and test on each platform
3. Verify auto-update works from previous version
### Phase 6: Version Management
**Semantic Versioning:**
- `v1.0.0` → Stable release
- `v1.1.0-beta.1` → Beta release
- `v1.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:
```yaml
# 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:
```yaml
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Version to build'
required: true
```
### Build on PR (Testing)
Create separate workflow for PR testing:
```yaml
# .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:
```yaml
- 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:
```yaml
# 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:
```javascript
// macos-notarize.js
await notarize({
// ...
timeout: 1800000 // 30 minutes
});
```
### Windows: SmartScreen warning
Without EV certificate, SmartScreen shows warning for first ~1000 downloads. Solutions:
1. Purchase EV code signing certificate (~$400/year)
2. Accept warnings initially (reputation builds over time)
### Linux: AppImage won't run
Missing FUSE. Document in release notes:
```markdown
## 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
1.`git tag v1.2.0 && git push --tags` triggers workflow
2. ✅ All 4 build targets complete successfully
3. ✅ GitHub Release created with all artifacts
4. ✅ Update manifests (latest*.yml) present
5. ✅ Existing users see "Update Available" within 1 minute
6. ✅ 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
1. **Secrets Protection**: Never log secrets or expose in artifacts
2. **Certificate Security**: Rotate certificates before expiration
3. **Tag Protection**: Consider requiring reviews for tags
4. **Artifact Integrity**: SHA512 checksums in update manifests
## Future Enhancements
1. **Changelog Generation**: Auto-generate from merged PRs
2. **Slack/Discord Notifications**: Post release announcements
3. **Download Statistics**: Track per-platform adoption
4. **Rollback Mechanism**: Quick revert to previous version