import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; import { assertSuccess } from "@effect/vitest/utils"; import { FileSystem, Path, Effect } from "effect"; import { isCommandAvailable, launchDetached, resolveAvailableEditors, resolveEditorLaunch, } from "./open.ts"; it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => { it.effect("returns commands for command-based editors", () => Effect.gen(function* () { const antigravityLaunch = yield* resolveEditorLaunch( { cwd: "/tmp/workspace", editor: "antigravity" }, "darwin", { PATH: "" }, ); assert.deepEqual(antigravityLaunch, { command: "agy", args: ["/tmp/workspace"], }); const cursorLaunch = yield* resolveEditorLaunch( { cwd: "/tmp/workspace", editor: "cursor" }, "darwin", { PATH: "" }, ); assert.deepEqual(cursorLaunch, { command: "cursor", args: ["/tmp/workspace"], }); const traeLaunch = yield* resolveEditorLaunch( { cwd: "/tmp/workspace", editor: "trae" }, "darwin", ); assert.deepEqual(traeLaunch, { command: "trae", args: ["/tmp/workspace"], }); const kiroLaunch = yield* resolveEditorLaunch( { cwd: "/tmp/workspace", editor: "kiro" }, "darwin", { PATH: "" }, ); assert.deepEqual(kiroLaunch, { command: "kiro", args: ["ide", "/tmp/workspace"], }); const vscodeLaunch = yield* resolveEditorLaunch( { cwd: "/tmp/workspace", editor: "vscode" }, "darwin", { PATH: "" }, ); assert.deepEqual(vscodeLaunch, { command: "code", args: ["/tmp/workspace"], }); const vscodeInsidersLaunch = yield* resolveEditorLaunch( { cwd: "/tmp/workspace", editor: "vscode-insiders" }, "darwin", ); assert.deepEqual(vscodeInsidersLaunch, { command: "code-insiders", args: ["/tmp/workspace"], }); const vscodiumLaunch = yield* resolveEditorLaunch( { cwd: "/tmp/workspace", editor: "vscodium" }, "darwin", ); assert.deepEqual(vscodiumLaunch, { command: "codium", args: ["/tmp/workspace"], }); const zedLaunch = yield* resolveEditorLaunch( { cwd: "/tmp/workspace", editor: "zed" }, "darwin", { PATH: "" }, ); assert.deepEqual(zedLaunch, { command: "zed", args: ["/tmp/workspace"], }); const ideaLaunch = yield* resolveEditorLaunch( { cwd: "/tmp/workspace", editor: "idea" }, "darwin", ); assert.deepEqual(ideaLaunch, { command: "idea", args: ["/tmp/workspace"], }); }), ); it.effect("applies launch-style-specific navigation arguments", () => Effect.gen(function* () { const lineOnly = yield* resolveEditorLaunch( { cwd: "/tmp/workspace/AGENTS.md:48", editor: "cursor" }, "darwin", { PATH: "" }, ); assert.deepEqual(lineOnly, { command: "cursor", args: ["--goto", "/tmp/workspace/AGENTS.md:48"], }); const lineAndColumn = yield* resolveEditorLaunch( { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "cursor" }, "darwin", { PATH: "" }, ); assert.deepEqual(lineAndColumn, { command: "cursor", args: ["--goto", "/tmp/workspace/src/open.ts:71:5"], }); const traeLineAndColumn = yield* resolveEditorLaunch( { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "trae" }, "darwin", ); assert.deepEqual(traeLineAndColumn, { command: "trae", args: ["--goto", "/tmp/workspace/src/open.ts:71:5"], }); const kiroLineAndColumn = yield* resolveEditorLaunch( { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "kiro" }, "darwin", { PATH: "" }, ); assert.deepEqual(kiroLineAndColumn, { command: "kiro", args: ["ide", "--goto", "/tmp/workspace/src/open.ts:71:5"], }); const vscodeLineAndColumn = yield* resolveEditorLaunch( { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "vscode" }, "darwin", { PATH: "" }, ); assert.deepEqual(vscodeLineAndColumn, { command: "code", args: ["--goto", "/tmp/workspace/src/open.ts:71:5"], }); const vscodeInsidersLineAndColumn = yield* resolveEditorLaunch( { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "vscode-insiders" }, "darwin", ); assert.deepEqual(vscodeInsidersLineAndColumn, { command: "code-insiders", args: ["--goto", "/tmp/workspace/src/open.ts:71:5"], }); const vscodiumLineAndColumn = yield* resolveEditorLaunch( { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "vscodium" }, "darwin", ); assert.deepEqual(vscodiumLineAndColumn, { command: "codium", args: ["--goto", "/tmp/workspace/src/open.ts:71:5"], }); const zedLineAndColumn = yield* resolveEditorLaunch( { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "zed" }, "darwin", { PATH: "" }, ); assert.deepEqual(zedLineAndColumn, { command: "zed", args: ["/tmp/workspace/src/open.ts:71:5"], }); const zedLineOnly = yield* resolveEditorLaunch( { cwd: "/tmp/workspace/AGENTS.md:48", editor: "zed" }, "darwin", { PATH: "" }, ); assert.deepEqual(zedLineOnly, { command: "zed", args: ["/tmp/workspace/AGENTS.md:48"], }); const ideaLineOnly = yield* resolveEditorLaunch( { cwd: "/tmp/workspace/AGENTS.md:48", editor: "idea" }, "darwin", ); assert.deepEqual(ideaLineOnly, { command: "idea", args: ["--line", "48", "/tmp/workspace/AGENTS.md"], }); const ideaLineAndColumn = yield* resolveEditorLaunch( { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "idea" }, "darwin", ); assert.deepEqual(ideaLineAndColumn, { command: "idea", args: ["--line", "71", "--column", "5", "/tmp/workspace/src/open.ts"], }); }), ); it.effect("falls back to zeditor when zed is not installed", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-test-" }); yield* fs.writeFileString(path.join(dir, "zeditor"), "#!/bin/sh\nexit 0\n"); yield* fs.chmod(path.join(dir, "zeditor"), 0o755); const result = yield* resolveEditorLaunch({ cwd: "/tmp/workspace", editor: "zed" }, "linux", { PATH: dir, }); assert.deepEqual(result, { command: "zeditor", args: ["/tmp/workspace"], }); }), ); it.effect("falls back to the primary command when no alias is installed", () => Effect.gen(function* () { const result = yield* resolveEditorLaunch({ cwd: "/tmp/workspace", editor: "zed" }, "linux", { PATH: "", }); assert.deepEqual(result, { command: "zed", args: ["/tmp/workspace"], }); }), ); it.effect("maps file-manager editor to OS open commands", () => Effect.gen(function* () { const launch1 = yield* resolveEditorLaunch( { cwd: "/tmp/workspace", editor: "file-manager" }, "darwin", { PATH: "" }, ); assert.deepEqual(launch1, { command: "open", args: ["/tmp/workspace"], }); const launch2 = yield* resolveEditorLaunch( { cwd: "C:\\workspace", editor: "file-manager" }, "win32", { PATH: "" }, ); assert.deepEqual(launch2, { command: "explorer", args: ["C:\\workspace"], }); const launch3 = yield* resolveEditorLaunch( { cwd: "/tmp/workspace", editor: "file-manager" }, "linux", { PATH: "" }, ); assert.deepEqual(launch3, { command: "xdg-open", args: ["/tmp/workspace"], }); }), ); }); it.layer(NodeServices.layer)("launchDetached", (it) => { it.effect("resolves when command can be spawned", () => Effect.gen(function* () { const result = yield* launchDetached({ command: process.execPath, args: ["-e", "process.exit(0)"], }).pipe(Effect.result); assertSuccess(result, undefined); }), ); it.effect("rejects when command does not exist", () => Effect.gen(function* () { const result = yield* launchDetached({ command: `t3code-no-such-command-${Date.now()}`, args: [], }).pipe(Effect.result); assert.equal(result._tag, "Failure"); }), ); }); it.layer(NodeServices.layer)("isCommandAvailable", (it) => { it.effect("resolves win32 commands with PATHEXT", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-test-" }); yield* fs.writeFileString(path.join(dir, "code.CMD"), "@echo off\r\n"); const env = { PATH: dir, PATHEXT: ".COM;.EXE;.BAT;.CMD", } satisfies NodeJS.ProcessEnv; assert.equal(isCommandAvailable("code", { platform: "win32", env }), true); }), ); it("returns false when a command is not on PATH", () => { const env = { PATH: "", PATHEXT: ".COM;.EXE;.BAT;.CMD", } satisfies NodeJS.ProcessEnv; assert.equal(isCommandAvailable("definitely-not-installed", { platform: "win32", env }), false); }); it.effect("does not treat bare files without executable extension as available on win32", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-test-" }); yield* fs.writeFileString(path.join(dir, "npm"), "echo nope\r\n"); const env = { PATH: dir, PATHEXT: ".COM;.EXE;.BAT;.CMD", } satisfies NodeJS.ProcessEnv; assert.equal(isCommandAvailable("npm", { platform: "win32", env }), false); }), ); it.effect("appends PATHEXT for commands with non-executable extensions on win32", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-test-" }); yield* fs.writeFileString(path.join(dir, "my.tool.CMD"), "@echo off\r\n"); const env = { PATH: dir, PATHEXT: ".COM;.EXE;.BAT;.CMD", } satisfies NodeJS.ProcessEnv; assert.equal(isCommandAvailable("my.tool", { platform: "win32", env }), true); }), ); it.effect("uses platform-specific PATH delimiter for platform overrides", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const firstDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-test-" }); const secondDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-test-" }); yield* fs.writeFileString(path.join(firstDir, "code.CMD"), "@echo off\r\n"); yield* fs.writeFileString(path.join(secondDir, "code.CMD"), "MZ"); const env = { PATH: `${firstDir};${secondDir}`, PATHEXT: ".COM;.EXE;.BAT;.CMD", } satisfies NodeJS.ProcessEnv; assert.equal(isCommandAvailable("code", { platform: "win32", env }), true); }), ); }); it.layer(NodeServices.layer)("resolveAvailableEditors", (it) => { it.effect("returns installed editors for command launches", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-editors-" }); yield* fs.writeFileString(path.join(dir, "trae.CMD"), "@echo off\r\n"); yield* fs.writeFileString(path.join(dir, "kiro.CMD"), "@echo off\r\n"); yield* fs.writeFileString(path.join(dir, "code-insiders.CMD"), "@echo off\r\n"); yield* fs.writeFileString(path.join(dir, "codium.CMD"), "@echo off\r\n"); yield* fs.writeFileString(path.join(dir, "explorer.CMD"), "MZ"); const editors = resolveAvailableEditors("win32", { PATH: dir, PATHEXT: ".COM;.EXE;.BAT;.CMD", }); assert.deepEqual(editors, ["trae", "kiro", "vscode-insiders", "vscodium", "file-manager"]); }), ); it.effect("includes zed when only the zeditor command is installed", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-editors-" }); yield* fs.writeFileString(path.join(dir, "zeditor"), "#!/bin/sh\nexit 0\n"); yield* fs.writeFileString(path.join(dir, "xdg-open"), "#!/bin/sh\nexit 0\n"); yield* fs.chmod(path.join(dir, "zeditor"), 0o755); yield* fs.chmod(path.join(dir, "xdg-open"), 0o755); const editors = resolveAvailableEditors("linux", { PATH: dir, }); assert.deepEqual(editors, ["zed", "file-manager"]); }), ); it("omits file-manager when the platform opener is unavailable", () => { const editors = resolveAvailableEditors("linux", { PATH: "", }); assert.deepEqual(editors, []); }); });