Fix CLI routing: single classifier, open-project for cold & hot start

- Add classify.ts as the single source of truth for CLI invocation
  routing (discriminated union, derives known commands from Commander)
- Remove all hardcoded command lists and duplicate path detection logic
- Simplify shell wrappers to dumb pipes (zero classification)
- Fix hot-start: use `open -n -g -a` (VS Code pattern) so second-instance
  event fires when app is already running
- Fix cold-start race: pull-based IPC (getPendingOpenProject) so renderer
  fetches the pending path after React mounts, instead of push event that
  arrived before the listener existed
- Fix asar read corruption: unpack node-entrypoint-runner.js from asar
  (Node.js v24 in Electron 41 can't parse package.json inside asar)
- Strip ELECTRON_RUN_AS_NODE from env when spawning desktop app from CLI
This commit is contained in:
Mohamed Boudra
2026-04-04 19:21:12 +07:00
parent fefe260f0a
commit 43542ac858
16 changed files with 441 additions and 288 deletions
@@ -1,44 +1,69 @@
import os from "node:os";
import { mkdtempSync, mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { mkdirSync, mkdtempSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { parseOpenProjectPathFromArgv } from "./open-project-routing";
describe("open-project-routing", () => {
it("keeps CLI flags out of project-open mode", () => {
it("returns a bare absolute path argument", () => {
const projectPath = mkdtempSync(path.join(tmpdir(), "paseo-open-project-"));
expect(
parseOpenProjectPathFromArgv({
argv: ["/Applications/Paseo.app/Contents/MacOS/Paseo", projectPath],
isDefaultApp: false,
}),
).toBe(projectPath);
});
it("finds a bare absolute path even when Chromium noise args appear first", () => {
const projectPath = mkdtempSync(path.join(tmpdir(), "paseo-open-project-"));
expect(
parseOpenProjectPathFromArgv({
argv: [
"/Applications/Paseo.app/Contents/MacOS/Paseo",
"--allow-file-access-from-files",
"--no-sandbox",
projectPath,
],
isDefaultApp: false,
}),
).toBe(projectPath);
});
it("does not treat flags as project paths", () => {
const projectPath = mkdtempSync(path.join(tmpdir(), "paseo-open-project-"));
const flagLikeDirectory = path.join(projectPath, "--version");
mkdirSync(flagLikeDirectory);
expect(
parseOpenProjectPathFromArgv({
argv: ["/Applications/Paseo.app/Contents/MacOS/Paseo", "--version", flagLikeDirectory],
isDefaultApp: false,
}),
).toBe(flagLikeDirectory);
expect(
parseOpenProjectPathFromArgv({
argv: ["/Applications/Paseo.app/Contents/MacOS/Paseo", "--version"],
isDefaultApp: false,
cwd: process.cwd(),
}),
).toBeNull();
});
it("resolves a lone existing directory argument to an absolute project path", () => {
const parentDir = mkdtempSync(path.join(os.tmpdir(), "paseo-desktop-open-"));
const projectDir = path.join(parentDir, "project");
mkdirSync(projectDir);
it("returns the path from an explicit --open-project flag for backward compatibility", () => {
const projectPath = mkdtempSync(path.join(tmpdir(), "paseo-open-project-"));
expect(
parseOpenProjectPathFromArgv({
argv: ["/Applications/Paseo.app/Contents/MacOS/Paseo", "project"],
argv: [
"/Applications/Paseo.app/Contents/MacOS/Paseo",
"--open-project",
projectPath,
],
isDefaultApp: false,
cwd: parentDir,
}),
).toBe(projectDir);
});
it("keeps known CLI commands in passthrough mode even if a directory matches", () => {
const parentDir = mkdtempSync(path.join(os.tmpdir(), "paseo-desktop-command-"));
mkdirSync(path.join(parentDir, "status"));
expect(
parseOpenProjectPathFromArgv({
argv: ["/Applications/Paseo.app/Contents/MacOS/Paseo", "status"],
isDefaultApp: false,
cwd: parentDir,
}),
).toBeNull();
).toBe(projectPath);
});
});