mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-12 15:22:55 +01:00
468 lines
15 KiB
Markdown
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
|