From 359f0d3e7b21f39e145b4fb3bd80ae468793dbff Mon Sep 17 00:00:00 2001 From: ethernet Date: Thu, 24 Sep 2026 07:53:51 -0400 Subject: [PATCH] test: preserve historical venv Desktop branch probing without PM launcher --- tests-js/update-ui.test.mjs | 42 +++++++++++++++++++ .../e2e-assets/source-branch-probe.cjs | 11 ++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/tests-js/update-ui.test.mjs b/tests-js/update-ui.test.mjs index c1b8bffb7f..8730dccc6e 100644 --- a/tests-js/update-ui.test.mjs +++ b/tests-js/update-ui.test.mjs @@ -157,6 +157,48 @@ test.skipIf(process.platform === 'win32')('probe Git reaches the staged main eve } }) +test.skipIf(process.platform === 'win32')('historical venv install without a PM launcher still checks staged Git main', () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-legacy-branch-')) + const git = execFileSync('which', ['git'], { encoding: 'utf8' }).trim() + try { + const checkout = path.join(root, 'checkout') + const bare = path.join(root, 'serve.git') + fs.mkdirSync(checkout) + const run = (args, cwd = checkout) => execFileSync(git, args, { cwd, encoding: 'utf8' }).trim() + run(['init', '-b', 'main']) + run(['-c', 'user.name=Fixture', '-c', 'user.email=e2e@example.invalid', '-c', 'commit.gpgsign=false', 'commit', '--allow-empty', '-m', 'old']) + const old = run(['rev-parse', 'HEAD']) + run(['-c', 'user.name=Fixture', '-c', 'user.email=e2e@example.invalid', '-c', 'commit.gpgsign=false', 'commit', '--allow-empty', '-m', 'target']) + const sha = run(['rev-parse', 'HEAD']) + run(['clone', '--bare', checkout, bare], root) + run(['reset', '--hard', old]) + run(['remote', 'add', 'origin', 'https://github.com/NousResearch/hermes-agent.git']) + const cfg = path.join(root, 'gitconfig') + run(['config', '--file', cfg, '--add', `url.file://${bare}.insteadOf`, 'https://github.com/NousResearch/hermes-agent.git']) + const legacy = path.join(checkout, 'venv', 'bin', 'hermes') + fs.mkdirSync(path.dirname(legacy), { recursive: true }) + fs.writeFileSync(legacy, '#!/bin/sh\nexit 0\n', { mode: 0o700 }) + const launchEnv = { HERMES_DESKTOP_USER_DATA_DIR: root } + const capturedEnv = { ...process.env, GIT_CONFIG_GLOBAL: cfg } + sourceBranchProbe.prepareSourceBranchEnvironment(checkout, sha, git, capturedEnv, launchEnv) + expect(fs.existsSync(path.join(checkout, '.hermes', 'bin', 'hermes'))).toBe(false) + expect(launchEnv.HERMES_E2E_SOURCE_ROOT).toBe(checkout) + expect(launchEnv.HERMES_E2E_SOURCE_URL).toBe(`file://${bare}`) + expect(launchEnv.NODE_OPTIONS).toContain('source-branch-probe.cjs') + const launcher = path.join(checkout, '.hermes', 'bin', 'hermes') + fs.mkdirSync(path.dirname(launcher), { recursive: true }) + fs.symlinkSync(path.join(root, 'missing'), launcher) + expect(() => sourceBranchProbe.prepareSourceBranchEnvironment(checkout, sha, git, capturedEnv, launchEnv)).toThrow(/launcher/) + fs.unlinkSync(launcher) + // A PM tree with no published launcher is unfinished, not legacy. + fs.mkdirSync(path.join(checkout, 'pm')) + fs.writeFileSync(path.join(checkout, 'pm', 'lock.json'), '{}') + expect(() => sourceBranchProbe.prepareSourceBranchEnvironment(checkout, sha, git, { ...process.env, GIT_CONFIG_GLOBAL: cfg }, launchEnv)).toThrow(/launcher/) + } finally { + fs.rmSync(root, { recursive: true, force: true }) + } +}) + test.skipIf(process.platform === 'win32')('preloaded historical Desktop Git check reads staged origin rather than the public API', () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-historical-probe-')) const git = execFileSync('which', ['git'], { encoding: 'utf8' }).trim() diff --git a/tests/install/e2e-assets/source-branch-probe.cjs b/tests/install/e2e-assets/source-branch-probe.cjs index 2ecd8aee2e..393b662276 100644 --- a/tests/install/e2e-assets/source-branch-probe.cjs +++ b/tests/install/e2e-assets/source-branch-probe.cjs @@ -50,7 +50,16 @@ function prepareSourceBranchEnvironment(root, expectedSha, realGit, capturedEnv, // every non-checker invocation untouched. const launcher = path.join(install, '.hermes', 'bin', 'hermes') const original = `${launcher}.e2e-original` - if (!fs.lstatSync(launcher).isFile() || fs.existsSync(original)) { + // Historical venv installs have no PM launcher. Their Electron-side probe + // handles the branch selection; only PM source checks need this wrapper. + const launcherStat = fs.lstatSync(launcher, { throwIfNoEntry: false }) + if (!launcherStat) { + if (!fs.existsSync(path.join(install, 'pm', 'lock.json')) + && fs.existsSync(path.join(install, 'venv', 'bin', 'hermes')) + && !fs.existsSync(original)) return + throw new Error('source app-update requires an unmodified installation launcher') + } + if (!launcherStat.isFile() || fs.existsSync(original)) { throw new Error('source app-update requires an unmodified installation launcher') } fs.copyFileSync(launcher, original)