fix(electron): make the packaged Windows build pass the #7592 cold-restart smoke (#11443)

Validated in a combined 3-PR batch worktree off release/v3.8.51 tip.
- Focused test: electron-smoke-script.test.ts — part of batch's 165/165 node:test run
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity, check:docs-counts-sync — all OK
- Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff

Real hardware validation on a fresh packaged Windows build (Hard Rule #18) surfacing and fixing 5 genuine defects (GNU-tar drive-letter parsing, missing asar file, electron-builder's node_modules extraResources drop, USERPROFILE-derived userData path, unreachable driver-log assertion) is exactly the kind of investigation this repo needs more of. Thank you.
This commit is contained in:
Webman
2026-08-25 16:54:20 -05:00
committed by GitHub
parent 700819a29b
commit 9862f129e2
7 changed files with 140 additions and 12 deletions

View File

@@ -360,7 +360,12 @@ function isInsideDir(parentDir, candidateDir) {
return candidate === parent || candidate.startsWith(parent + sep);
}
async function ensureSmokeEnvDirs(smokeEnv, dataDir) {
export async function ensureSmokeEnvDirs(smokeEnv, dataDir, { currentPlatform = platform() } = {}) {
// The win32 branches below must key off the SMOKE TARGET's platform, not the
// host's: tests (and any future cross-platform dry-run) inject a win32-shaped
// smokeEnv while running on a Linux CI host, and branching on the host
// platform() there silently skipped the USERPROFILE/APPDATA userData tree —
// exactly what this function exists to pre-create (#7592).
const dirNames = [
"DATA_DIR",
"HOME",
@@ -383,11 +388,21 @@ async function ensureSmokeEnvDirs(smokeEnv, dataDir) {
// On Windows, Electron derives its userData from APPDATA/<productName>.
// requestSingleInstanceLock() runs synchronously at module load and
// fails silently if the directory doesn't exist yet — causing exit(0).
if (platform() === "win32" && smokeEnv.APPDATA) {
if (currentPlatform === "win32" && smokeEnv.APPDATA) {
for (const subdir of ["omniroute-desktop", "OmniRoute", "omniroute"]) {
dirs.push(join(smokeEnv.APPDATA, subdir));
}
}
// Electron resolves the Roaming profile from %USERPROFILE%\AppData\Roaming
// (USERPROFILE takes precedence over the APPDATA env var) and the path
// service throws — rather than creates — when that directory is missing,
// which makes requestSingleInstanceLock() return false and the app exit(0)
// before app.whenReady(). Pre-create the derived tree as well.
if (currentPlatform === "win32" && smokeEnv.USERPROFILE) {
for (const subdir of ["omniroute-desktop", "OmniRoute", "omniroute"]) {
dirs.push(join(smokeEnv.USERPROFILE, "AppData", "Roaming", subdir));
}
}
await Promise.all(dirs.map((dir) => mkdir(dir, { recursive: true })));
}
@@ -506,7 +521,14 @@ async function waitForReady({ logs, smokeUrl, timeoutMs, settleMs, exitState })
* by the single-launch path and the cold-restart (two-launch) path so both
* exercise identical spawn/readiness/shutdown behavior.
*/
async function launchAndCollectLogs({ appExecutable, smokeUrl, dataDir, timeoutMs, settleMs, streamLogs }) {
async function launchAndCollectLogs({
appExecutable,
smokeUrl,
dataDir,
timeoutMs,
settleMs,
streamLogs,
}) {
const smokeEnv = buildSmokeEnv({ dataDir });
await assertPortIsFree(smokeUrl);
await ensureSmokeEnvDirs(smokeEnv, dataDir);
@@ -568,7 +590,14 @@ async function main() {
!process.env.ELECTRON_SMOKE_DATA_DIR && process.env.ELECTRON_SMOKE_KEEP_DATA !== "1";
try {
await launchAndCollectLogs({ appExecutable, smokeUrl, dataDir, timeoutMs, settleMs, streamLogs });
await launchAndCollectLogs({
appExecutable,
smokeUrl,
dataDir,
timeoutMs,
settleMs,
streamLogs,
});
if (!coldRestart) return;