From 41a507fe8bb6b000a9d2d426eaefa559925d7908 Mon Sep 17 00:00:00 2001 From: AndyMik90 Date: Thu, 18 Dec 2025 23:53:06 +0100 Subject: [PATCH] feat: auto-download prebuilt node-pty binaries for Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eliminates the need for Visual Studio Build Tools on Windows by: 1. GitHub Actions workflow (.github/workflows/build-prebuilds.yml) - Builds node-pty for Windows x64 with correct Electron ABI - Uploads prebuilt binaries as release assets - Triggered on releases and manual dispatch 2. Smart postinstall script (auto-claude-ui/scripts/postinstall.js) - On Windows: tries to download prebuilts first - Falls back to electron-rebuild if prebuilts unavailable - Shows clear instructions if compilation fails 3. Download helper (auto-claude-ui/scripts/download-prebuilds.js) - Fetches prebuilt binaries from GitHub releases - Extracts and installs to node_modules/node-pty Windows users can now run `npm install` without installing Visual Studio Build Tools, as long as prebuilt binaries exist for their Electron version. Fixes #8 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .github/workflows/build-prebuilds.yml | 132 ++++++++++ README.md | 12 + auto-claude-ui/package.json | 2 +- auto-claude-ui/scripts/download-prebuilds.js | 248 +++++++++++++++++++ auto-claude-ui/scripts/postinstall.js | 140 +++++++++++ 5 files changed, 533 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-prebuilds.yml create mode 100644 auto-claude-ui/scripts/download-prebuilds.js create mode 100644 auto-claude-ui/scripts/postinstall.js diff --git a/.github/workflows/build-prebuilds.yml b/.github/workflows/build-prebuilds.yml new file mode 100644 index 00000000..34765831 --- /dev/null +++ b/.github/workflows/build-prebuilds.yml @@ -0,0 +1,132 @@ +name: Build Native Module Prebuilds + +on: + # Build on releases + release: + types: [published] + # Manual trigger for testing + workflow_dispatch: + inputs: + electron_version: + description: 'Electron version to build for' + required: false + default: '39.2.6' + +env: + # Default Electron version - update when upgrading Electron in package.json + ELECTRON_VERSION: ${{ github.event.inputs.electron_version || '39.2.6' }} + +jobs: + build-windows: + runs-on: windows-latest + strategy: + matrix: + arch: [x64] + # Add arm64 when GitHub Actions supports Windows ARM runners + # arch: [x64, arm64] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Install Visual Studio Build Tools + uses: microsoft/setup-msbuild@v2 + + - name: Install node-pty and rebuild for Electron + working-directory: auto-claude-ui + shell: pwsh + run: | + # Install only node-pty + pnpm add node-pty@1.1.0-beta42 + + # Get Electron ABI version + $electronAbi = (npx electron-abi $env:ELECTRON_VERSION) + Write-Host "Building for Electron $env:ELECTRON_VERSION (ABI: $electronAbi)" + + # Rebuild node-pty for Electron + npx @electron/rebuild --version $env:ELECTRON_VERSION --module-dir node_modules/node-pty --arch ${{ matrix.arch }} + + - name: Package prebuilt binaries + working-directory: auto-claude-ui + shell: pwsh + run: | + $electronAbi = (npx electron-abi $env:ELECTRON_VERSION) + $prebuildDir = "prebuilds/win32-${{ matrix.arch }}-electron-$electronAbi" + + New-Item -ItemType Directory -Force -Path $prebuildDir + + # Copy all built native files + $buildDir = "node_modules/node-pty/build/Release" + if (Test-Path $buildDir) { + Copy-Item "$buildDir/*.node" $prebuildDir/ -Force + Copy-Item "$buildDir/*.dll" $prebuildDir/ -Force -ErrorAction SilentlyContinue + Copy-Item "$buildDir/*.exe" $prebuildDir/ -Force -ErrorAction SilentlyContinue + + # Also copy conpty files if they exist in subdirectory + if (Test-Path "$buildDir/conpty") { + Copy-Item "$buildDir/conpty/*" $prebuildDir/ -Force + } + } + + # List what we packaged + Write-Host "Packaged prebuilds:" + Get-ChildItem $prebuildDir + + - name: Create archive + working-directory: auto-claude-ui + shell: pwsh + run: | + $electronAbi = (npx electron-abi $env:ELECTRON_VERSION) + $archiveName = "node-pty-win32-${{ matrix.arch }}-electron-$electronAbi.zip" + + Compress-Archive -Path "prebuilds/*" -DestinationPath $archiveName + + Write-Host "Created archive: $archiveName" + Get-ChildItem $archiveName + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: node-pty-win32-${{ matrix.arch }} + path: auto-claude-ui/node-pty-*.zip + retention-days: 90 + + - name: Upload to release + if: github.event_name == 'release' + uses: softprops/action-gh-release@v1 + with: + files: auto-claude-ui/node-pty-*.zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Create a combined prebuilds package + package-prebuilds: + needs: build-windows + runs-on: ubuntu-latest + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: List artifacts + run: | + echo "Downloaded artifacts:" + find artifacts -type f -name "*.zip" + + - name: Upload combined artifact + uses: actions/upload-artifact@v4 + with: + name: node-pty-prebuilds-all + path: artifacts/**/*.zip + retention-days: 90 diff --git a/README.md b/README.md index c405fd8f..cbd602fe 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,18 @@ pnpm run build && pnpm run start # or: npm run build && npm run start ``` +
+Windows users: If installation fails with node-gyp errors, click here + +Auto Claude automatically downloads prebuilt binaries for Windows. If prebuilts aren't available for your Electron version yet, you'll need Visual Studio Build Tools: + +1. Download [Visual Studio Build Tools 2022](https://visualstudio.microsoft.com/visual-cpp-build-tools/) +2. Select "Desktop development with C++" workload +3. In "Individual Components", add "MSVC v143 - VS 2022 C++ x64/x86 Spectre-mitigated libs" +4. Restart terminal and run `npm install` again + +
+ ### Step 4: Start Building 1. Add your project in the UI diff --git a/auto-claude-ui/package.json b/auto-claude-ui/package.json index 056bc99b..55c2ee88 100644 --- a/auto-claude-ui/package.json +++ b/auto-claude-ui/package.json @@ -6,7 +6,7 @@ "author": "Auto Claude Team", "license": "AGPL-3.0", "scripts": { - "postinstall": "electron-rebuild", + "postinstall": "node scripts/postinstall.js", "dev": "electron-vite dev", "build": "electron-vite build", "start": "electron .", diff --git a/auto-claude-ui/scripts/download-prebuilds.js b/auto-claude-ui/scripts/download-prebuilds.js new file mode 100644 index 00000000..4dc13530 --- /dev/null +++ b/auto-claude-ui/scripts/download-prebuilds.js @@ -0,0 +1,248 @@ +#!/usr/bin/env node +/** + * Download prebuilt native modules for Windows + * + * This script downloads pre-compiled node-pty binaries from GitHub releases, + * eliminating the need for Visual Studio Build Tools on Windows. + */ + +const https = require('https'); +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +const GITHUB_REPO = 'AndyMik90/Auto-Claude'; +const GITHUB_API = 'https://api.github.com'; + +/** + * Get the Electron ABI version for the installed Electron + */ +function getElectronAbi() { + try { + // Try to get from electron-abi package + const result = execSync('npx electron-abi', { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'], + }).trim(); + return result; + } catch { + // Fallback: read from electron package + try { + const electronPkg = require('electron/package.json'); + const version = electronPkg.version; + // Electron 39.x = ABI 140 + const majorVersion = parseInt(version.split('.')[0], 10); + // This is a rough mapping, electron-abi is more accurate + const abiMap = { + 39: 140, + 38: 139, + 37: 136, + 36: 135, + 35: 134, + 34: 132, + 33: 131, + 32: 130, + 31: 129, + 30: 128, + }; + return abiMap[majorVersion] || null; + } catch { + return null; + } + } +} + +/** + * Get the latest release from GitHub + */ +function getLatestRelease() { + return new Promise((resolve, reject) => { + const options = { + hostname: 'api.github.com', + path: `/repos/${GITHUB_REPO}/releases/latest`, + headers: { + 'User-Agent': 'Auto-Claude-Installer', + Accept: 'application/vnd.github.v3+json', + }, + }; + + https + .get(options, (res) => { + let data = ''; + res.on('data', (chunk) => (data += chunk)); + res.on('end', () => { + if (res.statusCode === 200) { + resolve(JSON.parse(data)); + } else if (res.statusCode === 404) { + resolve(null); // No releases yet + } else { + reject(new Error(`GitHub API returned ${res.statusCode}`)); + } + }); + }) + .on('error', reject); + }); +} + +/** + * Find prebuild asset in release + */ +function findPrebuildAsset(release, arch, electronAbi) { + if (!release || !release.assets) return null; + + const assetName = `node-pty-win32-${arch}-electron-${electronAbi}.zip`; + return release.assets.find((asset) => asset.name === assetName); +} + +/** + * Download a file from URL + */ +function downloadFile(url, destPath) { + return new Promise((resolve, reject) => { + const file = fs.createWriteStream(destPath); + + const request = (url) => { + https + .get(url, { headers: { 'User-Agent': 'Auto-Claude-Installer' } }, (res) => { + if (res.statusCode === 302 || res.statusCode === 301) { + // Follow redirect + request(res.headers.location); + return; + } + + if (res.statusCode !== 200) { + reject(new Error(`Download failed with status ${res.statusCode}`)); + return; + } + + res.pipe(file); + file.on('finish', () => { + file.close(); + resolve(); + }); + }) + .on('error', (err) => { + fs.unlink(destPath, () => {}); // Delete partial file + reject(err); + }); + }; + + request(url); + }); +} + +/** + * Extract zip file (using built-in tools) + */ +function extractZip(zipPath, destDir) { + const { execSync } = require('child_process'); + + // Use PowerShell on Windows + execSync(`powershell -Command "Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force"`, { + stdio: 'inherit', + }); +} + +/** + * Main function to download and install prebuilds + */ +async function downloadPrebuilds() { + const arch = process.arch; // x64 or arm64 + const electronAbi = getElectronAbi(); + + if (!electronAbi) { + console.log('[prebuilds] Could not determine Electron ABI version'); + return { success: false, reason: 'unknown-abi' }; + } + + console.log(`[prebuilds] Looking for prebuilds: win32-${arch}, Electron ABI ${electronAbi}`); + + // Check for prebuilds in GitHub releases + let release; + try { + release = await getLatestRelease(); + } catch (err) { + console.log(`[prebuilds] Could not fetch releases: ${err.message}`); + return { success: false, reason: 'fetch-failed' }; + } + + if (!release) { + console.log('[prebuilds] No releases found'); + return { success: false, reason: 'no-releases' }; + } + + const asset = findPrebuildAsset(release, arch, electronAbi); + if (!asset) { + console.log(`[prebuilds] No prebuild found for win32-${arch}-electron-${electronAbi}`); + console.log('[prebuilds] Available assets:', release.assets?.map((a) => a.name).join(', ') || 'none'); + return { success: false, reason: 'no-matching-prebuild' }; + } + + console.log(`[prebuilds] Found prebuild: ${asset.name}`); + + // Download the prebuild + const tempDir = path.join(__dirname, '..', '.prebuild-temp'); + const zipPath = path.join(tempDir, asset.name); + const nodePtyDir = path.join(__dirname, '..', 'node_modules', 'node-pty'); + const buildDir = path.join(nodePtyDir, 'build', 'Release'); + + try { + // Create temp directory + fs.mkdirSync(tempDir, { recursive: true }); + + console.log(`[prebuilds] Downloading ${asset.name}...`); + await downloadFile(asset.browser_download_url, zipPath); + + console.log('[prebuilds] Extracting...'); + extractZip(zipPath, tempDir); + + // Find the extracted prebuild directory + const extractedDir = path.join(tempDir, 'prebuilds', `win32-${arch}-electron-${electronAbi}`); + + if (!fs.existsSync(extractedDir)) { + throw new Error(`Extracted directory not found: ${extractedDir}`); + } + + // Ensure build/Release directory exists + fs.mkdirSync(buildDir, { recursive: true }); + + // Copy files to node_modules/node-pty/build/Release + const files = fs.readdirSync(extractedDir); + for (const file of files) { + const src = path.join(extractedDir, file); + const dest = path.join(buildDir, file); + fs.copyFileSync(src, dest); + console.log(`[prebuilds] Installed: ${file}`); + } + + // Cleanup temp directory + fs.rmSync(tempDir, { recursive: true, force: true }); + + console.log('[prebuilds] Successfully installed prebuilt binaries!'); + return { success: true }; + } catch (err) { + // Cleanup on error + if (fs.existsSync(tempDir)) { + fs.rmSync(tempDir, { recursive: true, force: true }); + } + console.log(`[prebuilds] Download/extract failed: ${err.message}`); + return { success: false, reason: 'install-failed', error: err.message }; + } +} + +// Export for use by postinstall +module.exports = { downloadPrebuilds, getElectronAbi }; + +// Run if called directly +if (require.main === module) { + downloadPrebuilds() + .then((result) => { + if (!result.success) { + process.exit(1); + } + }) + .catch((err) => { + console.error('[prebuilds] Error:', err); + process.exit(1); + }); +} diff --git a/auto-claude-ui/scripts/postinstall.js b/auto-claude-ui/scripts/postinstall.js new file mode 100644 index 00000000..b071cfd6 --- /dev/null +++ b/auto-claude-ui/scripts/postinstall.js @@ -0,0 +1,140 @@ +#!/usr/bin/env node +/** + * Post-install script for Auto Claude UI + * + * On Windows: + * 1. Try to download prebuilt node-pty binaries from GitHub releases + * 2. Fall back to electron-rebuild if prebuilds aren't available + * 3. Show helpful error message if compilation fails + * + * On macOS/Linux: + * 1. Run electron-rebuild (compilers are typically available) + */ + +const { spawn } = require('child_process'); +const os = require('os'); +const path = require('path'); +const fs = require('fs'); + +const isWindows = os.platform() === 'win32'; + +const WINDOWS_BUILD_TOOLS_HELP = ` +================================================================================ + VISUAL STUDIO BUILD TOOLS REQUIRED +================================================================================ + +Prebuilt binaries weren't available for your Electron version, and compilation +requires Visual Studio Build Tools. + +To install: + + 1. Download Visual Studio Build Tools 2022: + https://visualstudio.microsoft.com/visual-cpp-build-tools/ + + 2. Run installer and select: + - "Desktop development with C++" workload + + 3. In "Individual Components", also select: + - "MSVC v143 - VS 2022 C++ x64/x86 Spectre-mitigated libs" + + 4. Restart your terminal and run: npm install + +================================================================================ +`; + +/** + * Run electron-rebuild + */ +function runElectronRebuild() { + return new Promise((resolve, reject) => { + const npx = isWindows ? 'npx.cmd' : 'npx'; + const child = spawn(npx, ['electron-rebuild'], { + stdio: 'inherit', + shell: isWindows, + cwd: path.join(__dirname, '..'), + }); + + child.on('close', (code) => { + if (code === 0) { + resolve({ success: true }); + } else { + reject(new Error(`electron-rebuild exited with code ${code}`)); + } + }); + + child.on('error', reject); + }); +} + +/** + * Check if node-pty is already built + */ +function isNodePtyBuilt() { + const buildDir = path.join(__dirname, '..', 'node_modules', 'node-pty', 'build', 'Release'); + if (!fs.existsSync(buildDir)) return false; + + // Check for the main .node file + const files = fs.readdirSync(buildDir); + return files.some((f) => f.endsWith('.node')); +} + +/** + * Main postinstall logic + */ +async function main() { + console.log('[postinstall] Setting up native modules for Electron...\n'); + + // If node-pty is already built (e.g., from a previous successful install), skip + if (isNodePtyBuilt()) { + console.log('[postinstall] Native modules already built, skipping rebuild.'); + return; + } + + if (isWindows) { + // On Windows, try prebuilds first + console.log('[postinstall] Windows detected - checking for prebuilt binaries...\n'); + + try { + // Dynamic import to handle case where the script doesn't exist yet + const { downloadPrebuilds } = require('./download-prebuilds.js'); + const result = await downloadPrebuilds(); + + if (result.success) { + console.log('\n[postinstall] Successfully installed prebuilt binaries!'); + console.log('[postinstall] No Visual Studio Build Tools required.\n'); + return; + } + + console.log(`\n[postinstall] Prebuilds not available (${result.reason})`); + console.log('[postinstall] Falling back to electron-rebuild...\n'); + } catch (err) { + console.log('[postinstall] Could not check for prebuilds:', err.message); + console.log('[postinstall] Falling back to electron-rebuild...\n'); + } + } + + // Run electron-rebuild + try { + console.log('[postinstall] Running electron-rebuild...\n'); + await runElectronRebuild(); + console.log('\n[postinstall] Native modules built successfully!'); + } catch (error) { + console.error('\n[postinstall] Failed to build native modules.\n'); + + if (isWindows) { + console.error(WINDOWS_BUILD_TOOLS_HELP); + } else { + console.error('Error:', error.message); + console.error('\nYou may need to install build tools for your platform:'); + console.error(' macOS: xcode-select --install'); + console.error(' Linux: sudo apt-get install build-essential\n'); + } + + process.exit(1); + } +} + +main().catch((err) => { + console.error('[postinstall] Unexpected error:', err); + process.exit(1); +});