Fix macOS packaged CLI daemon Dock icons (#1759)

* Fix macOS CLI daemon relaunch path

* test(cli): mock helper existence in daemon launch test

* fix(desktop): launch packaged CLI through Helper

The packaged macOS CLI shim entered through the main app executable, so daemon supervision inherited app lifecycle behavior and surfaced Dock icons. Make Helper the required macOS CLI runtime, keep daemon relaunches on process.execPath, and cover cold bundled CLI daemon starts in release smoke.

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
yz
2026-06-29 20:45:53 +02:00
committed by GitHub
co-authored by Mohamed Boudra
parent f94ec0e430
commit efd7ab3420
4 changed files with 274 additions and 7 deletions
@@ -1,10 +1,67 @@
import { readFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
import {
chmodSync,
copyFileSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
function writeExecutable(filePath: string, contents: string): void {
writeFileSync(filePath, contents, "utf8");
chmodSync(filePath, 0o755);
}
function createFakeMacBundle(options: { includeHelper: boolean }): {
root: string;
shimPath: string;
} {
const root = mkdtempSync(join(tmpdir(), "paseo-cli-shim-test-"));
const appPath = join(root, "Paseo.app");
const contentsPath = join(appPath, "Contents");
const resourcesPath = join(contentsPath, "Resources");
const shimPath = join(resourcesPath, "bin", "paseo");
const mainPath = join(contentsPath, "MacOS", "Paseo");
const helperPath = join(
contentsPath,
"Frameworks",
"Paseo Helper.app",
"Contents",
"MacOS",
"Paseo Helper",
);
mkdirSync(dirname(shimPath), { recursive: true });
mkdirSync(dirname(mainPath), { recursive: true });
copyFileSync(join(packageRoot, "bin", "paseo"), shimPath);
chmodSync(shimPath, 0o755);
writeExecutable(mainPath, "#!/bin/sh\necho main-executable\n");
if (options.includeHelper) {
mkdirSync(dirname(helperPath), { recursive: true });
writeExecutable(
helperPath,
[
"#!/bin/sh",
'printf "helper env=%s/%s\\n" "$ELECTRON_RUN_AS_NODE" "$PASEO_NODE_ENV"',
'printf "args=%s\\n" "$*"',
"",
].join("\n"),
);
}
return { root, shimPath };
}
describe("desktop packaging", () => {
it("unpacks server zsh shell integration files for external shells", () => {
const config = readFileSync(join(packageRoot, "electron-builder.yml"), "utf8");
@@ -48,4 +105,38 @@ describe("desktop packaging", () => {
expect(deps[required], `${required} must be declared in dependencies`).toBe("*");
}
});
it("launches the packaged macOS CLI through Helper instead of the main app executable", () => {
if (process.platform === "win32") return;
const bundle = createFakeMacBundle({ includeHelper: true });
try {
const result = spawnSync(bundle.shimPath, ["--version"], { encoding: "utf8" });
expect(result.status).toBe(0);
expect(result.stdout).toContain("helper env=1/production");
expect(result.stdout).toContain("node-entrypoint-runner.js");
expect(result.stdout).toContain("node-script");
expect(result.stdout).toContain("@getpaseo/cli/dist/index.js");
expect(result.stdout).toContain("--version");
expect(result.stdout).not.toContain("main-executable");
} finally {
rmSync(bundle.root, { recursive: true, force: true });
}
});
it("fails packaged macOS CLI startup when Helper is missing", () => {
if (process.platform === "win32") return;
const bundle = createFakeMacBundle({ includeHelper: false });
try {
const result = spawnSync(bundle.shimPath, ["--version"], { encoding: "utf8" });
expect(result.status).toBe(1);
expect(result.stderr).toContain("Bundled Paseo Helper executable not found");
expect(result.stdout).not.toContain("main-executable");
} finally {
rmSync(bundle.root, { recursive: true, force: true });
}
});
});