fix(desktop): recover from corrupt cached Electron download on build
hermes desktop failed on Linux with an ENOENT renaming release/linux-unpacked/electron -> Hermes. Root cause is a corrupt cached Electron zip (~/.cache/electron/electron-*.zip): app-builder unpack-electron extracts a partial tree from the bad zip that is missing the electron binary, so electron-builder dies on the final rename. Re-running repeats the broken extraction, leaving the desktop app permanently unlaunchable until the cache is manually purged. - Add _electron_download_cache_dirs() + _purge_corrupt_electron_cache() to hermes_cli/main.py: validate every electron-*.zip via zipfile.testzip() and delete corrupt ones; honor electron_config_cache / ELECTRON_CACHE overrides with per-OS defaults. - Wire purge + single retry into cmd_gui packaged-build failure path so a poisoned download self-heals (electron re-downloads clean). - Add beforePack hook (apps/desktop/scripts/before-pack.cjs) to wipe the target unpacked dir before staging, making packaging idempotent across interrupted runs. Cross-platform, best-effort. - Tests: corrupt-zip detector, cmd_gui purge/retry/launch path, no-retry-when-clean path, and node --test for the cleanup helper.
This commit is contained in:
parent
e003c53b06
commit
f583c6ebd5
5 changed files with 311 additions and 0 deletions
53
apps/desktop/scripts/before-pack.test.cjs
Normal file
53
apps/desktop/scripts/before-pack.test.cjs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const os = require('node:os')
|
||||
const path = require('node:path')
|
||||
const test = require('node:test')
|
||||
|
||||
const { cleanStaleAppOutDir } = require('../scripts/before-pack.cjs')
|
||||
|
||||
test('cleanStaleAppOutDir removes a populated unpacked directory', () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-before-pack-'))
|
||||
try {
|
||||
const appOutDir = path.join(tempRoot, 'linux-unpacked')
|
||||
fs.mkdirSync(appOutDir, { recursive: true })
|
||||
// Reproduce the corrupted partial state: license + payload present,
|
||||
// electron binary missing — exactly what trips the ENOENT rename.
|
||||
fs.writeFileSync(path.join(appOutDir, 'LICENSE.electron.txt'), 'x', 'utf8')
|
||||
fs.writeFileSync(path.join(appOutDir, 'resources.pak'), 'x', 'utf8')
|
||||
fs.mkdirSync(path.join(appOutDir, 'resources'), { recursive: true })
|
||||
fs.writeFileSync(path.join(appOutDir, 'resources', 'app.asar'), 'x', 'utf8')
|
||||
|
||||
const removed = cleanStaleAppOutDir(appOutDir)
|
||||
|
||||
assert.equal(removed, true)
|
||||
assert.equal(fs.existsSync(appOutDir), false)
|
||||
} finally {
|
||||
fs.rmSync(tempRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('cleanStaleAppOutDir is a no-op when the directory is absent', () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-before-pack-'))
|
||||
try {
|
||||
const missing = path.join(tempRoot, 'does-not-exist')
|
||||
assert.equal(cleanStaleAppOutDir(missing), false)
|
||||
} finally {
|
||||
fs.rmSync(tempRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('cleanStaleAppOutDir ignores empty or invalid input', () => {
|
||||
assert.equal(cleanStaleAppOutDir(''), false)
|
||||
assert.equal(cleanStaleAppOutDir(undefined), false)
|
||||
assert.equal(cleanStaleAppOutDir(null), false)
|
||||
assert.equal(cleanStaleAppOutDir(42), false)
|
||||
})
|
||||
|
||||
test('beforePack default export resolves even when cleanup throws', async () => {
|
||||
const { default: beforePack } = require('../scripts/before-pack.cjs')
|
||||
// A directory path that rmSync can't remove is simulated by passing a
|
||||
// context whose appOutDir is a file the hook will try (and be allowed) to
|
||||
// remove; the contract under test is that the hook never rejects.
|
||||
await assert.doesNotReject(beforePack({ appOutDir: '', electronPlatformName: 'linux' }))
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue