Merge pull request #587 from aaddrick/claude/electron-get-durable-fix

fix(deps): fetch electron binary via @electron/get, drop ^41 pin
This commit is contained in:
Aaddrick
2026-05-14 06:33:31 -04:00
committed by GitHub
3 changed files with 123 additions and 6 deletions

View File

@@ -41,6 +41,17 @@ The build script automatically detects your distribution and selects the appropr
| Arch Linux | `.AppImage` (via AUR) | yay/paru |
| Other | `.AppImage` | - |
## Build Environment Variables
The build pulls the Electron prebuilt binary from `github.com/electron/electron/releases` via `@electron/get`. Two upstream environment variables let you redirect that fetch:
- `ELECTRON_MIRROR` — base URL to fetch Electron releases from instead of GitHub. Useful for mirrors or local proxies. Example: `ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/`.
- `ELECTRON_CUSTOM_DIR` — overrides the path segment after the mirror. Defaults to `v{version}`.
The cache location is fixed at `~/.cache/electron/` (resolved by `@electron/get` via `envPaths`) and is reused across builds. `ELECTRON_CACHE` is **not** read by `@electron/get` — set `ELECTRON_MIRROR` if you need to avoid the public CDN.
The pinned Electron version lives in `scripts/setup/dependencies.sh` (`electron_version`) and must match `build-reference/app-extracted/package.json` — the upstream Claude Desktop `app.asar` is built against a specific Electron major and running a different one is unsupported.
## Installing the Built Package
### For .deb packages (Debian/Ubuntu)

View File

@@ -198,6 +198,13 @@ setup_nodejs() {
setup_electron_asar() {
section_header 'Electron & Asar Handling'
# Pin Electron to the exact version upstream Claude Desktop ships
# (build-reference/app-extracted/package.json). The shipped app.asar
# binds to specific V8/NAPI ABI, Chromium pairing, and node-pty
# native surface — running a different Electron major against this
# asar is unsupported. Bump when upstream bumps.
local electron_version='41.5.0'
echo "Ensuring local Electron and Asar installation in $work_dir..."
cd "$work_dir" || exit 1
@@ -214,17 +221,34 @@ setup_electron_asar() {
[[ ! -f $asar_bin_path ]] && echo 'Asar binary not found.' && install_needed=true
if [[ $install_needed == true ]]; then
echo "Installing Electron and Asar locally into $work_dir..."
# Pin to electron 41.x: electron@42.0.0 (2026-05-06) dropped the
# postinstall that fetches the prebuilt binary into dist/, leaving
# node_modules/electron/dist absent and the build aborting (#584).
# A durable fix using @electron/get is tracked separately.
if ! npm install --no-save 'electron@^41' @electron/asar; then
echo "Installing electron@${electron_version} and Asar locally into $work_dir..."
if ! npm install --no-save \
"electron@${electron_version}" @electron/asar @electron/get extract-zip; then
echo 'Failed to install Electron and/or Asar locally.' >&2
cd "$project_root" || exit 1
exit 1
fi
echo 'Electron and Asar installation command finished.'
# electron@42+ no longer ships a postinstall script that fetches
# the prebuilt binary into dist/. If npm didn't populate it, fetch
# the matching binary explicitly via @electron/get. See #584.
# Retry once on transient CDN failures (503, network drops).
if [[ ! -d $electron_dist_path ]]; then
echo 'Electron postinstall did not populate dist/; fetching binary explicitly...'
local fetch_attempts=0
while ! node "$project_root/scripts/setup/fetch-electron-binary.js"; do
fetch_attempts=$((fetch_attempts + 1))
if (( fetch_attempts >= 2 )); then
echo 'Failed to fetch Electron binary via @electron/get after 2 attempts.' >&2
echo 'For air-gapped or mirrored builds set ELECTRON_MIRROR or ELECTRON_CUSTOM_DIR; see docs/BUILDING.md.' >&2
cd "$project_root" || exit 1
exit 1
fi
echo "Retrying Electron binary fetch (attempt $((fetch_attempts + 1))/2)..."
sleep 2
done
fi
else
echo 'Local Electron distribution and Asar binary already present.'
fi

View File

@@ -0,0 +1,82 @@
#!/usr/bin/env node
// Fetches the Electron prebuilt binary into node_modules/electron/dist/.
//
// electron@42.0.0 (2026-05-06) removed the postinstall script that
// historically populated dist/ during `npm install`. This helper restores
// that behavior using @electron/get + extract-zip, so the rest of the
// build pipeline (which depends on the dist/ layout) keeps working.
//
// Run from the directory containing node_modules/electron. Reads the
// installed electron version from its package.json and downloads the
// matching binary for the host platform/arch.
//
// See: https://github.com/aaddrick/claude-desktop-debian/issues/584
'use strict';
const fs = require('node:fs');
const path = require('node:path');
const { createRequire } = require('node:module');
async function main() {
const cwd = process.cwd();
const electronModuleDir = path.join(cwd, 'node_modules', 'electron');
const distDir = path.join(electronModuleDir, 'dist');
if (!fs.existsSync(electronModuleDir)) {
throw new Error(
`Electron module not found at ${electronModuleDir}; ` +
"run 'npm install electron' first.",
);
}
const pkgPath = path.join(electronModuleDir, 'package.json');
const { version } = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
if (!version) {
throw new Error(`Could not read version from ${pkgPath}`);
}
const platform = 'linux';
// node's process.arch values map cleanly to electron release archs,
// except 'arm' which electron publishes as 'armv7l'.
const arch = process.arch === 'arm' ? 'armv7l' : process.arch;
const supportedArchs = ['x64', 'arm64', 'armv7l', 'ia32'];
if (!supportedArchs.includes(arch)) {
throw new Error(
`Unsupported architecture: ${arch}. ` +
`Electron publishes Linux binaries for ${supportedArchs.join(', ')}.`,
);
}
// Resolve @electron/get and extract-zip from the work-dir's
// node_modules. The script lives at scripts/setup/ so a plain
// require() walks up from there and never sees work_dir/.
const workDirRequire = createRequire(path.join(cwd, 'package.json'));
const { downloadArtifact } = workDirRequire('@electron/get');
const extractZip = workDirRequire('extract-zip');
console.log(`Fetching electron@${version} for ${platform}-${arch}...`);
const zipPath = await downloadArtifact({
version,
platform,
arch,
artifactName: 'electron',
});
console.log(`Extracting ${zipPath} into ${distDir}`);
fs.mkdirSync(distDir, { recursive: true });
await extractZip(zipPath, { dir: distDir });
const electronBin = path.join(distDir, 'electron');
if (fs.existsSync(electronBin)) {
fs.chmodSync(electronBin, 0o755);
}
console.log('Electron binary fetched and extracted successfully.');
}
main().catch((err) => {
console.error(err && err.stack ? err.stack : err);
process.exit(1);
});