test(desktop): extract Windows hermes-resolution helpers for real unit tests

windows-hermes-resolution.test.ts regexed main.ts source text to check
three Windows resolution bugs that caused desktop reinstall loops:
1. findOnPath()'s PATHEXT extension order (must try real extensions before
   the empty one, or an extensionless Git-Bash hermes shim shadows
   hermes.cmd/.exe).
2. handOffWindowsBootstrapRecovery()'s --update vs --repair choice (must
   gate on any real-install signal, not just the hermes.exe shim).
3. unwrapWindowsVenvHermesCommand()'s probe-before-trust behavior (must
   canImportHermesCli() before returning a venv python, or a broken venv
   gets re-selected forever).

Extract all three into pure, dependency-injected functions in
windows-hermes-path.ts (no Electron import): buildPathExtCandidates(),
chooseUpdaterArgs(), resolveVenvHermesCommand(). main.ts's
findOnPath/handOffWindowsBootstrapRecovery/unwrapWindowsVenvHermesCommand
now call these with their existing helpers (fileExists, canImportHermesCli,
getVenvPython, etc.) passed through as deps.

windows-hermes-path.test.ts replaces windows-hermes-resolution.test.ts,
importing the pure functions directly and asserting real return values
with fake/injected dependencies (fake venvs, fake probes) -- no readFileSync,
no regex-on-source.
This commit is contained in:
ethernet 2026-07-08 10:11:49 -04:00
parent 4527943e91
commit ec2cb3ab47
5 changed files with 364 additions and 155 deletions

View file

