Fix Electron binary repair in release CI
Use the stricter Electron package binary installer when native rebuild needs to repair a missing Electron executable, avoiding partial dist/path.txt installs in release CI.
This commit is contained in:
parent
7e1cde4673
commit
a2a3c348fa
|
|
@ -137,19 +137,12 @@ function ensureElectronPackageInstalled() {
|
|||
}
|
||||
|
||||
// Why: CI has observed Electron's postinstall exiting cleanly without
|
||||
// writing path.txt; native rebuild and tests both need the binary path.
|
||||
console.log('[rebuild] Electron package binary is missing; rerunning Electron install.')
|
||||
// writing path.txt; use our strict installer instead of Electron's install.js
|
||||
// so partial extracts and poisoned caches are rejected before native rebuild.
|
||||
console.log('[rebuild] Electron package binary is missing; installing Electron package binary.')
|
||||
resetPartialElectronInstall()
|
||||
try {
|
||||
execFileSync(process.execPath, [require.resolve('electron/install.js')], {
|
||||
cwd: projectDir,
|
||||
env: {
|
||||
...process.env,
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: '',
|
||||
force_no_cache: 'true'
|
||||
},
|
||||
stdio: 'inherit'
|
||||
})
|
||||
runElectronPackageBinaryInstall()
|
||||
} catch (/** @type {any} */ err) {
|
||||
console.error('[rebuild] Electron install retry failed:', err?.message ?? err)
|
||||
logElectronInstallDiagnostics()
|
||||
|
|
@ -185,6 +178,31 @@ function ensureElectronPackageInstalled() {
|
|||
}
|
||||
}
|
||||
|
||||
function runElectronPackageBinaryInstall() {
|
||||
const env = { ...process.env }
|
||||
delete env.ELECTRON_SKIP_BINARY_DOWNLOAD
|
||||
delete env.npm_config_electron_skip_binary_download
|
||||
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
['config/scripts/install-electron-package-binary.mjs'],
|
||||
{
|
||||
cwd: projectDir,
|
||||
env,
|
||||
stdio: 'inherit'
|
||||
}
|
||||
)
|
||||
|
||||
if (result.error) {
|
||||
throw result.error
|
||||
}
|
||||
if (result.status !== 0) {
|
||||
throw new Error(
|
||||
`config/scripts/install-electron-package-binary.mjs exited with status ${result.status}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function resetPartialElectronInstall() {
|
||||
const electronPackageDir = resolve(projectDir, 'node_modules/electron')
|
||||
// Why: Electron's installer can leave a partial dist/ tree behind after
|
||||
|
|
|
|||
|
|
@ -1,18 +1,31 @@
|
|||
import { spawnSync } from 'node:child_process'
|
||||
import { chmodSync, copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import {
|
||||
chmodSync,
|
||||
copyFileSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
writeFileSync
|
||||
} from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const sourceScriptPath = fileURLToPath(new URL('./rebuild-native-deps.mjs', import.meta.url))
|
||||
const sourceInstallScriptPath = fileURLToPath(
|
||||
new URL('./install-electron-package-binary.mjs', import.meta.url)
|
||||
)
|
||||
|
||||
describe('rebuild-native-deps Electron install fallback', () => {
|
||||
it('continues non-strict postinstall when Electron retry download fails', () => {
|
||||
const projectDir = mkTempProject()
|
||||
|
||||
try {
|
||||
writeFakeElectronPackage(projectDir, { installExitsWith: 1 })
|
||||
writeFakeElectronPackage(projectDir)
|
||||
writeFakeElectronGet(projectDir, { downloadRejects: true })
|
||||
writeFakeExtractZip(projectDir, { createExecutable: false })
|
||||
writeFakeElectronRebuild(projectDir)
|
||||
|
||||
const result = runRebuildScript(projectDir, {
|
||||
|
|
@ -22,8 +35,12 @@ describe('rebuild-native-deps Electron install fallback', () => {
|
|||
|
||||
expect(result.status, result.stderr).toBe(0)
|
||||
expect(result.stderr).toContain('Electron install retry failed')
|
||||
expect(result.stderr).toContain('Continuing postinstall because Electron binary installation failed')
|
||||
expect(readFileSync(join(projectDir, 'electron-install.log'), 'utf8')).toBe('install attempted\n')
|
||||
expect(result.stderr).toContain(
|
||||
'Continuing postinstall because Electron binary installation failed'
|
||||
)
|
||||
expect(readFileSync(join(projectDir, 'electron-get.log'), 'utf8')).toBe(
|
||||
'download attempted\n'
|
||||
)
|
||||
} finally {
|
||||
rmSync(projectDir, { recursive: true, force: true })
|
||||
}
|
||||
|
|
@ -33,7 +50,9 @@ describe('rebuild-native-deps Electron install fallback', () => {
|
|||
const projectDir = mkTempProject()
|
||||
|
||||
try {
|
||||
writeFakeElectronPackage(projectDir, { installExitsWith: 1 })
|
||||
writeFakeElectronPackage(projectDir)
|
||||
writeFakeElectronGet(projectDir, { downloadRejects: true })
|
||||
writeFakeExtractZip(projectDir, { createExecutable: false })
|
||||
writeFakeElectronRebuild(projectDir)
|
||||
|
||||
const result = runRebuildScript(projectDir, {
|
||||
|
|
@ -43,7 +62,9 @@ describe('rebuild-native-deps Electron install fallback', () => {
|
|||
|
||||
expect(result.status).toBe(1)
|
||||
expect(result.stderr).toContain('Electron install retry failed')
|
||||
expect(result.stderr).not.toContain('Continuing postinstall because Electron binary installation failed')
|
||||
expect(result.stderr).not.toContain(
|
||||
'Continuing postinstall because Electron binary installation failed'
|
||||
)
|
||||
} finally {
|
||||
rmSync(projectDir, { recursive: true, force: true })
|
||||
}
|
||||
|
|
@ -53,14 +74,18 @@ describe('rebuild-native-deps Electron install fallback', () => {
|
|||
const projectDir = mkTempProject()
|
||||
|
||||
try {
|
||||
writeFakeElectronPackage(projectDir, { installExitsWith: 1 })
|
||||
writeFakeElectronPackage(projectDir)
|
||||
writeFakeElectronGet(projectDir, { downloadRejects: true })
|
||||
writeFakeExtractZip(projectDir, { createExecutable: false })
|
||||
writeFakeElectronRebuild(projectDir)
|
||||
|
||||
const result = runRebuildScript(projectDir)
|
||||
|
||||
expect(result.status).toBe(1)
|
||||
expect(result.stderr).toContain('Electron install retry failed')
|
||||
expect(result.stderr).not.toContain('Continuing postinstall because Electron binary installation failed')
|
||||
expect(result.stderr).not.toContain(
|
||||
'Continuing postinstall because Electron binary installation failed'
|
||||
)
|
||||
} finally {
|
||||
rmSync(projectDir, { recursive: true, force: true })
|
||||
}
|
||||
|
|
@ -70,13 +95,17 @@ describe('rebuild-native-deps Electron install fallback', () => {
|
|||
const projectDir = mkTempProject()
|
||||
|
||||
try {
|
||||
writeFakeElectronPackage(projectDir, {
|
||||
installExitsWith: 1,
|
||||
logPartialStateBeforeInstall: true
|
||||
})
|
||||
writeFakeElectronPackage(projectDir)
|
||||
writeFakeElectronGet(projectDir, { logPartialStateBeforeInstall: true })
|
||||
writeFakeExtractZip(projectDir, { createExecutable: false })
|
||||
writeFakeElectronRebuild(projectDir)
|
||||
mkdirSync(join(projectDir, 'node_modules', 'electron', 'dist', 'locales'), { recursive: true })
|
||||
writeFileSync(join(projectDir, 'node_modules', 'electron', 'dist', 'locales', 'stale.pak'), '')
|
||||
mkdirSync(join(projectDir, 'node_modules', 'electron', 'dist', 'locales'), {
|
||||
recursive: true
|
||||
})
|
||||
writeFileSync(
|
||||
join(projectDir, 'node_modules', 'electron', 'dist', 'locales', 'stale.pak'),
|
||||
''
|
||||
)
|
||||
writeFileSync(join(projectDir, 'node_modules', 'electron', 'path.txt'), 'stale-path')
|
||||
|
||||
const result = runRebuildScript(projectDir, {
|
||||
|
|
@ -84,8 +113,8 @@ describe('rebuild-native-deps Electron install fallback', () => {
|
|||
})
|
||||
|
||||
expect(result.status).toBe(1)
|
||||
expect(readFileSync(join(projectDir, 'electron-install.log'), 'utf8')).toBe(
|
||||
'partial cleared\ninstall attempted\n'
|
||||
expect(readFileSync(join(projectDir, 'electron-get.log'), 'utf8')).toBe(
|
||||
'partial cleared\ndownload attempted\n'
|
||||
)
|
||||
} finally {
|
||||
rmSync(projectDir, { recursive: true, force: true })
|
||||
|
|
@ -97,6 +126,10 @@ function mkTempProject() {
|
|||
const projectDir = mkdtempSync(join(tmpdir(), 'orca-rebuild-native-deps-'))
|
||||
mkdirSync(join(projectDir, 'config', 'scripts'), { recursive: true })
|
||||
copyFileSync(sourceScriptPath, join(projectDir, 'config', 'scripts', 'rebuild-native-deps.mjs'))
|
||||
copyFileSync(
|
||||
sourceInstallScriptPath,
|
||||
join(projectDir, 'config', 'scripts', 'install-electron-package-binary.mjs')
|
||||
)
|
||||
return projectDir
|
||||
}
|
||||
|
||||
|
|
@ -106,39 +139,90 @@ function runRebuildScript(projectDir, extraEnv = {}) {
|
|||
encoding: 'utf8',
|
||||
env: {
|
||||
...process.env,
|
||||
npm_config_platform: 'linux',
|
||||
npm_config_arch: 'x64',
|
||||
...extraEnv
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function writeFakeElectronPackage(
|
||||
projectDir,
|
||||
{ installExitsWith, logPartialStateBeforeInstall = false }
|
||||
) {
|
||||
function writeFakeElectronPackage(projectDir) {
|
||||
const electronDir = join(projectDir, 'node_modules', 'electron')
|
||||
mkdirSync(electronDir, { recursive: true })
|
||||
writeFileSync(join(electronDir, 'package.json'), JSON.stringify({ version: '41.5.0' }))
|
||||
writeFileSync(
|
||||
join(electronDir, 'package.json'),
|
||||
JSON.stringify({ name: 'electron', version: '41.5.0' })
|
||||
)
|
||||
writeFileSync(join(electronDir, 'checksums.json'), '{}')
|
||||
writeFileSync(
|
||||
join(electronDir, 'index.js'),
|
||||
"throw new Error('Electron failed to install correctly, please delete node_modules/electron and try installing again')\n"
|
||||
)
|
||||
writeFileSync(
|
||||
join(electronDir, 'install.js'),
|
||||
`
|
||||
const { appendFileSync, existsSync } = require('node:fs')
|
||||
if (${JSON.stringify(logPartialStateBeforeInstall)}) {
|
||||
appendFileSync(
|
||||
'electron-install.log',
|
||||
existsSync('node_modules/electron/dist') || existsSync('node_modules/electron/path.txt')
|
||||
? 'partial still present\\n'
|
||||
: 'partial cleared\\n'
|
||||
)
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const pathFile = path.join(__dirname, 'path.txt')
|
||||
if (!fs.existsSync(pathFile)) {
|
||||
throw new Error('Electron failed to install correctly, please delete node_modules/electron and try installing again')
|
||||
}
|
||||
appendFileSync('electron-install.log', 'install attempted\\n')
|
||||
process.exit(${installExitsWith})
|
||||
const electronPath = path.join(__dirname, 'dist', fs.readFileSync(pathFile, 'utf8'))
|
||||
if (!fs.existsSync(electronPath)) {
|
||||
throw new Error('Electron failed to install correctly, please delete node_modules/electron and try installing again')
|
||||
}
|
||||
module.exports = electronPath
|
||||
`
|
||||
)
|
||||
chmodSync(join(electronDir, 'install.js'), 0o755)
|
||||
}
|
||||
|
||||
function writeFakeElectronGet(
|
||||
projectDir,
|
||||
{ downloadRejects = false, logPartialStateBeforeInstall = false } = {}
|
||||
) {
|
||||
const getDir = join(projectDir, 'node_modules', 'electron', 'node_modules', '@electron', 'get')
|
||||
mkdirSync(getDir, { recursive: true })
|
||||
writeFileSync(
|
||||
join(getDir, 'index.js'),
|
||||
`
|
||||
const { appendFileSync, existsSync, mkdirSync, writeFileSync } = require('node:fs')
|
||||
const { join } = require('node:path')
|
||||
exports.downloadArtifact = async function downloadArtifact(details) {
|
||||
if (${JSON.stringify(logPartialStateBeforeInstall)}) {
|
||||
appendFileSync(
|
||||
'electron-get.log',
|
||||
existsSync('node_modules/electron/dist') || existsSync('node_modules/electron/path.txt')
|
||||
? 'partial still present\\n'
|
||||
: 'partial cleared\\n'
|
||||
)
|
||||
}
|
||||
appendFileSync('electron-get.log', 'download attempted\\n')
|
||||
if (${JSON.stringify(downloadRejects)}) {
|
||||
throw new Error('download failed')
|
||||
}
|
||||
mkdirSync(details.cacheRoot, { recursive: true })
|
||||
const artifactPath = join(details.cacheRoot, 'electron.zip')
|
||||
writeFileSync(artifactPath, 'fake zip')
|
||||
return artifactPath
|
||||
}
|
||||
`
|
||||
)
|
||||
}
|
||||
|
||||
function writeFakeExtractZip(projectDir, { createExecutable }) {
|
||||
const extractDir = join(projectDir, 'node_modules', 'electron', 'node_modules', 'extract-zip')
|
||||
mkdirSync(extractDir, { recursive: true })
|
||||
writeFileSync(
|
||||
join(extractDir, 'index.js'),
|
||||
`
|
||||
const { mkdirSync, writeFileSync } = require('node:fs')
|
||||
const { join } = require('node:path')
|
||||
module.exports = async function extract(_zipPath, options) {
|
||||
mkdirSync(join(options.dir, 'locales'), { recursive: true })
|
||||
if (${JSON.stringify(createExecutable)}) {
|
||||
writeFileSync(join(options.dir, 'electron'), '')
|
||||
writeFileSync(join(options.dir, 'version'), 'v41.5.0')
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
chmodSync(join(extractDir, 'index.js'), 0o755)
|
||||
}
|
||||
|
||||
function writeFakeElectronRebuild(projectDir) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue