Files
orca/tests/tools/win-update-e2e/trace-installer-branches.mjs
T
OrcaWinandm4air 4b6fe95943 fix(windows): preserve relocated terminals and native process scans (#22872)
* fix(windows): ship the process-table addon to the relocated daemon host

The Windows terminal daemon runs from a copy of the app under
%LOCALAPPDATA%\Orca\daemon-host\<version>. That copy took node-pty but not
@vscode/windows-process-tree, so the daemon's bare require of the addon found
nothing and every process-table read (foreground tracking, descendant sweeps)
fell back to a powershell.exe Get-CimInstance scan (#16905).

- Copy the addon's runtime files (package.json, lib/, the .node binary) into
  the host; the ~25MB of gyp intermediates beside them are filtered out.
- Treat a host missing those files as unmaterialized, so hosts built before
  this are rebuilt, and skip relocation if the install itself lacks them.
- Log the daemon's native/CIM capability at startup and warn once when the
  process table falls back to CIM.

Revives #19525 on current main.

* test(windows): locate update-survival loss before relaunch

* test(windows): preserve daemon tree before update-survival proof

* test(windows): distinguish Electron exit from launcher close timeout

* test(windows): verify process exit when inherited pipes delay close

* test(windows): trace installer process checks in isolated survival runs

* fix(windows): probe process-query capability before installer sweep

* fix(windows): match installer probe and process-check profile behavior

* fix(windows): use NSIS separators for the process-check include

* test(windows): dismiss session-search overlay in survival harness

---------

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-09-26 14:31:07 -07:00

116 lines
3.6 KiB
JavaScript

import { readFileSync, writeFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const require = createRequire(import.meta.url)
const builderRequire = createRequire(require.resolve('electron-builder/package.json'))
const builderRoot = path.dirname(builderRequire.resolve('app-builder-lib/package.json'))
const processChecks = path.join(
builderRoot,
'templates/nsis/include/allowOnlyOneInstallerInstance.nsh'
)
const hooks = fileURLToPath(
new URL('../../../config/nsis/orca-installer-hooks.nsh', import.meta.url)
)
const capabilityCheck = fileURLToPath(
new URL('../../../config/nsis/orca-process-check.nsh', import.meta.url)
)
// CI-only patch: the installer and its embedded uninstaller share these macros.
const traceMacro = `
!macro ORCA_E2E_TRACE TEXT
Push $R8
Push $R9
StrCpy $R8 0
\${if} \${Errors}
StrCpy $R8 1
\${endIf}
ClearErrors
ReadEnvStr $R9 ORCA_E2E_NSIS_TRACE
\${if} $R9 != ""
FileOpen $R9 "$R9" a
\${ifNot} \${Errors}
FileSeek $R9 0 END
FileWrite $R9 "$EXEPATH | $CMDLINE | dir=$INSTDIR | \${TEXT}$\\r$\\n"
FileClose $R9
\${endIf}
\${endIf}
ClearErrors
\${if} $R8 == 1
SetErrors
\${endIf}
Pop $R9
Pop $R8
!macroend
`
function replaceOnce(source, anchor, replacement) {
if (source.split(anchor).length !== 2) {
throw new Error(`Expected one NSIS trace anchor: ${anchor}`)
}
return source.replace(anchor, () => replacement)
}
function insertAfter(source, anchor, insertion) {
return replaceOnce(source, anchor, `${anchor}\n${insertion}`)
}
let checks = readFileSync(processChecks, 'utf8').replaceAll('\r\n', '\n')
checks = traceMacro + checks
checks = insertAfter(
checks,
'!macro FIND_PROCESS _FILE _RETURN',
' !insertmacro ORCA_E2E_TRACE "find image=${_FILE} branch=$IsPowerShellAvailable"'
)
checks = insertAfter(
checks,
'!macro KILL_PROCESS _FILE _FORCE',
' !insertmacro ORCA_E2E_TRACE "kill image=${_FILE} force=${_FORCE} branch=$IsPowerShellAvailable"'
)
checks = replaceOnce(
checks,
' Pop $0\n!macroend ',
' Pop $0\n !insertmacro ORCA_E2E_TRACE "kill-result=$0"\n!macroend '
)
const findCall = '!insertmacro FIND_PROCESS "${APP_EXECUTABLE_FILENAME}" $R0'
if (checks.split(findCall).length !== 4) {
throw new Error('Expected three find-process calls')
}
checks = checks.replaceAll(
findCall,
() => `${findCall}\n !insertmacro ORCA_E2E_TRACE "find-result=$R0"`
)
let uninstall = readFileSync(hooks, 'utf8').replaceAll('\r\n', '\n')
uninstall = insertAfter(
uninstall,
'!macro customUnInstall',
' !insertmacro ORCA_E2E_TRACE "custom-uninstall entered"'
)
uninstall = insertAfter(
uninstall,
' ${ifNot} ${isUpdated}',
' !insertmacro ORCA_E2E_TRACE "genuine-uninstall daemon sweep"'
)
let capability = readFileSync(capabilityCheck, 'utf8').replaceAll('\r\n', '\n')
capability = insertAfter(
capability,
' Pop $0',
' !insertmacro ORCA_E2E_TRACE "capability=$0 powershell=$PowerShellPath"'
)
capability = replaceOnce(
capability,
' !insertmacro _CHECK_APP_RUNNING',
' !insertmacro ORCA_E2E_TRACE "selected-branch=$IsPowerShellAvailable (0=path,1=image)"\n !insertmacro _CHECK_APP_RUNNING'
)
if (!process.argv.includes('--check')) {
if (process.env.GITHUB_ACTIONS !== 'true' || process.platform !== 'win32') {
throw new Error('Installer tracing may only patch a disposable Windows CI checkout')
}
writeFileSync(processChecks, checks)
writeFileSync(hooks, uninstall)
writeFileSync(capabilityCheck, capability)
}
console.log('Validated installer/uninstaller branch-trace anchors')