@ -129,6 +129,7 @@ import { hiddenWindowsChildOptions } from './windows-child-options'
import { readWindowsUserEnvVar } from './windows-user-env'
import { isPackagedInstallPath as isPackagedInstallPathUnderRoots } from './workspace-cwd'
import { readWslWindowsClipboardImage } from './wsl-clipboard-image'
import { buildPathExtCandidates, chooseUpdaterArgs, resolveVenvHermesCommand } from './windows-hermes-path'
const USER_DATA_OVERRIDE = process.env.HERMES_DESKTOP_USER_DATA_DIR
@ -1415,9 +1416,7 @@ function findOnPath(command) {
// shell-script shim named `hermes` — must not shadow `hermes.cmd`/`hermes.exe`.
// The empty entry is kept LAST so callers that already include the extension
// (py.exe, pwsh.exe, powershell.exe) still resolve.
const extensions = IS_WINDOWS
? [...(process.env.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean), '']
: ['']
const extensions = buildPathExtCandidates(process.env.PATHEXT, IS_WINDOWS)
for (const entry of pathEntries) {
for (const extension of extensions) {
@ -1437,71 +1436,21 @@ function isCommandScript(command) {
}
function unwrapWindowsVenvHermesCommand(command, backendArgs) {
if (!IS_WINDOWS || !command || isCommandScript(command)) {
return null
}
const resolved = path.resolve(String(command))
if (!/^hermes(?:\.exe)?$/i.test(path.basename(resolved))) {
return null
}
const scriptsDir = path.dirname(resolved)
if (path.basename(scriptsDir).toLowerCase() !== 'scripts') {
return null
}
const venvRoot = path.dirname(scriptsDir)
const python = getVenvPython(venvRoot)
if (!fileExists(python)) {
return null
}
const root = path.dirname(venvRoot)
// Smoke-test the venv interpreter before trusting it. A venv whose update
// died mid-`pip install` still has python.exe + hermes.exe on disk, but the
// backend dies on its first import (e.g. ModuleNotFoundError: dotenv) before
// the gateway ever binds. Returning it here also BYPASSED the caller's
// `--version` probe, so Retry/"Repair install" re-resolved the same broken
// venv forever instead of falling through to the bootstrap installer.
// Mirror isActiveRuntimeUsable(): probe with the checkout on PYTHONPATH so a
// healthy source-tree venv passes.
if (
!canImportHermesCli(python, {
env: {
PYTHONPATH: [...(directoryExists(root) ? [root] : []), process.env.PYTHONPATH]
.filter(Boolean)
.join(path.delimiter)
}
})
) {
rememberLog(
`Ignoring venv Hermes at ${python}: runtime import probe failed (broken/partial venv); falling through to bootstrap.`
)
return null
}
return {
label: `existing Hermes Python at ${python}`,
command: python,
args: ['-m', 'hermes_cli.main', ...backendArgs],
bootstrap: false,
env: buildDesktopBackendEnv({
hermesHome: HERMES_HOME,
pythonPathEntries: [...(directoryExists(root) ? [root] : []), ...getVenvSitePackagesEntries(venvRoot)],
venvRoot
}),
kind: 'python',
// Surfaced so backendSupportsServe() can read this runtime's source for the
// `serve` capability check instead of falling back to a heavyweight probe.
root,
shell: false
}
return resolveVenvHermesCommand(command, backendArgs, {
isWindows: IS_WINDOWS,
isCommandScript,
fileExists,
directoryExists,
canImportHermesCli,
getVenvPython,
getVenvSitePackagesEntries,
buildDesktopBackendEnv,
hermesHome: HERMES_HOME,
resolvePath: (...segments) => path.resolve(...segments),
dirname: p => path.dirname(p),
basename: p => path.basename(p),
rememberLog
})
}
// Does the resolved runtime understand the `serve` subcommand? The desktop
@ -2656,7 +2605,7 @@ async function handOffWindowsBootstrapRecovery(reason) {
const haveRealInstall =
fileExists(venvPython) || fileExists(venvHermes) || fileExists(path.join(updateRoot, '.hermes-bootstrap-complete'))
const updaterArgs = haveRealInstall ? ['--update', '--branch', branch] : ['--repair', '--branch', branch]
const updaterArgs = chooseUpdaterArgs(haveRealInstall, branch)
await releaseBackendLockForUpdate(updateRoot)

View file

@ -0,0 +1,139 @@
// Unit tests for the pure Windows `hermes` resolution helpers extracted from
// main.ts's findOnPath(), handOffWindowsBootstrapRecovery(), and
// unwrapWindowsVenvHermesCommand(). These pin the two Windows resolution bugs
// that caused desktop reinstall loops:
// 1. buildPathExtCandidates() — PATHEXT extensions must be tried BEFORE the
// empty extension, or an extensionless Git-Bash `hermes` shim shadows
// the real hermes.cmd/hermes.exe.
// 2. chooseUpdaterArgs() — must gate on haveRealInstall (any real-install
// signal), not just the hermes.exe console-script shim, or healthy
// installs get forced into a destructive --repair.
// 3. resolveVenvHermesCommand() — must probe the venv python via
// canImportHermesCli() before trusting it, or a broken venv gets
// re-selected forever instead of falling through to bootstrap.
import assert from 'node:assert/strict'
import test from 'node:test'
import { buildPathExtCandidates, chooseUpdaterArgs, resolveVenvHermesCommand } from './windows-hermes-path'
test('buildPathExtCandidates: Windows tries PATHEXT extensions before the empty extension', () => {
const extensions = buildPathExtCandidates('.COM;.EXE;.BAT;.CMD', true)
assert.deepEqual(extensions, ['.COM', '.EXE', '.BAT', '.CMD', ''])
assert.equal(extensions[extensions.length - 1], '', 'empty extension must be last, not first')
assert.notEqual(extensions[0], '', 'the buggy empty-extension-first order must not return')
})
test('buildPathExtCandidates: defaults to .COM;.EXE;.BAT;.CMD when PATHEXT is unset on Windows', () => {
assert.deepEqual(buildPathExtCandidates(undefined, true), ['.COM', '.EXE', '.BAT', '.CMD', ''])
})
test('buildPathExtCandidates: respects a custom PATHEXT, still empty-last', () => {
assert.deepEqual(buildPathExtCandidates('.EXE;.PS1', true), ['.EXE', '.PS1', ''])
})
test('buildPathExtCandidates: non-Windows only tries the bare name', () => {
assert.deepEqual(buildPathExtCandidates('.COM;.EXE;.BAT;.CMD', false), [''])
assert.deepEqual(buildPathExtCandidates(undefined, false), [''])
})
test('chooseUpdaterArgs: gentle --update when a real-install signal is present', () => {
assert.deepEqual(chooseUpdaterArgs(true, 'main'), ['--update', '--branch', 'main'])
})
test('chooseUpdaterArgs: destructive --repair only when NO real-install signal is present', () => {
assert.deepEqual(chooseUpdaterArgs(false, 'main'), ['--repair', '--branch', 'main'])
})
test('chooseUpdaterArgs: passes the branch through unchanged in both cases', () => {
assert.deepEqual(chooseUpdaterArgs(true, 'release/1.2'), ['--update', '--branch', 'release/1.2'])
assert.deepEqual(chooseUpdaterArgs(false, 'release/1.2'), ['--repair', '--branch', 'release/1.2'])
})
function makeDeps(overrides: Partial<Parameters<typeof resolveVenvHermesCommand>[2]> = {}) {
return {
isWindows: true,
isCommandScript: () => false,
fileExists: () => true,
directoryExists: () => false,
canImportHermesCli: () => true,
getVenvPython: (venvRoot: string) => `${venvRoot}/Scripts/python.exe`,
getVenvSitePackagesEntries: () => [],
buildDesktopBackendEnv: () => ({ FAKE_ENV: '1' }),
hermesHome: '/fake/hermes-home',
resolvePath: (...segments: string[]) => segments.join('/').replace(/\/+/g, '/'),
dirname: (p: string) => p.slice(0, p.lastIndexOf('/')) || '/',
basename: (p: string) => p.slice(p.lastIndexOf('/') + 1),
rememberLog: () => {},
...overrides
}
}
test('resolveVenvHermesCommand: returns null off Windows', () => {
const deps = makeDeps({ isWindows: false })
assert.equal(resolveVenvHermesCommand('/root/venv/Scripts/hermes.exe', [], deps), null)
})
test('resolveVenvHermesCommand: returns null for a .cmd/.bat script command', () => {
const deps = makeDeps({ isCommandScript: () => true })
assert.equal(resolveVenvHermesCommand('/root/venv/Scripts/hermes.cmd', [], deps), null)
})
test('resolveVenvHermesCommand: returns null when the basename is not hermes/hermes.exe', () => {
const deps = makeDeps()
assert.equal(resolveVenvHermesCommand('/root/venv/Scripts/python.exe', [], deps), null)
})
test('resolveVenvHermesCommand: returns null when the parent dir is not Scripts', () => {
const deps = makeDeps()
assert.equal(resolveVenvHermesCommand('/root/venv/bin/hermes.exe', [], deps), null)
})
test('resolveVenvHermesCommand: returns null when the venv python does not exist on disk', () => {
const deps = makeDeps({ fileExists: () => false })
assert.equal(resolveVenvHermesCommand('/root/venv/Scripts/hermes.exe', [], deps), null)
})
test('resolveVenvHermesCommand: probes the venv python before trusting it (returns null on failed probe)', () => {
let probed = false
const deps = makeDeps({
canImportHermesCli: (python: string) => {
probed = true
assert.equal(python, '/root/venv/Scripts/python.exe')
return false
}
})
const result = resolveVenvHermesCommand('/root/venv/Scripts/hermes.exe', ['serve'], deps)
assert.equal(probed, true, 'must probe the venv interpreter; a broken venv must not be re-selected forever')
assert.equal(result, null, 'a failed probe must fall through (return null) so the resolver reaches bootstrap')
})
test('resolveVenvHermesCommand: returns the resolved python backend descriptor when the probe passes', () => {
const deps = makeDeps()
const result = resolveVenvHermesCommand('/root/venv/Scripts/hermes.exe', ['serve', '--port', '0'], deps)
assert.ok(result, 'a passing probe must return a backend descriptor, not null')
assert.equal(result.command, '/root/venv/Scripts/python.exe')
assert.deepEqual(result.args, ['-m', 'hermes_cli.main', 'serve', '--port', '0'])
assert.equal(result.bootstrap, false)
assert.equal(result.kind, 'python')
assert.equal(result.shell, false)
assert.deepEqual(result.env, { FAKE_ENV: '1' })
})
test('resolveVenvHermesCommand: is case-insensitive on hermes.exe and the Scripts dir name', () => {
const deps = makeDeps()
assert.ok(resolveVenvHermesCommand('/root/venv/Scripts/HERMES.EXE', [], deps))
assert.ok(resolveVenvHermesCommand('/root/venv/SCRIPTS/hermes.exe', [], deps))
})

View file

@ -0,0 +1,206 @@
/**
* windows-hermes-path.ts
*
* Pure, dependency-injected pieces of Windows `hermes` resolution pulled out
* of main.ts's findOnPath(), handOffWindowsBootstrapRecovery(), and
* unwrapWindowsVenvHermesCommand(). Each of the three functions here pins one
* of the Windows resolution bugs that caused desktop reinstall loops:
*
* 1. buildPathExtCandidates() findOnPath() tried the empty extension
* FIRST, so an extensionless Git-Bash `hermes` shim shadowed the real
* hermes.cmd/hermes.exe; the shim then failed the --version probe and
* the desktop fell through to a spurious bootstrap/repair. The fix:
* PATHEXT extensions first, empty extension LAST.
* 2. chooseUpdaterArgs() handOffWindowsBootstrapRecovery() chose
* --update vs the destructive --repair by checking ONLY
* venv\Scripts\hermes.exe (the console-script shim, written at the END
* of venv setup and absent in interrupted states), so it escalated to a
* full venv recreate even on healthy installs. The fix: gate on ANY
* real-install signal, not just the shim.
* 3. resolveVenvHermesCommand() unwrapWindowsVenvHermesCommand() returned
* the venv python with NO runtime probe (bypassing the caller's
* --version check too), so a venv broken mid-update (e.g. missing
* python-dotenv) was re-selected forever: Retry / "Repair install"
* resolved the same dead interpreter instead of falling through to the
* bootstrap installer. The fix: probe-before-trust.
*
* Kept in a standalone ts module (no Electron imports, dependencies passed
* as parameters) so it can be unit-tested with `node --test` without
* mocking Electron or the filesystem, same pattern as backend-probes.ts and
* backend-command.ts.
*/
import path from 'node:path'
/**
* Build the ordered list of extensions findOnPath() should try when
* resolving a bare command name off PATH.
*
* On Windows this MUST try PATHEXT extensions (.COM;.EXE;.BAT;.CMD by
* default) BEFORE the bare/empty-extension name: a real command resolves via
* its .exe/.cmd per Windows command-resolution semantics, and an
* extensionless file (e.g. a Git-Bash shell-script shim named `hermes`) must
* not shadow `hermes.cmd`/`hermes.exe`. The empty entry is kept LAST so
* callers that already include the extension (py.exe, pwsh.exe,
* powershell.exe) still resolve.
*
* On non-Windows platforms there is no PATHEXT concept: only the bare name
* is tried.
*
* @param {string | undefined} pathext - process.env.PATHEXT (or undefined).
* @param {boolean} isWindows
* @returns {string[]} extensions to try, in order, always ending in ''.
*/
export function buildPathExtCandidates(pathext: string | undefined, isWindows: boolean): string[] {
if (!isWindows) {
return ['']
}
return [...(pathext || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean), '']
}
/**
* Choose the Windows bootstrap-recovery updater invocation: the gentle
* in-place --update when ANY real-install signal is present, the
* destructive --repair (full venv recreate) otherwise.
*
* haveRealInstall must be computed by the caller from ALL real-install
* signals (venv python interpreter, venv hermes shim, bootstrap-complete
* marker) gating on just the hermes.exe console-script shim alone is the
* regression this function's callers must avoid: that shim is written at
* the END of venv setup and is absent in exactly the interrupted/quarantined
* states this recovery exists to heal.
*
* @param {boolean} haveRealInstall
* @param {string} branch
* @returns {string[]} updater argv, e.g. ['--update', '--branch', 'main'].
*/
export function chooseUpdaterArgs(haveRealInstall: boolean, branch: string): string[] {
return haveRealInstall ? ['--update', '--branch', branch] : ['--repair', '--branch', branch]
}
export interface ResolveVenvHermesCommandDeps {
isWindows: boolean
isCommandScript: (command: string) => boolean
fileExists: (filePath: string) => boolean
directoryExists: (filePath: string) => boolean
canImportHermesCli: (python: string, opts?: { env?: Record<string, string> }) => boolean
getVenvPython: (venvRoot: string) => string
getVenvSitePackagesEntries: (venvRoot: string) => string[]
buildDesktopBackendEnv: (opts: {
hermesHome: string
pythonPathEntries: string[]
venvRoot: string
}) => Record<string, string>
hermesHome: string
resolvePath: (...segments: string[]) => string
dirname: (p: string) => string
basename: (p: string) => string
rememberLog?: (message: string) => void
}
/**
* If `command` is a Windows venv `hermes`/`hermes.exe` console-script shim
* (i.e. `<venvRoot>/Scripts/hermes(.exe)`), resolve it to the underlying
* venv python invoked as `python -m hermes_cli.main <backendArgs>` but
* ONLY after smoke-testing that interpreter with canImportHermesCli(). A
* venv whose update died mid-`pip install` still has python.exe + hermes.exe
* on disk, but the backend dies on its first import (e.g.
* ModuleNotFoundError: dotenv) before the gateway ever binds. Returning it
* unprobed also bypasses the caller's `--version` probe, so Retry/"Repair
* install" re-resolves the same broken venv forever instead of falling
* through to the bootstrap installer.
*
* Mirrors isActiveRuntimeUsable(): probes with the checkout on PYTHONPATH so
* a healthy source-tree venv passes.
*
* Returns null when `command` is not a venv hermes shim, the underlying
* python doesn't exist, or the import probe fails. Otherwise returns the
* resolved backend descriptor.
*/
export function resolveVenvHermesCommand(
command: string,
backendArgs: string[],
deps: ResolveVenvHermesCommandDeps
): {
label: string
command: string
args: string[]
bootstrap: false
env: Record<string, string>
kind: 'python'
root: string
shell: false
} | null {
const {
isWindows,
isCommandScript,
fileExists,
directoryExists,
canImportHermesCli,
getVenvPython,
getVenvSitePackagesEntries,
buildDesktopBackendEnv,
hermesHome,
resolvePath,
dirname,
basename,
rememberLog
} = deps
if (!isWindows || !command || isCommandScript(command)) {
return null
}
const resolved = resolvePath(String(command))
if (!/^hermes(?:\.exe)?$/i.test(basename(resolved))) {
return null
}
const scriptsDir = dirname(resolved)
if (basename(scriptsDir).toLowerCase() !== 'scripts') {
return null
}
const venvRoot = dirname(scriptsDir)
const python = getVenvPython(venvRoot)
if (!fileExists(python)) {
return null
}
const root = dirname(venvRoot)
if (
!canImportHermesCli(python, {
env: {
PYTHONPATH: [...(directoryExists(root) ? [root] : []), process.env.PYTHONPATH]
.filter((entry): entry is string => Boolean(entry))
.join(path.delimiter)
}
})
) {
rememberLog?.(
`Ignoring venv Hermes at ${python}: runtime import probe failed (broken/partial venv); falling through to bootstrap.`
)
return null
}
return {
label: `existing Hermes Python at ${python}`,
command: python,
args: ['-m', 'hermes_cli.main', ...backendArgs],
bootstrap: false,
env: buildDesktopBackendEnv({
hermesHome,
pythonPathEntries: [...(directoryExists(root) ? [root] : []), ...getVenvSitePackagesEntries(venvRoot)],
venvRoot
}),
kind: 'python',
root,
shell: false
}
}

View file

@ -1,85 +0,0 @@
// Regression guards for Windows `hermes` resolution in main.ts.
//
// main.ts has no module.exports, so these follow the repo's source-assertion
// test pattern (see windows-child-process.test.ts). They pin the two Windows
// resolution bugs that caused desktop reinstall loops:
// 1. findOnPath() tried the empty extension FIRST, so an extensionless
// Git-Bash `hermes` shim shadowed the real hermes.cmd/hermes.exe; the
// shim then failed the --version probe and the desktop fell through to a
// spurious bootstrap/repair.
// 2. handOffWindowsBootstrapRecovery() chose --update vs the destructive
// --repair by checking ONLY venv\Scripts\hermes.exe (the console-script
// shim, written at the END of venv setup and absent in interrupted
// states), so it escalated to a full venv recreate even on healthy
// installs.
// 3. unwrapWindowsVenvHermesCommand() returned the venv python with NO
// runtime probe (bypassing the caller's --version check too), so a venv
// broken mid-update (e.g. missing python-dotenv) was re-selected forever:
// Retry / "Repair install" resolved the same dead interpreter instead of
// falling through to the bootstrap installer.
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
function readMain() {
return fs.readFileSync(path.join(__dirname, 'main.ts'), 'utf8').replace(/\r\n/g, '\n')
}
test('findOnPath tries PATHEXT extensions before the bare (empty) name on Windows', () => {
const source = readMain()
// Fixed order: PATHEXT first, empty string LAST.
assert.match(
source,
/\(process\.env\.PATHEXT \|\| '\.COM;\.EXE;\.BAT;\.CMD'\)\.split\(';'\)\.filter\(Boolean\), ''\]/,
'extensions array must end with the empty string, not start with it'
)
// The buggy empty-first order must not return.
assert.doesNotMatch(
source,
/\['', \.\.\.\(process\.env\.PATHEXT/,
'empty-extension-first order regressed: an extensionless shim can shadow hermes.cmd/.exe'
)
})
test('Windows bootstrap recovery chooses --update when any real-install signal is present', () => {
const source = readMain()
assert.match(source, /const haveRealInstall =/, 'recovery must compute haveRealInstall')
assert.match(source, /fileExists\(venvPython\)/, 'recovery must accept the venv interpreter as a real-install signal')
assert.match(
source,
/\.hermes-bootstrap-complete/,
'recovery must accept the bootstrap-complete marker as a real-install signal'
)
assert.match(source, /updaterArgs = haveRealInstall \? \['--update'/, 'updaterArgs must gate on haveRealInstall')
// The old too-narrow check (only venv\Scripts\hermes.exe) must not return.
assert.doesNotMatch(
source,
/updaterArgs = fileExists\(venvHermes\) \?/,
'recovery regressed to gating only on the hermes.exe shim, which forces destructive --repair'
)
})
test('unwrapWindowsVenvHermesCommand smoke-tests the venv python before trusting it', () => {
const source = readMain()
const fnStart = source.indexOf('function unwrapWindowsVenvHermesCommand(')
assert.notEqual(fnStart, -1, 'unwrapWindowsVenvHermesCommand must exist in main.ts')
// Slice out just the function body (up to the next top-level function decl)
const fnEnd = source.indexOf('\nfunction ', fnStart + 1)
const body = source.slice(fnStart, fnEnd === -1 ? undefined : fnEnd)
assert.match(
body,
/canImportHermesCli\(python/,
'unwrap must probe the venv interpreter; returning it unprobed re-selects a broken venv ' +
'forever (Retry/Repair loop on a mid-update venv missing e.g. python-dotenv)'
)
assert.match(
body,
/return null\s*\n\s*\}\s*\n\s*return \{/,
'a failed probe must fall through (return null) so the resolver reaches the bootstrap rung'
)
})

View file

@ -38,7 +38,7 @@
"test:desktop:nsis": "node scripts/test-desktop.mjs nsis",
"test:desktop:existing": "node scripts/test-desktop.mjs existing",
"test:desktop:fresh": "node scripts/test-desktop.mjs fresh",
"test:desktop:platforms": "node --test electron/bootstrap-platform.test.ts electron/hardening.test.ts electron/backend-env.test.ts electron/backend-probes.test.ts electron/backend-ready.test.ts electron/bootstrap-runner.test.ts electron/connection-config.test.ts electron/dashboard-token.test.ts electron/gateway-ws-probe.test.ts electron/oauth-net-request.test.ts electron/desktop-uninstall.test.ts electron/session-windows.test.ts electron/link-title-window.test.ts electron/workspace-cwd.test.ts electron/fs-read-dir.test.ts electron/git-root.test.ts electron/git-worktree-ops.test.ts electron/windows-child-options.test.ts electron/update-remote.test.ts electron/update-count.test.ts electron/update-rebuild.test.ts electron/update-marker.test.ts electron/update-relaunch.test.ts electron/windows-user-env.test.ts electron/wsl-clipboard-image.test.ts electron/titlebar-overlay-width.test.ts electron/window-state.test.ts electron/zoom.test.ts electron/windows-hermes-resolution.test.ts electron/oauth-session-request.test.ts",
"test:desktop:platforms": "node --test electron/bootstrap-platform.test.ts electron/hardening.test.ts electron/backend-env.test.ts electron/backend-probes.test.ts electron/backend-ready.test.ts electron/bootstrap-runner.test.ts electron/connection-config.test.ts electron/dashboard-token.test.ts electron/gateway-ws-probe.test.ts electron/oauth-net-request.test.ts electron/desktop-uninstall.test.ts electron/session-windows.test.ts electron/link-title-window.test.ts electron/workspace-cwd.test.ts electron/fs-read-dir.test.ts electron/git-root.test.ts electron/git-worktree-ops.test.ts electron/windows-child-options.test.ts electron/update-remote.test.ts electron/update-count.test.ts electron/update-rebuild.test.ts electron/update-marker.test.ts electron/update-relaunch.test.ts electron/windows-user-env.test.ts electron/wsl-clipboard-image.test.ts electron/titlebar-overlay-width.test.ts electron/window-state.test.ts electron/zoom.test.ts electron/windows-hermes-path.test.ts electron/oauth-session-request.test.ts",
"typecheck": "tsc -p . --noEmit && tsc -p tsconfig.electron.json --noEmit",
"lint": "eslint src/ electron/",
"lint:fix": "eslint src/ electron/ --fix",