mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-10-16 07:16:36 +08:00
fix(release): make Windows Squirrel app stop idempotent
This commit is contained in:
@@ -195,19 +195,40 @@ export async function findReadyElectronUpdaterPage(pages) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function stopInstalledApp(executable) {
|
||||
const script = [
|
||||
export function buildStopInstalledAppScript() {
|
||||
const matchingProcesses =
|
||||
"@(Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -and [String]::Equals([System.IO.Path]::GetFullPath($_.ExecutablePath), $target, [StringComparison]::OrdinalIgnoreCase) })";
|
||||
return [
|
||||
"$ErrorActionPreference = 'Stop'",
|
||||
"$target = [System.IO.Path]::GetFullPath($env:LIME_TARGET_EXECUTABLE)",
|
||||
"$processes = @(Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -and [String]::Equals([System.IO.Path]::GetFullPath($_.ExecutablePath), $target, [StringComparison]::OrdinalIgnoreCase) })",
|
||||
"$processes | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction Stop }",
|
||||
`$processes = ${matchingProcesses}`,
|
||||
"$processes | ForEach-Object { try { Stop-Process -Id $_.ProcessId -Force -ErrorAction Stop } catch { if ($_.FullyQualifiedErrorId -notmatch '^NoProcessFoundForGivenId(?:,|$)') { throw } } }",
|
||||
'Write-Output "stopped=$($processes.Count)"',
|
||||
].join("; ");
|
||||
const result = await runProcess(
|
||||
}
|
||||
|
||||
export async function stopInstalledApp(
|
||||
executable,
|
||||
{
|
||||
runProcessImpl = runProcess,
|
||||
waitForProcessExitImpl = waitForWindowsProcessExit,
|
||||
timeoutMs = 30_000,
|
||||
} = {},
|
||||
) {
|
||||
const result = await runProcessImpl(
|
||||
"powershell.exe",
|
||||
["-NoProfile", "-NonInteractive", "-Command", script],
|
||||
[
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-Command",
|
||||
buildStopInstalledAppScript(),
|
||||
],
|
||||
{
|
||||
env: { ...process.env, LIME_TARGET_EXECUTABLE: executable },
|
||||
timeoutMs: 30_000,
|
||||
env: {
|
||||
...process.env,
|
||||
LIME_TARGET_EXECUTABLE: executable,
|
||||
},
|
||||
timeoutMs,
|
||||
},
|
||||
);
|
||||
if (result.exitCode !== 0) {
|
||||
@@ -215,6 +236,10 @@ export async function stopInstalledApp(executable) {
|
||||
`failed to stop installed app at ${executable}: exit ${result.exitCode}`,
|
||||
);
|
||||
}
|
||||
await waitForProcessExitImpl(executable, {
|
||||
runProcessImpl,
|
||||
timeoutMs,
|
||||
});
|
||||
return { executable, exitCode: result.exitCode };
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import process from "node:process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
buildNMinusOneLaunchEnv,
|
||||
buildStopInstalledAppScript,
|
||||
buildWaitForWindowsProcessExitScript,
|
||||
classifySquirrelUninstallResult,
|
||||
compareVersions,
|
||||
@@ -39,6 +40,7 @@ import {
|
||||
|
||||
export {
|
||||
buildNMinusOneLaunchEnv,
|
||||
buildStopInstalledAppScript,
|
||||
buildWaitForWindowsProcessExitScript,
|
||||
classifySquirrelUninstallResult,
|
||||
compareVersions,
|
||||
@@ -48,6 +50,7 @@ export {
|
||||
resolveInstalledSquirrelPaths,
|
||||
resolveSquirrelFeed,
|
||||
selectNMinusOneVersion,
|
||||
stopInstalledApp,
|
||||
uninstallInstalledSquirrel,
|
||||
waitForWindowsProcessExit,
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ import YAML from "yaml";
|
||||
|
||||
import {
|
||||
buildNMinusOneLaunchEnv,
|
||||
buildStopInstalledAppScript,
|
||||
buildWaitForWindowsProcessExitScript,
|
||||
buildWindowsRcSummary,
|
||||
cleanupFromSummary,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
resolveSquirrelFeed,
|
||||
selectNMinusOneVersion,
|
||||
selectSquirrelInstaller,
|
||||
stopInstalledApp,
|
||||
uninstallInstalledSquirrel,
|
||||
waitForWindowsProcessExit,
|
||||
} from "./windows-squirrel-rc-smoke.mjs";
|
||||
@@ -160,6 +162,49 @@ describe("Windows Squirrel RC smoke", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("停止已退出的应用时忽略 PID 竞态,但保留其他 Stop-Process 错误", async () => {
|
||||
const script = buildStopInstalledAppScript();
|
||||
expect(script).toContain("NoProcessFoundForGivenId");
|
||||
expect(script).toContain("FullyQualifiedErrorId");
|
||||
expect(script).toContain("throw");
|
||||
|
||||
const runProcessImpl = vi.fn().mockResolvedValue({ exitCode: 0 });
|
||||
const waitForProcessExitImpl = vi.fn().mockResolvedValue({
|
||||
executable: "C:\\runner\\Lime.exe",
|
||||
exitCode: 0,
|
||||
timeoutMs: 30_000,
|
||||
});
|
||||
|
||||
await expect(
|
||||
stopInstalledApp("C:\\runner\\Lime.exe", {
|
||||
runProcessImpl,
|
||||
waitForProcessExitImpl,
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
executable: "C:\\runner\\Lime.exe",
|
||||
exitCode: 0,
|
||||
});
|
||||
expect(runProcessImpl).toHaveBeenCalledWith(
|
||||
"powershell.exe",
|
||||
[
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-Command",
|
||||
script,
|
||||
],
|
||||
expect.objectContaining({
|
||||
env: expect.objectContaining({
|
||||
LIME_TARGET_EXECUTABLE: "C:\\runner\\Lime.exe",
|
||||
}),
|
||||
timeoutMs: 30_000,
|
||||
}),
|
||||
);
|
||||
expect(waitForProcessExitImpl).toHaveBeenCalledWith(
|
||||
"C:\\runner\\Lime.exe",
|
||||
{ runProcessImpl, timeoutMs: 30_000 },
|
||||
);
|
||||
});
|
||||
|
||||
it("卸载只操作候选 Update.exe,并等待安装目录与快捷方式消失", async () => {
|
||||
const updateExecutable =
|
||||
"C:\\Users\\runner\\AppData\\Local\\lime\\Update.exe";
|
||||
|
||||
Reference in New Issue
Block a user