mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
- 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
189 lines
5.1 KiB
TypeScript
189 lines
5.1 KiB
TypeScript
import { mkdirSync, mkdtempSync } from "node:fs";
|
|
import { homedir, tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import { classifyInvocation, isExistingDirectory, isPathLikeArg } from "./classify.js";
|
|
|
|
const knownCommands = new Set(["ls", "run", "status"]);
|
|
|
|
describe("classifyInvocation", () => {
|
|
it("classifies no args as CLI mode", () => {
|
|
expect(
|
|
classifyInvocation({
|
|
argv: [],
|
|
knownCommands,
|
|
cwd: process.cwd(),
|
|
}),
|
|
).toEqual({ kind: "cli", argv: [] });
|
|
});
|
|
|
|
it("classifies flags as CLI mode", () => {
|
|
expect(
|
|
classifyInvocation({
|
|
argv: ["--version"],
|
|
knownCommands,
|
|
cwd: process.cwd(),
|
|
}),
|
|
).toEqual({ kind: "cli", argv: ["--version"] });
|
|
});
|
|
|
|
it("classifies known commands as CLI mode", () => {
|
|
expect(
|
|
classifyInvocation({
|
|
argv: ["ls", "--json"],
|
|
knownCommands,
|
|
cwd: process.cwd(),
|
|
}),
|
|
).toEqual({ kind: "cli", argv: ["ls", "--json"] });
|
|
});
|
|
|
|
it("classifies '.' as an open-project invocation", () => {
|
|
const projectDir = mkdtempSync(path.join(tmpdir(), "paseo-classify-dot-"));
|
|
|
|
expect(
|
|
classifyInvocation({
|
|
argv: ["."],
|
|
knownCommands,
|
|
cwd: projectDir,
|
|
}),
|
|
).toEqual({
|
|
kind: "open-project",
|
|
resolvedPath: projectDir,
|
|
});
|
|
});
|
|
|
|
it("classifies '..' as an open-project invocation", () => {
|
|
const parentDir = mkdtempSync(path.join(tmpdir(), "paseo-classify-parent-"));
|
|
const childDir = path.join(parentDir, "child");
|
|
mkdirSync(childDir);
|
|
|
|
expect(
|
|
classifyInvocation({
|
|
argv: [".."],
|
|
knownCommands,
|
|
cwd: childDir,
|
|
}),
|
|
).toEqual({
|
|
kind: "open-project",
|
|
resolvedPath: parentDir,
|
|
});
|
|
});
|
|
|
|
it("classifies './myproject' as an open-project invocation", () => {
|
|
const parentDir = mkdtempSync(path.join(tmpdir(), "paseo-classify-relative-"));
|
|
const projectDir = path.join(parentDir, "myproject");
|
|
mkdirSync(projectDir);
|
|
|
|
expect(
|
|
classifyInvocation({
|
|
argv: ["./myproject"],
|
|
knownCommands,
|
|
cwd: parentDir,
|
|
}),
|
|
).toEqual({
|
|
kind: "open-project",
|
|
resolvedPath: projectDir,
|
|
});
|
|
});
|
|
|
|
it("classifies an absolute path as an open-project invocation", () => {
|
|
const projectDir = mkdtempSync(path.join(tmpdir(), "paseo-classify-absolute-"));
|
|
|
|
expect(
|
|
classifyInvocation({
|
|
argv: [projectDir],
|
|
knownCommands,
|
|
cwd: process.cwd(),
|
|
}),
|
|
).toEqual({
|
|
kind: "open-project",
|
|
resolvedPath: projectDir,
|
|
});
|
|
});
|
|
|
|
it("classifies a home-relative path as an open-project invocation", () => {
|
|
const projectDir = mkdtempSync(path.join(homedir(), "paseo-classify-home-"));
|
|
const relativeToHome = `~/${path.basename(projectDir)}`;
|
|
|
|
expect(
|
|
classifyInvocation({
|
|
argv: [relativeToHome],
|
|
knownCommands,
|
|
cwd: process.cwd(),
|
|
}),
|
|
).toEqual({
|
|
kind: "open-project",
|
|
resolvedPath: projectDir,
|
|
});
|
|
});
|
|
|
|
it("classifies an existing directory name as an open-project invocation", () => {
|
|
const parentDir = mkdtempSync(path.join(tmpdir(), "paseo-classify-existing-"));
|
|
const projectDir = path.join(parentDir, "myproject");
|
|
mkdirSync(projectDir);
|
|
|
|
expect(
|
|
classifyInvocation({
|
|
argv: ["myproject"],
|
|
knownCommands,
|
|
cwd: parentDir,
|
|
}),
|
|
).toEqual({
|
|
kind: "open-project",
|
|
resolvedPath: projectDir,
|
|
});
|
|
});
|
|
|
|
it("keeps known commands in CLI mode even when a matching directory exists", () => {
|
|
const parentDir = mkdtempSync(path.join(tmpdir(), "paseo-classify-command-"));
|
|
mkdirSync(path.join(parentDir, "status"));
|
|
|
|
expect(
|
|
classifyInvocation({
|
|
argv: ["status"],
|
|
knownCommands,
|
|
cwd: parentDir,
|
|
}),
|
|
).toEqual({
|
|
kind: "cli",
|
|
argv: ["status"],
|
|
});
|
|
});
|
|
|
|
it("classifies nonexistent args as CLI mode", () => {
|
|
expect(
|
|
classifyInvocation({
|
|
argv: ["nonexistent"],
|
|
knownCommands,
|
|
cwd: process.cwd(),
|
|
}),
|
|
).toEqual({
|
|
kind: "cli",
|
|
argv: ["nonexistent"],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("path helpers", () => {
|
|
it("detects path-like prefixes", () => {
|
|
expect(isPathLikeArg(".")).toBe(true);
|
|
expect(isPathLikeArg("..")).toBe(true);
|
|
expect(isPathLikeArg("./project")).toBe(true);
|
|
expect(isPathLikeArg("../project")).toBe(true);
|
|
expect(isPathLikeArg("/tmp/project")).toBe(true);
|
|
expect(isPathLikeArg("~")).toBe(true);
|
|
expect(isPathLikeArg("~/project")).toBe(true);
|
|
expect(isPathLikeArg("C:\\project")).toBe(true);
|
|
expect(isPathLikeArg("status")).toBe(false);
|
|
});
|
|
|
|
it("detects existing directories relative to cwd", () => {
|
|
const parentDir = mkdtempSync(path.join(tmpdir(), "paseo-classify-helper-"));
|
|
const projectDir = path.join(parentDir, "project");
|
|
mkdirSync(projectDir);
|
|
|
|
expect(isExistingDirectory({ pathArg: "project", cwd: parentDir })).toBe(true);
|
|
expect(isExistingDirectory({ pathArg: "missing", cwd: parentDir })).toBe(false);
|
|
});
|
|
});
|