Refactored dev-docs folder after multiple additions to organise correctly

This commit is contained in:
Richard Osborne
2026-01-07 20:28:40 +01:00
parent beff9f0886
commit 4a1080d547
125 changed files with 18456 additions and 957 deletions

View File

@@ -0,0 +1,153 @@
# Phase 8: Auto-Update & Distribution - Progress Tracker
**Last Updated:** 2026-01-07
**Overall Status:** 🔴 Not Started
---
## Quick Summary
| Metric | Value |
| ------------ | ------ |
| Total Tasks | 5 |
| Completed | 0 |
| In Progress | 0 |
| Not Started | 5 |
| **Progress** | **0%** |
**Estimated Effort:** 38-56 hours (excluding optional Windows signing)
---
## Task Status
| Task | Name | Status | Effort | Doc |
| ---- | -------------------------------- | -------------- | ------ | -------------------------------------------- |
| 7.1 | Rebrand to Nodegex | 🔴 Not Started | 4-6h | [TASK-7.1](./TASK-7.1-rebrand-nodegex.md) |
| 7.2 | Fix macOS Code Signing | 🔴 Not Started | 8-12h | [TASK-7.2](./TASK-7.2-macos-signing.md) |
| 7.3 | Configure Auto-Update Publishing | 🔴 Not Started | 4-6h | [TASK-7.3](./TASK-7.3-auto-update-config.md) |
| 7.4 | Linux Universal Distribution | 🔴 Not Started | 6-8h | [TASK-7.4](./TASK-7.4-linux-distribution.md) |
| 7.5 | GitHub Actions CI/CD | 🔴 Not Started | 12-16h | [TASK-7.5](./TASK-7.5-github-actions.md) |
| 7.6 | Windows Code Signing | 🔴 Not Started | 4-8h | _(optional, no doc yet)_ |
---
## Task Details
### 7.1 Rebrand to Nodegex
**Status:** 🔴 Not Started
Rename application from OpenNoodl to Nodegex across all user-facing surfaces:
- Package.json productName, appId, protocols
- Window titles and UI strings
- Protocol handlers (`nodegex://`)
- userData paths with migration for existing users
### 7.2 Fix macOS Code Signing
**Status:** 🔴 Not Started
Configure electron-builder for automatic signing (eliminates 30+ manual file signatures):
- Certificate configuration via `CSC_NAME`
- Entitlements for hardened runtime
- Automatic notarization via afterSign hook
- Support for both Intel and Apple Silicon
### 7.3 Configure Auto-Update Publishing
**Status:** 🔴 Not Started
Connect existing electron-updater infrastructure to GitHub Releases:
- Add publish configuration to package.json
- Configure update server URL
- Generate `latest-*.yml` manifests
- Test update detection and installation
### 7.4 Linux Universal Distribution
**Status:** 🔴 Not Started
Add AppImage and .deb targets:
- AppImage for universal distribution (auto-update supported)
- .deb for Debian/Ubuntu native experience
- Handle native module compatibility (dugite, desktop-trampoline)
- Test on Ubuntu 22.04/24.04 LTS
### 7.5 GitHub Actions CI/CD
**Status:** 🔴 Not Started
Create automated build pipeline:
- Matrix build for macOS (x64, arm64), Windows (x64), Linux (x64)
- Secure certificate storage via GitHub Secrets
- Automatic GitHub Release creation on tag push
- Update manifest generation
### 7.6 Windows Code Signing (Optional)
**Status:** 🔴 Not Started
Add Windows code signing to eliminate SmartScreen warnings:
- Obtain code signing certificate (EV or standard)
- Configure in electron-builder
- Add to CI/CD pipeline
---
## Status Legend
- 🔴 **Not Started** - Work has not begun
- 🟡 **In Progress** - Actively being worked on
- 🟢 **Complete** - Finished and verified
---
## Recent Updates
| Date | Update |
| ---------- | ------------------------------------------------- |
| 2026-01-07 | Updated PROGRESS.md to reflect actual task status |
| 2026-01-07 | Renumbered from Phase 7 to Phase 8 |
---
## Dependencies
**Depends on:** Phase 0-3 (stable editor)
**Task Dependencies:**
```
7.1 Rebrand ──┬──► 7.2 macOS Signing ──┐
├──► 7.3 Auto-Update ────┼──► 7.5 GitHub Actions CI/CD
└──► 7.4 Linux Distro ───┘
7.6 Windows Signing (optional)
```
---
## Success Criteria
1. ✅ User can receive update notification without losing projects
2. ✅ macOS build requires zero manual signing steps
3. ✅ Linux AppImage runs on Ubuntu 22.04+ without dependencies
4.`git tag v1.2.0 && git push --tags` triggers full release
5. ✅ All UI shows "Nodegex" branding
6. ✅ Existing OpenNoodl users' data migrates automatically
---
## Notes
Previously Phase 7 "auto-update-and-distribution". Covers macOS code signing, Windows signing, auto-update infrastructure, Linux distribution, and GitHub Actions CI/CD.
See [README.md](./README.md) for comprehensive technical analysis and architecture decisions.

View File

