feat: auto-download prebuilt node-pty binaries for Windows
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
@@ -114,6 +114,18 @@ pnpm run build && pnpm run start
|
||||
# or: npm run build && npm run start
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary><b>Windows users:</b> If installation fails with node-gyp errors, click here</summary>
|
||||
|
||||
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
|
||||
|
||||
</details>
|
||||
|
||||
### Step 4: Start Building
|
||||
|
||||
1. Add your project in the UI
|
||||
|
||||
@@ -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 .",
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user