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.
54 lines
2.2 KiB
JavaScript
54 lines
2.2 KiB
JavaScript
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' }))
|
|
})
|