@@ -0,0 +1,203 @@
# Phase 8: Auto-Update & Cross-Platform Deployment Infrastructure
## Executive Summary
Phase 8 transforms Nodegex from a manually-distributed application requiring full reinstalls into a professionally deployed desktop application with seamless auto-updates across Windows, macOS (Intel & Apple Silicon), and Linux.
**Current Pain Points:**
- Manual code signing of 30+ files for each macOS build
- Users must download and reinstall for every update, losing local preferences
- No Linux universal distribution
- No automated CI/CD pipeline
- Rebranding from OpenNoodl to Nodegex not complete
**End State:**
- Push a git tag → GitHub Actions builds all platforms → Users see "Update Available" → One-click update
- User data (projects, preferences) persists across updates
- Professional code signing handled automatically
- Linux support via AppImage (universal) and .deb (Debian/Ubuntu)
## Why This Matters
1. **User Experience**: Currently users must re-add all projects after every update. This is a deal-breaker for adoption.
2. **Development Velocity**: Manual signing and packaging takes hours per release. Automated CI/CD enables rapid iteration.
3. **Community Growth**: Linux users are a significant portion of the open-source developer community.
4. **Professional Credibility**: Auto-updates are expected in modern desktop applications.
## Technical Analysis
### Existing Infrastructure (What We Have)
| Component | Status | Location |
|-----------|--------|----------|
| electron-updater | ✅ Installed | `autoupdater.js` |
| Update UI | ✅ Complete | `BaseWindow.tsx`, `TitleBar` |
| Notarization script | ✅ Exists | `build/macos-notarize.js` |
| electron-builder config | ⚠️ Incomplete | `package.json` build section |
| Publish config | ❌ Missing | Needs GitHub Releases setup |
| CI/CD | ❌ Missing | Needs GitHub Actions |
### The Mac Signing Problem Diagnosed
The 30+ manual signatures happen because **electron-builder's automatic signing isn't configured correctly**.
When properly configured, electron-builder signs in this order (automatically):
1. All binaries in `asar.unpacked` (dugite, desktop-trampoline)
2. Helper apps (GPU, Plugin, Renderer)
3. Frameworks (Electron, Squirrel, Mantle, ReactiveObjC)
4. Main executable
5. The .app bundle itself
**Root Cause**: Missing `CSC_LINK` or `CSC_NAME` environment variable. Without this, electron-builder skips signing entirely, then notarization fails.
**The Fix**:
```bash
# Option 1: Certificate file (for CI)
export CSC_LINK="path/to/certificate.p12"
export CSC_KEY_PASSWORD="certificate-password"
# Option 2: Keychain certificate (for local builds)
export CSC_NAME="Developer ID Application: Osborne Solutions (Y35J975HXR)"
```
### User Data Persistence
This is already solved by Electron's architecture:
| Platform | userData Location | Survives Updates? |
|----------|------------------|-------------------|
| Windows | `%APPDATA%/Nodegex` | ✅ Yes |
| macOS | `~/Library/Application Support/Nodegex` | ✅ Yes |
| Linux | `~/.config/Nodegex` | ✅ Yes |
The project list uses `localStorage` which is stored in `userData`. The reason Richard is losing data is because users are doing **fresh installs** (delete app, download new, install) rather than using the auto-update mechanism.
Once auto-update works, this problem disappears.
## Task Breakdown
### Task 7.1: Rebrand to Nodegex
**Effort**: 4-6 hours | **Complexity**: Low
Update all user-facing references from OpenNoodl/Noodl to Nodegex:
- `package.json` productName, appId, description
- Window titles and UI strings
- Protocol handlers (`nodegex://`)
- userData paths (with migration for existing users)
- Documentation and comments
### Task 7.2: Fix macOS Code Signing
**Effort**: 8-12 hours | **Complexity**: High
Configure electron-builder to sign automatically:
- Verify certificate is in keychain correctly
- Add `CSC_NAME` to build environment
- Test that all 30+ files are signed automatically
- Verify notarization succeeds
- Test on both Intel and Apple Silicon
### Task 7.3: Configure Auto-Update Publishing
**Effort**: 4-6 hours | **Complexity**: Medium
Add GitHub Releases as update source:
- Add `publish` config to package.json
- Configure update server URL
- Test update detection and download
- Test quit-and-install flow
### Task 7.4: Linux Universal Distribution
**Effort**: 6-8 hours | **Complexity**: Medium
Add AppImage and .deb targets:
- Configure AppImage with auto-update support
- Configure .deb for Debian/Ubuntu
- Handle native module compatibility
- Test on Ubuntu 22.04/24.04
### Task 7.5: GitHub Actions CI/CD
**Effort**: 12-16 hours | **Complexity**: High
Create automated build pipeline:
- Matrix build for all platforms/architectures
- Secure certificate storage via GitHub Secrets
- Automatic GitHub Release creation
- Version tagging workflow
### Task 7.6: Windows Code Signing (Optional Enhancement)
**Effort**: 4-8 hours | **Complexity**: Medium
Add Windows code signing to eliminate SmartScreen warnings:
- Obtain code signing certificate (EV or standard)
- Configure in electron-builder
- Add to CI/CD pipeline
## Architecture Decisions
### Update Distribution: GitHub Releases
**Why GitHub Releases over other options:**
| Option | Pros | Cons |
|--------|------|------|
| GitHub Releases | Free, integrated with repo, electron-updater native support | Public releases only |
| S3/CloudFront | Private releases, full control | Cost, complexity |
| Nuts/Hazel | More control | Self-hosted, maintenance |
| Electron Forge | Modern tooling | Migration effort |
**Decision**: GitHub Releases - simplest path, zero cost, electron-builder native support.
### Linux Format: AppImage + .deb
**Why AppImage:**
- Single file, no installation required
- Works on any Linux distribution
- electron-updater supports AppImage auto-updates
- No root required
**Why .deb:**
- Native experience for Debian/Ubuntu users (60%+ of Linux desktop)
- Integrates with system package manager
- Desktop integration (menus, file associations)
### Signing Certificate Storage
**Local Development**: Keychain (macOS) / Certificate Store (Windows)
**CI/CD**: GitHub Secrets with base64-encoded certificates
## Success Criteria
1. ✅ User can receive update notification without losing projects
2. ✅ macOS build requires zero manual signing steps
3. ✅ Linux AppImage runs on Ubuntu 22.04+ without dependencies
4.`git tag v1.2.0 && git push --tags` triggers full release
5. ✅ All UI shows "Nodegex" branding
6. ✅ Existing OpenNoodl users' data migrates automatically
## Risk Assessment
| Risk | Likelihood | Impact | Mitigation |
|------|------------|--------|------------|
| Apple certificate issues | Medium | High | Document exact certificate setup steps |
| Native module compatibility | Medium | Medium | Test dugite/desktop-trampoline on all platforms |
| Auto-update breaks for some users | Low | High | Include manual download fallback |
| Linux dependency issues | Medium | Medium | Test on fresh VM installations |
## Timeline Estimate
| Task | Effort | Dependencies |
|------|--------|--------------|
| 7.1 Rebrand | 4-6h | None |
| 7.2 macOS Signing | 8-12h | 7.1 |
| 7.3 Auto-Update Config | 4-6h | 7.1 |
| 7.4 Linux Distribution | 6-8h | 7.1 |
| 7.5 GitHub Actions | 12-16h | 7.2, 7.3, 7.4 |
| 7.6 Windows Signing | 4-8h | 7.5 (optional) |
**Total**: 38-56 hours (excluding Windows signing)
## References
- [electron-builder Code Signing](https://www.electron.build/code-signing)
- [electron-updater Documentation](https://www.electron.build/auto-update)
- [Apple Notarization](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution)
- [GitHub Actions for Electron](https://www.electron.build/multi-platform-build#github-actions)

View File

@@ -0,0 +1,303 @@
# Task 7.1: Rebrand to Nodegex
## Overview
Rename the application from "OpenNoodl" to "Nodegex" (Node Graph Expression) across all user-facing surfaces while maintaining backward compatibility for existing users.
## Scope
### In Scope
- Application name and branding
- Window titles
- Protocol handlers
- Package identifiers
- userData path (with migration)
- Documentation references
### Out of Scope (Keep as "noodl")
- Internal package names (noodl-editor, noodl-runtime, etc.)
- Code variables and function names
- Git history
- NPM package names (if published)
## Changes Required
### 1. Package Configuration
**`packages/noodl-editor/package.json`**
```json
{
"name": "noodl-editor", // Keep internal
"productName": "Nodegex", // Change from OpenNoodl
"description": "Full stack low-code React app builder - Nodegex",
"author": "The Low Code Foundation",
"homepage": "https://nodegex.dev", // Update when domain ready
"build": {
"appId": "com.nodegex.app", // Change from com.opennoodl.app
"protocols": {
"name": "nodegex", // Change from opennoodl
"schemes": ["nodegex"] // Change from opennoodl
}
}
}
```
### 2. Main Process
**`packages/noodl-editor/src/main/main.js`**
Update any hardcoded "OpenNoodl" or "Noodl" strings in:
- Window titles
- Dialog messages
- Menu items
```javascript
// Example changes
const mainWindow = new BrowserWindow({
title: 'Nodegex', // Was: OpenNoodl
// ...
});
```
### 3. Window Titles
**`packages/noodl-editor/src/editor/src/views/windows/BaseWindow/BaseWindow.tsx`**
```typescript
export function BaseWindow({
title = ProjectModel.instance.name, // Default project name as title
// ...
}) {
// The TitleBar component may show app name
}
```
**`packages/noodl-core-ui/src/components/app/TitleBar/TitleBar.tsx`**
Check for any hardcoded "Noodl" references.
### 4. Platform Identification
**`packages/noodl-platform-electron/src/platform-electron.ts`**
The userData path is determined by Electron using productName. After changing productName, the path becomes:
- Windows: `%APPDATA%/Nodegex`
- macOS: `~/Library/Application Support/Nodegex`
- Linux: `~/.config/Nodegex`
### 5. User Data Migration
**Critical**: Existing users have data in the old location. We need migration.
**Create: `packages/noodl-editor/src/main/src/migration.js`**
```javascript
const { app } = require('electron');
const fs = require('fs');
const path = require('path');
const OLD_NAMES = ['OpenNoodl', 'Noodl']; // Possible old names
const NEW_NAME = 'Nodegex';
function migrateUserData() {
const userDataPath = app.getPath('userData');
// Check if we're already in the new location
if (userDataPath.includes(NEW_NAME)) {
// Look for old data to migrate
for (const oldName of OLD_NAMES) {
const oldPath = userDataPath.replace(NEW_NAME, oldName);
if (fs.existsSync(oldPath) && !fs.existsSync(path.join(userDataPath, '.migrated'))) {
console.log(`Migrating user data from ${oldPath} to ${userDataPath}`);
// Copy contents (not move, safer)
copyDirectory(oldPath, userDataPath);
// Mark as migrated
fs.writeFileSync(path.join(userDataPath, '.migrated'), oldPath);
console.log('Migration complete');
break;
}
}
}
}
function copyDirectory(src, dest) {
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true });
}
const entries = fs.readdirSync(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
copyDirectory(srcPath, destPath);
} else {
// Don't overwrite existing files in new location
if (!fs.existsSync(destPath)) {
fs.copyFileSync(srcPath, destPath);
}
}
}
}
module.exports = { migrateUserData };
```
**Update: `packages/noodl-editor/src/main/main.js`**
```javascript
const { migrateUserData } = require('./src/migration');
app.on('ready', () => {
// Migrate before anything else
migrateUserData();
// ... rest of app initialization
});
```
### 6. Protocol Handler
Update deep links from `opennoodl://` to `nodegex://`
**`packages/noodl-editor/src/main/main.js`**
```javascript
// Register protocol handler
app.setAsDefaultProtocolClient('nodegex');
// Handle incoming URLs
app.on('open-url', (event, url) => {
if (url.startsWith('nodegex://')) {
// Handle URL
}
});
```
### 7. macOS Info.plist
**`packages/noodl-editor/build/Info.plist`** (if exists) or via electron-builder:
```json
{
"build": {
"mac": {
"extendInfo": {
"CFBundleDisplayName": "Nodegex",
"CFBundleName": "Nodegex",
"LSMultipleInstancesProhibited": true
}
}
}
}
```
### 8. UI Strings
Search for and replace user-facing strings:
```bash
# Find all references
grep -r "OpenNoodl\|Noodl" packages/ --include="*.tsx" --include="*.ts" --include="*.js"
```
Common locations:
- About dialogs
- Error messages
- Welcome screens
- Help text
- Tooltips
### 9. Launcher
**`packages/noodl-core-ui/src/preview/launcher/Launcher/`**
- Update any branding in launcher UI
- Logo/icon references
- Welcome messages
### 10. Build Assets
**`packages/noodl-editor/build/`**
- Icon files: Keep filenames, update icon content if needed
- Installer background images
- DMG background
### 11. Code Comments (Low Priority)
Internal comments can remain as "Noodl" for historical context. Only update user-visible strings.
## File Change Summary
| File | Change Type |
|------|-------------|
| `packages/noodl-editor/package.json` | productName, appId, protocols |
| `packages/noodl-editor/src/main/main.js` | Add migration, protocol handler |
| `packages/noodl-editor/src/main/src/migration.js` | Create new |
| `packages/noodl-core-ui/**/TitleBar*` | Check for hardcoded strings |
| `packages/noodl-core-ui/**/Launcher*` | Branding updates |
| Various `.tsx`, `.ts` files | User-facing string changes |
## Testing Checklist
### Fresh Install
- [ ] App installs as "Nodegex"
- [ ] userData created in correct location
- [ ] Protocol handler `nodegex://` works
- [ ] App icon shows correctly
- [ ] Window title shows "Nodegex"
- [ ] About dialog shows "Nodegex"
### Upgrade from OpenNoodl
- [ ] User data migrates automatically
- [ ] Projects list preserved
- [ ] Settings preserved
- [ ] No duplicate data created
### Platform Specific
- [ ] Windows: Start menu shows "Nodegex"
- [ ] macOS: Menu bar shows "Nodegex"
- [ ] macOS: Dock shows "Nodegex"
- [ ] Linux: Desktop entry shows "Nodegex"
## Rollback Plan
If issues arise, the migration is non-destructive:
1. Old userData folder is preserved
2. Migration marker file indicates completion
3. Can revert productName and migrate back
## Search Patterns
Use these to find remaining references:
```bash
# Case-insensitive search for noodl
grep -ri "noodl" packages/ --include="*.tsx" --include="*.ts" --include="*.js" \
| grep -v "node_modules" \
| grep -v ".bundle." \
| grep -v "// " \
| grep -v "* "
# Specific product names
grep -r "OpenNoodl\|opennoodl\|com\.opennoodl" packages/
```
## Notes on Internal Names
These should **NOT** change:
- `noodl-editor` package name
- `noodl-runtime` package name
- `noodl-core-ui` package name
- `@noodl/` npm scope (if any)
- Internal imports like `from '@noodl-models/...'`
Changing these would require massive refactoring with no user benefit.

View File

@@ -0,0 +1,390 @@
# Task 7.2: Fix macOS Automatic Code Signing
## Problem Statement
Currently, macOS builds require manual code signing of 30+ individual files using a bash script. This process:
- Takes 15-30 minutes per build
- Is error-prone (easy to miss files or sign in wrong order)
- Must be repeated for both Intel (x64) and Apple Silicon (arm64)
- Blocks automation via CI/CD
**Root Cause**: electron-builder's automatic signing isn't configured, so it skips signing entirely.
## Current Manual Process (What We're Eliminating)
```bash
# Current painful workflow:
1. Run electron-builder (produces unsigned app)
2. Manually run signing script with 30+ codesign commands
3. Sign in specific order (inner files first, .app last)
4. Hope you didn't miss anything
5. Run notarization
6. Wait 5-10 minutes for Apple
7. Staple the notarization ticket
8. Repeat for other architecture
```
## Target Automated Process
```bash
# Target workflow:
export CSC_NAME="Developer ID Application: Osborne Solutions (Y35J975HXR)"
export APPLE_ID="your@email.com"
export APPLE_APP_SPECIFIC_PASSWORD="xxxx-xxxx-xxxx-xxxx"
export APPLE_TEAM_ID="Y35J975HXR"
npm run build # Everything happens automatically
```
## Implementation
### Phase 1: Verify Certificate Setup
**Step 1.1: Check Keychain**
```bash
# List all Developer ID certificates
security find-identity -v -p codesigning
# Should show something like:
# 1) ABCD1234... "Developer ID Application: Osborne Solutions (Y35J975HXR)"
```
**Step 1.2: Verify Certificate Chain**
```bash
# Check certificate details
security find-certificate -c "Developer ID Application: Osborne Solutions" -p | \
openssl x509 -noout -subject -issuer -dates
```
**Step 1.3: Test Manual Signing**
```bash
# Create a simple test binary
echo 'int main() { return 0; }' | clang -x c - -o /tmp/test
codesign --sign "Developer ID Application: Osborne Solutions (Y35J975HXR)" \
--options runtime /tmp/test
codesign --verify --verbose /tmp/test
```
### Phase 2: Configure electron-builder
**Step 2.1: Update package.json**
```json
{
"build": {
"appId": "com.nodegex.app",
"productName": "Nodegex",
"afterSign": "./build/macos-notarize.js",
"mac": {
"category": "public.app-category.developer-tools",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist",
"target": [
{ "target": "dmg", "arch": ["x64", "arm64"] },
{ "target": "zip", "arch": ["x64", "arm64"] }
],
"signIgnore": [],
"extendInfo": {
"LSMultipleInstancesProhibited": true,
"NSMicrophoneUsageDescription": "Allow Nodegex apps to access the microphone?",
"NSCameraUsageDescription": "Allow Nodegex apps to access the camera?"
}
},
"dmg": {
"sign": false
},
"publish": {
"provider": "github",
"owner": "the-low-code-foundation",
"repo": "opennoodl",
"releaseType": "release"
}
}
}
```
**Key Configuration Notes:**
| Setting | Purpose |
|---------|---------|
| `hardenedRuntime: true` | Required for notarization |
| `gatekeeperAssess: false` | Skip Gatekeeper check during build (faster) |
| `entitlementsInherit` | Apply entitlements to all nested executables |
| `dmg.sign: false` | DMG signing is usually unnecessary and can cause issues |
**Step 2.2: Verify entitlements.mac.plist**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Required for Electron -->
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<!-- Required for Node.js child processes (git, etc.) -->
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<!-- Network access -->
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<!-- File access for projects -->
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<!-- Accessibility for some UI features -->
<key>com.apple.security.automation.apple-events</key>
<true/>
</dict>
</plist>
```
**Step 2.3: Update notarization script**
```javascript
// build/macos-notarize.js
const { notarize } = require('@electron/notarize');
const path = require('path');
module.exports = async function notarizing(context) {
const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== 'darwin') {
console.log('Skipping notarization: not macOS');
return;
}
// Check for required environment variables
const appleId = process.env.APPLE_ID;
const appleIdPassword = process.env.APPLE_APP_SPECIFIC_PASSWORD;
const teamId = process.env.APPLE_TEAM_ID;
if (!appleId || !appleIdPassword || !teamId) {
console.log('Skipping notarization: missing credentials');
console.log('Set APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, and APPLE_TEAM_ID');
return;
}
const appName = context.packager.appInfo.productFilename;
const appPath = path.join(appOutDir, `${appName}.app`);
console.log(`Notarizing ${appPath}...`);
try {
await notarize({
appPath,
appleId,
appleIdPassword,
teamId,
tool: 'notarytool' // Faster than legacy altool
});
console.log('Notarization complete!');
} catch (error) {
console.error('Notarization failed:', error);
throw error;
}
};
```
### Phase 3: Handle Native Modules in asar.unpacked
The dugite and desktop-trampoline binaries are in `asar.unpacked` which requires special handling.
**Step 3.1: Verify asar configuration**
```json
{
"build": {
"asarUnpack": [
"node_modules/dugite/**/*",
"node_modules/desktop-trampoline/**/*"
],
"files": [
"**/*",
"!node_modules/dugite/git/**/*",
"node_modules/dugite/git/bin/*",
"node_modules/dugite/git/libexec/git-core/*"
]
}
}
```
**Step 3.2: electron-builder automatically signs asar.unpacked**
When `CSC_NAME` or `CSC_LINK` is set, electron-builder will:
1. Find all Mach-O binaries in `asar.unpacked`
2. Sign each with hardened runtime and entitlements
3. Sign them in correct dependency order
### Phase 4: Build Environment Setup
**Step 4.1: Create build script**
```bash
#!/bin/bash
# scripts/build-mac.sh
set -e
# Certificate identity (must match keychain exactly)
export CSC_NAME="Developer ID Application: Osborne Solutions (Y35J975HXR)"
# Apple notarization credentials
export APPLE_ID="${APPLE_ID:?Set APPLE_ID environment variable}"
export APPLE_APP_SPECIFIC_PASSWORD="${APPLE_APP_SPECIFIC_PASSWORD:?Set APPLE_APP_SPECIFIC_PASSWORD}"
export APPLE_TEAM_ID="Y35J975HXR"
# Build for specified architecture or both
ARCH="${1:-universal}"
case "$ARCH" in
x64)
npx electron-builder --mac --x64
;;
arm64)
npx electron-builder --mac --arm64
;;
universal|both)
npx electron-builder --mac --x64 --arm64
;;
*)
echo "Usage: $0 [x64|arm64|universal]"
exit 1
;;
esac
echo "Build complete! Check dist/ for output."
```
**Step 4.2: Add to package.json scripts**
```json
{
"scripts": {
"build:mac": "./scripts/build-mac.sh",
"build:mac:x64": "./scripts/build-mac.sh x64",
"build:mac:arm64": "./scripts/build-mac.sh arm64"
}
}
```
### Phase 5: Verification
**Step 5.1: Verify all signatures**
```bash
# Check the .app bundle
codesign --verify --deep --strict --verbose=2 "dist/mac-arm64/Nodegex.app"
# Check specific problematic files
codesign -dv "dist/mac-arm64/Nodegex.app/Contents/Resources/app.asar.unpacked/node_modules/dugite/git/bin/git"
# Verify notarization
spctl --assess --type execute --verbose "dist/mac-arm64/Nodegex.app"
```
**Step 5.2: Test Gatekeeper**
```bash
# This simulates what happens when a user downloads and opens the app
xattr -d com.apple.quarantine "dist/mac-arm64/Nodegex.app"
xattr -w com.apple.quarantine "0081;5f8a1234;Safari;12345678-1234-1234-1234-123456789ABC" "dist/mac-arm64/Nodegex.app"
open "dist/mac-arm64/Nodegex.app"
```
**Step 5.3: Verify notarization stapling**
```bash
stapler validate "dist/Nodegex-1.2.0-arm64.dmg"
```
## Troubleshooting
### "The signature is invalid" or signing fails
```bash
# Reset code signing
codesign --remove-signature "path/to/file"
# Check certificate validity
security find-certificate -c "Developer ID" -p | openssl x509 -checkend 0
```
### "errSecInternalComponent" error
The certificate private key isn't accessible:
```bash
# Unlock keychain
security unlock-keychain -p "password" ~/Library/Keychains/login.keychain-db
# Or in CI, create a temporary keychain
security create-keychain -p "" build.keychain
security import certificate.p12 -k build.keychain -P "$CERT_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain
```
### Notarization timeout or failure
```bash
# Check notarization history
xcrun notarytool history --apple-id "$APPLE_ID" --password "$APPLE_APP_SPECIFIC_PASSWORD" --team-id "$APPLE_TEAM_ID"
# Get details on specific submission
xcrun notarytool log <submission-id> --apple-id "$APPLE_ID" --password "$APPLE_APP_SPECIFIC_PASSWORD" --team-id "$APPLE_TEAM_ID"
```
### dugite binaries not signed
Verify they're correctly unpacked:
```bash
ls -la "dist/mac-arm64/Nodegex.app/Contents/Resources/app.asar.unpacked/node_modules/dugite/git/bin/"
```
If missing, check `asarUnpack` patterns in build config.
## Files to Modify
| File | Changes |
|------|---------|
| `packages/noodl-editor/package.json` | Update build config, add mac targets |
| `packages/noodl-editor/build/entitlements.mac.plist` | Verify all required entitlements |
| `packages/noodl-editor/build/macos-notarize.js` | Update to use notarytool |
| `scripts/noodl-editor/build-editor.ts` | Add CSC_NAME handling |
## Success Criteria
1.`npm run build:mac:arm64` produces signed app with zero manual steps
2.`codesign --verify --deep --strict` passes
3.`spctl --assess --type execute` returns "accepted"
4. ✅ All 30+ files from manual script are signed automatically
5. ✅ App opens on fresh macOS install without Gatekeeper warning
## Environment Variables Reference
| Variable | Required | Description |
|----------|----------|-------------|
| `CSC_NAME` | Yes* | Certificate name in keychain |
| `CSC_LINK` | Yes* | Path to .p12 certificate file (CI) |
| `CSC_KEY_PASSWORD` | With CSC_LINK | Certificate password |
| `APPLE_ID` | For notarization | Apple Developer account email |
| `APPLE_APP_SPECIFIC_PASSWORD` | For notarization | App-specific password from appleid.apple.com |
| `APPLE_TEAM_ID` | For notarization | Team ID (e.g., Y35J975HXR) |
*One of `CSC_NAME` or `CSC_LINK` is required for signing.

View File

@@ -0,0 +1,358 @@
# Task 7.3: Configure Auto-Update Publishing
## Overview
Connect the existing auto-update infrastructure to GitHub Releases so users receive update notifications without manual downloads.
## What Already Exists
The codebase already has:
1. **electron-updater** - Installed and configured in `autoupdater.js`
2. **Update UI** - TitleBar shows "Update Available" state
3. **Confirmation Dialog** - Asks user to restart
4. **IPC Handlers** - Communication between main and renderer
What's missing: **The publish URL configuration**.
## How Auto-Update Works
```
┌─────────────────────────────────────────────────────────────────────┐
│ Auto-Update Flow │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. App Starts │
│ │ │
│ ▼ │
│ 2. Check GitHub Releases for latest-{platform}.yml │
│ │ │
│ ▼ │
│ 3. Compare versions (semver) │
│ │ │
│ ├─── Same version ──► Do nothing, check again in 60s │
│ │ │
│ └─── New version ──► Download in background │
│ │ │
│ ▼ │
│ 4. Download complete ──► Show "Update Available" in TitleBar │
│ │ │
│ ▼ │
│ 5. User clicks ──► Show confirmation dialog │
│ │ │
│ ├─── "Later" ──► Dismiss │
│ │ │
│ └─── "Restart" ──► quitAndInstall() │
│ │ │
│ ▼ │
│ 6. App restarts with new version │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
## Implementation
### Step 1: Add Publish Configuration
**`packages/noodl-editor/package.json`**
```json
{
"build": {
"publish": {
"provider": "github",
"owner": "the-low-code-foundation",
"repo": "opennoodl",
"releaseType": "release"
}
}
}
```
**Configuration Options:**
| Setting | Value | Description |
|---------|-------|-------------|
| `provider` | `"github"` | Use GitHub Releases |
| `owner` | `"the-low-code-foundation"` | GitHub org/user |
| `repo` | `"opennoodl"` | Repository name |
| `releaseType` | `"release"` | Only stable releases (not drafts/prereleases) |
### Step 2: Update autoupdater.js
**`packages/noodl-editor/src/main/src/autoupdater.js`**
```javascript
const { app, ipcMain } = require('electron');
const { autoUpdater } = require('electron-updater');
const log = require('electron-log');
// Configure logging
autoUpdater.logger = log;
autoUpdater.logger.transports.file.level = 'info';
// Disable auto-download so user can choose
autoUpdater.autoDownload = false;
function setupAutoUpdate(window) {
// Skip in dev mode
if (process.env.devMode === 'true' || process.env.autoUpdate === 'no') {
log.info('Auto-update disabled in dev mode');
return;
}
// Linux: Only AppImage supports auto-update
if (process.platform === 'linux' && !process.env.APPIMAGE) {
log.info('Auto-update only available for AppImage on Linux');
return;
}
// Check for updates on startup
checkForUpdates();
// Check periodically (every 60 seconds)
setInterval(checkForUpdates, 60 * 1000);
function checkForUpdates() {
log.info('Checking for updates...');
autoUpdater.checkForUpdates().catch((err) => {
log.error('Update check failed:', err);
});
}
// Update available - ask user if they want to download
autoUpdater.on('update-available', (info) => {
log.info('Update available:', info.version);
// Start download automatically (runs in background)
autoUpdater.downloadUpdate().catch((err) => {
log.error('Download failed:', err);
});
});
// No update available
autoUpdater.on('update-not-available', (info) => {
log.info('No update available. Current version:', app.getVersion());
});
// Download progress
autoUpdater.on('download-progress', (progress) => {
log.info(`Download progress: ${progress.percent.toFixed(1)}%`);
// Optionally send to renderer for progress UI
if (window && !window.isDestroyed()) {
window.webContents.send('updateDownloadProgress', progress);
}
});
// Download complete - notify user
autoUpdater.on('update-downloaded', (info) => {
log.info('Update downloaded:', info.version);
if (window && !window.isDestroyed()) {
window.webContents.send('showAutoUpdatePopup', {
version: info.version,
releaseNotes: info.releaseNotes
});
}
});
// Handle user response
ipcMain.on('autoUpdatePopupClosed', (event, restartNow) => {
if (restartNow) {
log.info('User requested restart for update');
autoUpdater.quitAndInstall(false, true);
} else {
log.info('User deferred update');
}
});
// Error handling
autoUpdater.on('error', (error) => {
log.error('Auto-updater error:', error);
// Don't spam logs - wait before retrying
setTimeout(checkForUpdates, 5 * 60 * 1000); // Retry in 5 minutes
});
}
module.exports = {
setupAutoUpdate
};
```
### Step 3: Enhance Update Dialog (Optional)
**`packages/noodl-editor/src/editor/src/views/windows/BaseWindow/BaseWindow.tsx`**
```typescript
const [AutoUpdateDialog, autoUpdateConfirmation] = useConfirmationDialog({
title: 'Update Available',
message: `Version ${updateInfo?.version || 'new'} is ready to install.
Release notes:
${updateInfo?.releaseNotes || 'Bug fixes and improvements.'}
Restart now to update?`,
confirmButtonLabel: 'Restart Now',
cancelButtonLabel: 'Later'
});
```
### Step 4: Update Manifests
When you build with `--publish always`, electron-builder creates:
**`latest.yml`** (Windows)
```yaml
version: 1.2.0
files:
- url: Nodegex-Setup-1.2.0.exe
sha512: abc123...
size: 85000000
path: Nodegex-Setup-1.2.0.exe
sha512: abc123...
releaseDate: '2024-01-15T10:30:00.000Z'
```
**`latest-mac.yml`** (macOS)
```yaml
version: 1.2.0
files:
- url: Nodegex-1.2.0-arm64.dmg
sha512: def456...
size: 150000000
- url: Nodegex-1.2.0-x64.dmg
sha512: ghi789...
size: 155000000
path: Nodegex-1.2.0-arm64.dmg
sha512: def456...
releaseDate: '2024-01-15T10:30:00.000Z'
```
**`latest-linux.yml`** (Linux)
```yaml
version: 1.2.0
files:
- url: Nodegex-1.2.0-x64.AppImage
sha512: jkl012...
size: 120000000
path: Nodegex-1.2.0-x64.AppImage
sha512: jkl012...
releaseDate: '2024-01-15T10:30:00.000Z'
```
### Step 5: Test Locally
**Create a test release:**
```bash
# Build with publish (but don't actually publish)
cd packages/noodl-editor
npx electron-builder --mac --publish never
# Check generated files
ls dist/
# Should include: latest-mac.yml
```
**Test update detection:**
```bash
# 1. Install an older version
# 2. Create a GitHub Release with newer version
# 3. Launch old version
# 4. Watch logs for update detection:
tail -f ~/Library/Logs/Nodegex/main.log
```
### Step 6: Configure Release Channels (Optional)
For beta/alpha testing:
```javascript
// In autoupdater.js
autoUpdater.channel = 'latest'; // Default
// Or allow user to opt into beta:
autoUpdater.channel = userPreferences.updateChannel || 'latest';
// Channels: 'latest' (stable), 'beta', 'alpha'
```
electron-builder creates separate manifests:
- `latest.yml` - Stable releases
- `beta.yml` - Beta releases
- `alpha.yml` - Alpha releases
## Files to Modify
| File | Changes |
|------|---------|
| `packages/noodl-editor/package.json` | Add publish configuration |
| `packages/noodl-editor/src/main/src/autoupdater.js` | Enhance with logging, progress |
| `packages/noodl-editor/src/editor/src/views/windows/BaseWindow/BaseWindow.tsx` | Optional: Better update dialog |
## Testing Checklist
### Local Testing
- [ ] Build produces `latest-*.yml` files
- [ ] App connects to GitHub Releases API
- [ ] Update detection works (with test release)
- [ ] Download progress shown (optional)
- [ ] "Restart" installs update
- [ ] "Later" dismisses dialog
- [ ] App restarts with new version
### Platform Testing
- [ ] macOS Intel: Download correct arch
- [ ] macOS ARM: Download correct arch
- [ ] Windows: NSIS installer works
- [ ] Linux AppImage: Update replaces file
### Edge Cases
- [ ] Offline: Graceful failure, retry later
- [ ] Partial download: Resume or restart
- [ ] Corrupted download: SHA512 check fails, retry
- [ ] Downgrade prevention: Don't install older version
## Troubleshooting
### Update not detected
Check logs:
```bash
# macOS
cat ~/Library/Logs/Nodegex/main.log
# Windows
type %USERPROFILE%\AppData\Roaming\Nodegex\logs\main.log
# Linux
cat ~/.config/Nodegex/logs/main.log
```
### Wrong architecture downloaded
Verify `latest-mac.yml` has both arch entries and correct `sha512` values.
### "Cannot find latest.yml"
Either:
1. Build wasn't published to GitHub Releases
2. Release is still in draft mode
3. Network/proxy issues
### Update downloads but doesn't install
Check:
1. SHA512 mismatch (corrupted download)
2. Disk space
3. Permissions (can write to app directory)
## Success Criteria
1. ✅ App checks for updates on startup
2. ✅ Update notification appears for new releases
3. ✅ Background download doesn't interrupt work
4. ✅ Restart installs update seamlessly
5. ✅ User data preserved after update
6. ✅ Works on all three platforms

