Skip to content

fix(ci): add native module rebuild for Electron #35

fix(ci): add native module rebuild for Electron

fix(ci): add native module rebuild for Electron #35

Workflow file for this run

# ==============================================
# NoteCode Release Pipeline
# ==============================================
# Triggered by pushing a version tag (v*)
# Publishes to: npm registry + GitHub Releases (Electron binaries)
#
# Usage:
# git tag v1.0.0 && git push origin main --tags
#
# Pre-release:
# git tag v1.0.0-beta.1 && git push origin main --tags
name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write # Required for creating GitHub Releases
env:
NODE_VERSION: '20'
jobs:
# ============================================
# Job 1: Publish to npm registry
# ============================================
npm-publish:
name: Publish to npm
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: 'https://registry.npmjs.org'
- name: Cache node_modules
uses: actions/cache@v4
with:
path: |
node_modules
backend/node_modules
frontend/node_modules
key: npm-publish-${{ runner.os }}-${{ hashFiles('package.json', 'backend/package.json', 'frontend/package.json') }}
restore-keys: npm-publish-${{ runner.os }}-
- name: Install dependencies
run: npm install --ignore-scripts
# --ignore-scripts: skip electron rebuild (not needed for npm publish)
- name: Build backend
run: npm run build --prefix backend
- name: Build frontend
run: npm run build --prefix frontend
- name: Publish to npm
run: npm publish || echo "::warning::npm publish failed (version may already exist)"
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# ============================================
# Job 2: Build Electron (matrix: win/mac/linux)
# ============================================
electron-build:
name: Build Electron (${{ matrix.platform }})
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
platform: win
- os: macos-latest
platform: mac
- os: ubuntu-latest
platform: linux
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
# Cache node_modules and Electron binaries per OS
- name: Cache node_modules
uses: actions/cache@v4
with:
path: |
node_modules
backend/node_modules
frontend/node_modules
electron/node_modules
key: electron-deps-v2-${{ runner.os }}-${{ hashFiles('package.json', 'backend/package.json', 'frontend/package.json', 'electron/package.json') }}
# NO restore-keys fallback - ensure exact match only
# Cache ONLY Electron binaries (not electron-builder cache - causes stale asar issues)
- name: Cache Electron binaries
uses: actions/cache@v4
with:
path: |
~/.cache/electron
~/AppData/Local/electron/Cache
~/Library/Caches/electron
key: electron-bin-v2-${{ runner.os }}-${{ hashFiles('electron/package.json') }}
# NO restore-keys fallback - ensure exact match only
# Disable Windows Defender real-time scanning — causes massive I/O slowdown
# when 7z processes thousands of small files in node_modules
- name: Disable Windows Defender (Windows only)
if: runner.os == 'Windows'
run: Set-MpPreference -DisableRealtimeMonitoring $true
shell: powershell
- name: Install all dependencies
run: npm install --ignore-scripts
- name: Install backend dependencies
run: npm install --prefix backend --ignore-scripts
- name: Install frontend dependencies
run: npm install --prefix frontend
- name: Install electron dependencies
run: npm install --prefix electron --ignore-scripts
# CRITICAL: Rebuild native modules (better-sqlite3) for Electron runtime
- name: Rebuild native modules for Electron
run: cd electron && npx @electron/rebuild -f -m ../backend -o better-sqlite3
- name: Build backend
run: cd backend && npm run build
- name: Build frontend
run: cd frontend && npm run build
- name: Build electron
run: cd electron && npm run build
# CRITICAL: Strict verification - FAIL if unexpected files or bundling failed
- name: Verify electron dist (STRICT)
run: |
node -e "
const fs = require('fs');
const path = require('path');
const distDir = 'electron/dist';
const files = fs.readdirSync(distDir);
const expected = ['main.js', 'preload.js'];
console.log('Files in dist:', files);
// Check for unexpected files
const unexpected = files.filter(f => !expected.includes(f));
if (unexpected.length > 0) {
console.error('FATAL: Unexpected files in dist:', unexpected);
console.error('This indicates tsc output mixed with esbuild output!');
process.exit(1);
}
// Verify main.js is bundled (should be > 500KB with electron-updater inlined)
const mainSize = fs.statSync(path.join(distDir, 'main.js')).size;
console.log('main.js size:', mainSize, 'bytes');
if (mainSize < 500000) {
console.error('FATAL: main.js too small (' + mainSize + ' bytes)');
console.error('electron-updater may not be bundled!');
process.exit(1);
}
console.log('✓ Verification passed: only main.js and preload.js, bundled correctly');
"
# Remove workspace symlinks that cause infinite traversal in 7z/electron-builder
- name: Remove workspace symlinks
run: |
node -e "const fs=require('fs'); ['notecode','notecode-app'].forEach(p => { ['backend/node_modules/','electron/node_modules/'].forEach(d => { const t=d+p; try { fs.rmSync(t,{recursive:true,force:true}); console.log('Removed',t); } catch(e) { console.log('Skip',t,e.message); } }); });"
# Clear electron-builder cache to prevent stale asar content
- name: Clear electron-builder cache
run: |
node -e "
const fs = require('fs');
const os = require('os');
const path = require('path');
const caches = [
path.join(os.homedir(), '.cache', 'electron-builder'),
path.join(os.homedir(), 'AppData', 'Local', 'electron-builder', 'Cache'),
path.join(os.homedir(), 'Library', 'Caches', 'electron-builder'),
path.join('..', 'release')
];
caches.forEach(c => {
try { fs.rmSync(c, {recursive:true,force:true}); console.log('Cleared:', c); }
catch(e) { console.log('Skip:', c); }
});
"
- name: Package Electron (${{ matrix.platform }})
run: cd electron && npx --no-install electron-builder --${{ matrix.platform }} --publish never
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEBUG: electron-builder
ELECTRON_BUILDER_COMPRESSION_LEVEL: 0
# Code signing (optional - add when certificates are available)
# CSC_LINK: ${{ secrets.MAC_CERT_P12 }}
# CSC_KEY_PASSWORD: ${{ secrets.MAC_CERT_PASSWORD }}
# WIN_CSC_LINK: ${{ secrets.WIN_CERT_PFX }}
# WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CERT_PASSWORD }}
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: electron-${{ matrix.platform }}
path: |
release/*.exe
release/*.dmg
release/*.zip
release/*.AppImage
release/*.deb
release/*.rpm
release/*.yml
release/*.yaml
if-no-files-found: warn
retention-days: 7
# ============================================
# Job 3: Create GitHub Release with artifacts
# ============================================
create-release:
name: Create GitHub Release
needs: [npm-publish, electron-build]
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for changelog generation
- name: Download all Electron artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
pattern: electron-*
merge-multiple: true
- name: List artifacts
run: ls -la artifacts/ || echo "No artifacts found"
- name: Generate changelog from commits
id: changelog
run: |
# Find previous tag for changelog range
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [ -z "$PREV_TAG" ]; then
CHANGELOG=$(git log --oneline --pretty=format:"- %s" HEAD)
else
CHANGELOG=$(git log --oneline --pretty=format:"- %s" ${PREV_TAG}..HEAD)
fi
# Write to output (multiline safe)
echo "changelog<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGELOG" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: "NoteCode ${{ github.ref_name }}"
body: |
## What's Changed
${{ steps.changelog.outputs.changelog }}
## Downloads
| Platform | File |
|----------|------|
| Windows (installer) | `NoteCode-*-win-x64.exe` |
| Windows (portable) | `NoteCode-*-win-x64-portable.exe` |
| macOS (Intel) | `NoteCode-*-mac-x64.dmg` |
| macOS (Apple Silicon) | `NoteCode-*-mac-arm64.dmg` |
| Linux (AppImage) | `NoteCode-*-linux-x64.AppImage` |
| Linux (deb) | `NoteCode-*-linux-x64.deb` |
| Linux (rpm) | `NoteCode-*-linux-x64.rpm` |
## CLI (npm)
```bash
npx notecode-app
# or install globally
npm install -g notecode-app
```
files: artifacts/*
draft: false
prerelease: ${{ contains(github.ref_name, 'beta') || contains(github.ref_name, 'alpha') || contains(github.ref_name, 'rc') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}