View File

@@ -0,0 +1,386 @@
# Task 7.4: Linux Universal Distribution
## Problem Statement
Linux support is currently incomplete:
- Only `.deb` target configured
- Someone added Arch Linux support (AUR?) but status unclear
- No AppImage for universal distribution
- No auto-update support for Linux
- Native modules (dugite, desktop-trampoline) may have compatibility issues
## Goals
1. **AppImage**: Universal format that works on any Linux distribution
2. **.deb**: Native experience for Debian/Ubuntu users (largest desktop Linux market)
3. **Auto-update**: AppImage supports electron-updater
4. **Tested**: Verified on Ubuntu 22.04 LTS and 24.04 LTS
## Linux Desktop Market Context
| Distribution Family | Market Share | Target Format |
|---------------------|--------------|---------------|
| Ubuntu/Debian | ~60% | .deb + AppImage |
| Fedora/RHEL | ~15% | AppImage (RPM optional) |
| Arch | ~10% | AppImage + AUR |
| Other | ~15% | AppImage |
**Decision**: AppImage as primary (works everywhere), .deb as secondary (native experience for majority).
## Implementation
### Phase 1: Configure electron-builder for Linux
**Step 1.1: Update package.json**
```json
{
"build": {
"linux": {
"target": [
{
"target": "AppImage",
"arch": ["x64"]
},
{
"target": "deb",
"arch": ["x64"]
}
],
"category": "Development",
"icon": "build/icons",
"synopsis": "Visual low-code React development platform",
"description": "Nodegex is a full-stack low-code platform for building React applications visually.",
"desktop": {
"Name": "Nodegex",
"Comment": "Visual React Development",
"Categories": "Development;IDE;",
"Keywords": "react;low-code;visual;programming;node;"
},
"mimeTypes": [
"x-scheme-handler/nodegex"
]
},
"appImage": {
"artifactName": "${productName}-${version}-${arch}.AppImage",
"license": "LICENSE"
},
"deb": {
"artifactName": "${productName}-${version}-${arch}.deb",
"depends": [
"libgtk-3-0",
"libnotify4",
"libnss3",
"libxss1",
"libxtst6",
"xdg-utils",
"libatspi2.0-0",
"libuuid1",
"libsecret-1-0"
],
"category": "Development"
}
}
}
```
### Phase 2: Handle Native Modules
The trickiest part is ensuring dugite's embedded git and desktop-trampoline work on Linux.
**Step 2.1: Verify dugite Linux binaries**
dugite downloads platform-specific git binaries. Verify they're included:
```bash
# After build, check the AppImage contents
./Nodegex-1.2.0-x64.AppImage --appimage-extract
ls -la squashfs-root/resources/app.asar.unpacked/node_modules/dugite/git/
```
**Step 2.2: Check desktop-trampoline**
```bash
ls -la squashfs-root/resources/app.asar.unpacked/node_modules/desktop-trampoline/build/Release/
file squashfs-root/resources/app.asar.unpacked/node_modules/desktop-trampoline/build/Release/desktop-trampoline
```
**Step 2.3: Verify library dependencies**
```bash
# Check for missing shared libraries
ldd squashfs-root/resources/app.asar.unpacked/node_modules/dugite/git/bin/git
ldd squashfs-root/nodegex
```
### Phase 3: AppImage Auto-Update Support
AppImage is the only Linux format that supports electron-updater.
**Step 3.1: How it works**
1. electron-updater checks GitHub Releases for `latest-linux.yml`
2. Downloads new `.AppImage` to temp location
3. User confirms restart
4. New AppImage replaces old one
5. App restarts
**Step 3.2: Required publish config**
```json
{
"build": {
"publish": {
"provider": "github",
"owner": "the-low-code-foundation",
"repo": "opennoodl"
}
}
}
```
**Step 3.3: Auto-update behavior**
The existing `autoupdater.js` already handles Linux correctly:
```javascript
if (process.platform === 'linux') {
return; // Currently disabled
}
```
We need to **enable** it for AppImage:
```javascript
function setupAutoUpdate(window) {
if (process.env.autoUpdate === 'no') return;
// AppImage auto-update works, .deb does not
if (process.platform === 'linux' && !process.env.APPIMAGE) {
console.log('Auto-update only available for AppImage');
return;
}
// ... rest of auto-update logic
}
```
### Phase 4: Icon Generation
Linux needs multiple icon sizes in PNG format.
**Step 4.1: Create icon set**
```bash
# From a 1024x1024 source icon
mkdir -p build/icons
for size in 16 24 32 48 64 128 256 512 1024; do
convert icon-source.png -resize ${size}x${size} build/icons/${size}x${size}.png
done
```
**Step 4.2: Directory structure**
```
build/
icons/
16x16.png
24x24.png
32x32.png
48x48.png
64x64.png
128x128.png
256x256.png
512x512.png
1024x1024.png
```
### Phase 5: Protocol Handler Registration
For `nodegex://` URLs to work:
**Step 5.1: Desktop file configuration**
The `mimeTypes` config in package.json creates the association. Additionally, update the `protocols` config:
```json
{
"build": {
"protocols": {
"name": "nodegex",
"schemes": ["nodegex"]
}
}
}
```
**Step 5.2: Manual registration (if needed)**
```bash
# For AppImage users who need manual registration
xdg-mime default nodegex.desktop x-scheme-handler/nodegex
```
### Phase 6: Build Script
**Step 6.1: Create Linux build script**
```bash
#!/bin/bash
# scripts/build-linux.sh
set -e
# Build for x64 (most common)
# ARM64 support would require additional setup for native modules
echo "Building Linux targets..."
# AppImage
npx electron-builder --linux AppImage --x64
# Debian package
npx electron-builder --linux deb --x64
echo "Build complete!"
echo ""
echo "Outputs:"
ls -la dist/*.AppImage dist/*.deb 2>/dev/null || echo "No artifacts found"
```
**Step 6.2: Add to package.json**
```json
{
"scripts": {
"build:linux": "./scripts/build-linux.sh",
"build:linux:appimage": "electron-builder --linux AppImage --x64",
"build:linux:deb": "electron-builder --linux deb --x64"
}
}
```
### Phase 7: Testing
**Step 7.1: Test on fresh Ubuntu VM**
```bash
# Ubuntu 22.04 LTS
sudo apt update
sudo apt install -y libfuse2 # Required for AppImage
chmod +x Nodegex-1.2.0-x64.AppImage
./Nodegex-1.2.0-x64.AppImage
```
**Step 7.2: Test .deb installation**
```bash
sudo dpkg -i Nodegex-1.2.0-x64.deb
# If dependencies missing:
sudo apt-get install -f
# Launch
nodegex
```
**Step 7.3: Test protocol handler**
```bash
xdg-open "nodegex://test"
```
**Step 7.4: Verify auto-update (AppImage only)**
1. Install older version
2. Create GitHub Release with newer version
3. Wait for update notification
4. Click "Restart"
5. Verify new version launches
## Known Issues & Workarounds
### AppImage FUSE dependency
Ubuntu 22.04+ doesn't include FUSE by default:
```bash
# Users need to install:
sudo apt install libfuse2
```
Document this in release notes.
### Wayland compatibility
Some Electron features behave differently on Wayland vs X11:
```bash
# Force X11 if issues occur
GDK_BACKEND=x11 ./Nodegex.AppImage
```
### Sandbox issues on some distributions
If sandbox errors occur:
```bash
# Disable sandbox (less secure but works)
./Nodegex.AppImage --no-sandbox
```
Or fix system-wide:
```bash
sudo sysctl -w kernel.unprivileged_userns_clone=1
```
## Files to Modify
| File | Changes |
|------|---------|
| `packages/noodl-editor/package.json` | Add Linux targets, icons, desktop config |
| `packages/noodl-editor/src/main/src/autoupdater.js` | Enable for AppImage |
| `packages/noodl-editor/build/icons/` | Add PNG icon set |
| `scripts/build-linux.sh` | Create build script |
## Success Criteria
1. ✅ AppImage runs on fresh Ubuntu 22.04 LTS
2. ✅ AppImage runs on fresh Ubuntu 24.04 LTS
3. ✅ .deb installs without manual dependency resolution
4. ✅ Auto-update works for AppImage distribution
5.`nodegex://` protocol handler works
6. ✅ Desktop integration (icon, menu entry) works
## Distribution Channels
### GitHub Releases
Both AppImage and .deb uploaded to releases.
### Optional: Snapcraft
```yaml
# snap/snapcraft.yaml (future enhancement)
name: nodegex
base: core22
version: '1.2.0'
summary: Visual low-code React development platform
confinement: classic
```
### Optional: Flathub
Flatpak provides another universal format but requires more setup and maintenance.
## ARM64 Consideration
ARM64 Linux (Raspberry Pi, etc.) would require:
- Cross-compilation setup
- ARM64 dugite binaries
- ARM64 desktop-trampoline build
This is out of scope for initial release but could be added later.

View File

@@ -0,0 +1,467 @@
# 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