import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";import * as NodePath from "@effect/platform-node/NodePath";import { assert, it } from "@effect/vitest";import { Deferred, Effect, Exit, FileSystem, Layer, PlatformError, Scope, Sink, Stream,} from "effect";import { ChildProcessSpawner } from "effect/unstable/process"; import { makeOpenLayer, Open } from "./open"; const encoder = new TextEncoder(); interface SpawnCall { readonly command: string; readonly args: ReadonlyArray<string>; readonly detached?: boolean | undefined; readonly shell?: boolean | string | undefined; readonly stdin?: unknown; readonly stdout?: unknown; readonly stderr?: unknown;} function decodePowerShellCommand(encoded: string): string { return Buffer.from(encoded, "base64").toString("utf16le");} function platformSpawnError(message: string) { return PlatformError.systemError({ _tag: "Unknown", module: "ChildProcess", method: "spawn", description: message, });} function spawnerLayer( calls: Array<SpawnCall>, handler: (call: SpawnCall) => { readonly code?: number; readonly fail?: string; readonly awaitExit?: Deferred.Deferred<void>; readonly onKill?: () => void; } = () => ({ code: 0, }),) { return Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make((command) => { const standardCommand = command as unknown as { readonly command: string; readonly args: ReadonlyArray<string>; readonly options: { readonly detached?: boolean; readonly shell?: boolean | string; readonly stdin?: unknown; readonly stdout?: unknown; readonly stderr?: unknown; }; }; const call: SpawnCall = { command: standardCommand.command, args: [...standardCommand.args], detached: standardCommand.options.detached, shell: standardCommand.options.shell, stdin: standardCommand.options.stdin, stdout: standardCommand.options.stdout, stderr: standardCommand.options.stderr, }; calls.push(call); const result = handler(call); if (result.fail) { return Effect.fail(platformSpawnError(result.fail)); } let exited = false; const exitCode = result.awaitExit ? Deferred.await(result.awaitExit).pipe( Effect.tap(() => Effect.sync(() => { exited = true; }), ), Effect.andThen(Effect.succeed(ChildProcessSpawner.ExitCode(result.code ?? 0))), ) : Effect.sync(() => { exited = true; return ChildProcessSpawner.ExitCode(result.code ?? 0); }); return Effect.succeed( ChildProcessSpawner.makeHandle({ pid: ChildProcessSpawner.ProcessId(calls.length), exitCode, isRunning: Effect.succeed(false), kill: () => Effect.sync(() => { if (!exited) { result.onKill?.(); } }), stdin: Sink.drain, stdout: Stream.make(encoder.encode("")), stderr: Stream.make(encoder.encode("")), all: Stream.empty, getInputFd: () => Sink.drain, getOutputFd: () => Stream.empty, }), ); }), );} const provideOpen = ( options: Parameters<typeof makeOpenLayer>[0], spawnLayer: Layer.Layer<ChildProcessSpawner.ChildProcessSpawner>,) => makeOpenLayer(options).pipe( Layer.provide(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer, spawnLayer)), ); const runOpen = <A>( options: Parameters<typeof makeOpenLayer>[0], spawnLayer: Layer.Layer<ChildProcessSpawner.ChildProcessSpawner>, effect: Effect.Effect<A, Error, Open>,) => effect.pipe(Effect.provide(provideOpen(options, spawnLayer))); const readOpen = Effect.service(Open); const writeExecutable = (filePath: string) => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; yield* fs.writeFileString(filePath, "#!/bin/sh\nexit 0\n"); yield* fs.chmod(filePath, 0o755); }); it.effect("getAvailableEditors detects installed editors through the service", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-editors-" }); yield* fs.writeFileString(`${dir}/code-insiders.CMD`, "@echo off\r\n"); yield* fs.writeFileString(`${dir}/codium.CMD`, "@echo off\r\n"); yield* fs.writeFileString(`${dir}/explorer.CMD`, "@echo off\r\n"); const calls: Array<SpawnCall> = []; const open = yield* runOpen( { platform: "win32", env: { PATH: dir, PATHEXT: ".COM;.EXE;.BAT;.CMD", }, }, spawnerLayer(calls), readOpen, ); const editors = yield* open.getAvailableEditors; assert.deepEqual(editors, ["vscode-insiders", "vscodium", "file-manager"]); assert.deepEqual(calls, []); }).pipe(Effect.provide(NodeFileSystem.layer)),); it.effect("getAvailableEditors does not advertise WSL file-manager from PowerShell alone", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-wsl-editors-" }); yield* fs.makeDirectory(`${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0`, { recursive: true, }); yield* writeExecutable(`${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`); const calls: Array<SpawnCall> = []; const open = yield* runOpen( { platform: "linux", env: { PATH: `${dir}:${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0`, WSL_DISTRO_NAME: "Ubuntu", }, isWsl: true, isInsideContainer: false, powerShellCommand: `${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`, }, spawnerLayer(calls), readOpen, ); const editors = yield* open.getAvailableEditors; assert.equal(editors.includes("file-manager"), false); assert.deepEqual(calls, []); }).pipe(Effect.provide(NodeFileSystem.layer)),); it.effect("openInEditor uses --goto for editors that support it", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-cursor-" }); yield* writeExecutable(`${dir}/cursor`); const calls: Array<SpawnCall> = []; const open = yield* runOpen( { platform: "darwin", env: { PATH: dir }, }, spawnerLayer(calls), readOpen, ); yield* open.openInEditor({ cwd: "/tmp/workspace/src/open.ts:71:5", editor: "cursor", }); assert.deepEqual(calls, [ { command: `${dir}/cursor`, args: ["--goto", "/tmp/workspace/src/open.ts:71:5"], detached: true, shell: false, stdin: "ignore", stdout: "ignore", stderr: "ignore", }, ]); }).pipe(Effect.provide(NodeFileSystem.layer)),); it.effect("openInEditor launches Windows batch shims through cmd.exe without shell mode", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-vscode-win32-" }); yield* fs.writeFileString(`${dir}/code.cmd`, "@echo off\r\n"); const calls: Array<SpawnCall> = []; const open = yield* runOpen( { platform: "win32", env: { PATH: dir, PATHEXT: ".COM;.EXE;.BAT;.CMD", }, }, spawnerLayer(calls), readOpen, ); yield* open.openInEditor({ cwd: "C:\\work\\100% real\\file.ts:12:4", editor: "vscode", }); assert.equal(calls.length, 1); assert.equal(calls[0]?.command, "cmd.exe"); assert.deepEqual(calls[0]?.args.slice(0, 4), ["/d", "/v:off", "/s", "/c"]); assert.equal(calls[0]?.args[4]?.toLowerCase().includes(`${dir}/code.cmd`.toLowerCase()), true); assert.equal(calls[0]?.args[4]?.includes('"--goto"'), true); assert.equal(calls[0]?.args[4]?.includes("100%% real"), true); assert.equal(calls[0]?.detached, true); assert.equal(calls[0]?.shell, false); assert.equal(calls[0]?.stdin, "ignore"); assert.equal(calls[0]?.stdout, "ignore"); assert.equal(calls[0]?.stderr, "ignore"); }).pipe(Effect.provide(NodeFileSystem.layer)),); it.effect("openInEditor detached launches survive service scope shutdown", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-zed-detached-" }); yield* writeExecutable(`${dir}/zed`); const calls: Array<SpawnCall> = []; const exitDeferred = yield* Deferred.make<void>(); let killCount = 0; yield* Effect.acquireUseRelease( Scope.make("sequential"), (scope) => Effect.gen(function* () { const runtimeServices = yield* Layer.build( provideOpen( { platform: "darwin", env: { PATH: dir }, }, spawnerLayer(calls, () => ({ awaitExit: exitDeferred, onKill: () => { killCount += 1; }, })), ), ).pipe(Scope.provide(scope)); const open = yield* readOpen.pipe(Effect.provide(runtimeServices), Scope.provide(scope)); yield* open.openInEditor({ cwd: "/tmp/workspace", editor: "zed", }); }), (scope) => Scope.close(scope, Exit.void), ); assert.equal(killCount, 0); assert.deepEqual(calls, [ { command: `${dir}/zed`, args: ["/tmp/workspace"], detached: true, shell: false, stdin: "ignore", stdout: "ignore", stderr: "ignore", }, ]); yield* Deferred.succeed(exitDeferred, void 0); yield* Effect.yieldNow; assert.equal(killCount, 0); }).pipe(Effect.provide(NodeFileSystem.layer)),); it.effect("openInEditor uses the default opener for file-manager on macOS", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-macos-" }); yield* writeExecutable(`${dir}/open`); const calls: Array<SpawnCall> = []; const open = yield* runOpen( { platform: "darwin", env: { PATH: dir }, }, spawnerLayer(calls), readOpen, ); yield* open.openInEditor({ cwd: "/tmp/workspace", editor: "file-manager", }); assert.deepEqual(calls, [ { command: `${dir}/open`, args: ["/tmp/workspace"], detached: undefined, shell: false, stdin: undefined, stdout: undefined, stderr: undefined, }, ]); }).pipe(Effect.provide(NodeFileSystem.layer)),); it.effect("openBrowser uses macOS open flags and app arguments", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-browser-macos-" }); yield* writeExecutable(`${dir}/open`); const calls: Array<SpawnCall> = []; const open = yield* runOpen( { platform: "darwin", env: { PATH: dir }, }, spawnerLayer(calls), readOpen, ); yield* open.openBrowser("https://example.com", { wait: true, background: true, newInstance: true, app: { name: "google chrome", arguments: ["--profile-directory=Work"], }, }); assert.deepEqual(calls, [ { command: `${dir}/open`, args: [ "--wait-apps", "--background", "--new", "-a", "google chrome", "https://example.com", "--args", "--profile-directory=Work", ], detached: undefined, shell: false, stdin: undefined, stdout: undefined, stderr: undefined, }, ]); }).pipe(Effect.provide(NodeFileSystem.layer)),); it.effect("openBrowser uses PowerShell on win32", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-browser-win32-" }); yield* fs.writeFileString(`${dir}/powershell.exe`, "MZ"); const calls: Array<SpawnCall> = []; const open = yield* runOpen( { platform: "win32", env: { PATH: dir, PATHEXT: ".COM;.EXE;.BAT;.CMD", }, }, spawnerLayer(calls), readOpen, ); yield* open.openBrowser("https://example.com"); assert.equal(calls.length, 1); assert.equal(calls[0]?.command, `${dir}/powershell.exe`); assert.equal(calls[0]?.args.at(-2), "-EncodedCommand"); assert.equal( decodePowerShellCommand(calls[0]?.args.at(-1) ?? ""), "Start 'https://example.com'", ); }).pipe(Effect.provide(NodeFileSystem.layer)),); it.effect("openBrowser falls back from WSL PowerShell to xdg-open", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-browser-wsl-" }); yield* fs.makeDirectory(`${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0`, { recursive: true, }); yield* writeExecutable(`${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`); yield* writeExecutable(`${dir}/xdg-open`); const calls: Array<SpawnCall> = []; const open = yield* runOpen( { platform: "linux", env: { PATH: `${dir}:${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0`, WSL_DISTRO_NAME: "Ubuntu", }, isWsl: true, isInsideContainer: false, powerShellCommand: `${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`, }, spawnerLayer(calls, (call) => call.command.includes("powershell") ? { fail: "powershell unavailable" } : { code: 0, }, ), readOpen, ); yield* open.openBrowser("https://example.com"); assert.equal(calls[0]?.command.includes("powershell"), true); assert.equal(calls[1]?.command, `${dir}/xdg-open`); assert.deepEqual(calls[1]?.args, ["https://example.com"]); }).pipe(Effect.provide(NodeFileSystem.layer)),); it.effect("openInEditor uses xdg-open first for WSL file-manager paths", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-wsl-file-manager-" }); yield* fs.makeDirectory(`${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0`, { recursive: true, }); yield* writeExecutable(`${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`); yield* writeExecutable(`${dir}/xdg-open`); const calls: Array<SpawnCall> = [];Lines 1–500 of 558. Load more
import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem"; import * as NodePath from "@effect/platform-node/NodePath"; import { assert, it } from "@effect/vitest"; import { Deferred, Effect, Exit, FileSystem, Layer, PlatformError, Scope, Sink, Stream, } from "effect"; import { ChildProcessSpawner } from "effect/unstable/process"; import { makeOpenLayer, Open } from "./open"; const encoder = new TextEncoder(); interface SpawnCall { readonly command: string; readonly args: ReadonlyArray<string>; readonly detached?: boolean | undefined; readonly shell?: boolean | string | undefined; readonly stdin?: unknown; readonly stdout?: unknown; readonly stderr?: unknown; } function decodePowerShellCommand(encoded: string): string { return Buffer.from(encoded, "base64").toString("utf16le"); } function platformSpawnError(message: string) { return PlatformError.systemError({ _tag: "Unknown", module: "ChildProcess", method: "spawn", description: message, }); } function spawnerLayer( calls: Array<SpawnCall>, handler: (call: SpawnCall) => { readonly code?: number; readonly fail?: string; readonly awaitExit?: Deferred.Deferred<void>; readonly onKill?: () => void; } = () => ({ code: 0, }), ) { return Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make((command) => { const standardCommand = command as unknown as { readonly command: string; readonly args: ReadonlyArray<string>; readonly options: { readonly detached?: boolean; readonly shell?: boolean | string; readonly stdin?: unknown; readonly stdout?: unknown; readonly stderr?: unknown; }; }; const call: SpawnCall = { command: standardCommand.command, args: [...standardCommand.args], detached: standardCommand.options.detached, shell: standardCommand.options.shell, stdin: standardCommand.options.stdin, stdout: standardCommand.options.stdout, stderr: standardCommand.options.stderr, }; calls.push(call); const result = handler(call); if (result.fail) { return Effect.fail(platformSpawnError(result.fail)); } let exited = false; const exitCode = result.awaitExit ? Deferred.await(result.awaitExit).pipe( Effect.tap(() => Effect.sync(() => { exited = true; }), ), Effect.andThen(Effect.succeed(ChildProcessSpawner.ExitCode(result.code ?? 0))), ) : Effect.sync(() => { exited = true; return ChildProcessSpawner.ExitCode(result.code ?? 0); }); return Effect.succeed( ChildProcessSpawner.makeHandle({ pid: ChildProcessSpawner.ProcessId(calls.length), exitCode, isRunning: Effect.succeed(false), kill: () => Effect.sync(() => { if (!exited) { result.onKill?.(); } }), stdin: Sink.drain, stdout: Stream.make(encoder.encode("")), stderr: Stream.make(encoder.encode("")), all: Stream.empty, getInputFd: () => Sink.drain, getOutputFd: () => Stream.empty, }), ); }), ); } const provideOpen = ( options: Parameters<typeof makeOpenLayer>[0], spawnLayer: Layer.Layer<ChildProcessSpawner.ChildProcessSpawner>, ) => makeOpenLayer(options).pipe( Layer.provide(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer, spawnLayer)), ); const runOpen = <A>( options: Parameters<typeof makeOpenLayer>[0], spawnLayer: Layer.Layer<ChildProcessSpawner.ChildProcessSpawner>, effect: Effect.Effect<A, Error, Open>, ) => effect.pipe(Effect.provide(provideOpen(options, spawnLayer))); const readOpen = Effect.service(Open); const writeExecutable = (filePath: string) => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; yield* fs.writeFileString(filePath, "#!/bin/sh\nexit 0\n"); yield* fs.chmod(filePath, 0o755); }); it.effect("getAvailableEditors detects installed editors through the service", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-editors-" }); yield* fs.writeFileString(`${dir}/code-insiders.CMD`, "@echo off\r\n"); yield* fs.writeFileString(`${dir}/codium.CMD`, "@echo off\r\n"); yield* fs.writeFileString(`${dir}/explorer.CMD`, "@echo off\r\n"); const calls: Array<SpawnCall> = []; const open = yield* runOpen( { platform: "win32", env: { PATH: dir, PATHEXT: ".COM;.EXE;.BAT;.CMD", }, }, spawnerLayer(calls), readOpen, ); const editors = yield* open.getAvailableEditors; assert.deepEqual(editors, ["vscode-insiders", "vscodium", "file-manager"]); assert.deepEqual(calls, []); }).pipe(Effect.provide(NodeFileSystem.layer)), ); it.effect("getAvailableEditors does not advertise WSL file-manager from PowerShell alone", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-wsl-editors-" }); yield* fs.makeDirectory(`${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0`, { recursive: true, }); yield* writeExecutable(`${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`); const calls: Array<SpawnCall> = []; const open = yield* runOpen( { platform: "linux", env: { PATH: `${dir}:${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0`, WSL_DISTRO_NAME: "Ubuntu", }, isWsl: true, isInsideContainer: false, powerShellCommand: `${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`, }, spawnerLayer(calls), readOpen, ); const editors = yield* open.getAvailableEditors; assert.equal(editors.includes("file-manager"), false); assert.deepEqual(calls, []); }).pipe(Effect.provide(NodeFileSystem.layer)), ); it.effect("openInEditor uses --goto for editors that support it", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-cursor-" }); yield* writeExecutable(`${dir}/cursor`); const calls: Array<SpawnCall> = []; const open = yield* runOpen( { platform: "darwin", env: { PATH: dir }, }, spawnerLayer(calls), readOpen, ); yield* open.openInEditor({ cwd: "/tmp/workspace/src/open.ts:71:5", editor: "cursor", }); assert.deepEqual(calls, [ { command: `${dir}/cursor`, args: ["--goto", "/tmp/workspace/src/open.ts:71:5"], detached: true, shell: false, stdin: "ignore", stdout: "ignore", stderr: "ignore", }, ]); }).pipe(Effect.provide(NodeFileSystem.layer)), ); it.effect("openInEditor launches Windows batch shims through cmd.exe without shell mode", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-vscode-win32-" }); yield* fs.writeFileString(`${dir}/code.cmd`, "@echo off\r\n"); const calls: Array<SpawnCall> = []; const open = yield* runOpen( { platform: "win32", env: { PATH: dir, PATHEXT: ".COM;.EXE;.BAT;.CMD", }, }, spawnerLayer(calls), readOpen, ); yield* open.openInEditor({ cwd: "C:\\work\\100% real\\file.ts:12:4", editor: "vscode", }); assert.equal(calls.length, 1); assert.equal(calls[0]?.command, "cmd.exe"); assert.deepEqual(calls[0]?.args.slice(0, 4), ["/d", "/v:off", "/s", "/c"]); assert.equal(calls[0]?.args[4]?.toLowerCase().includes(`${dir}/code.cmd`.toLowerCase()), true); assert.equal(calls[0]?.args[4]?.includes('"--goto"'), true); assert.equal(calls[0]?.args[4]?.includes("100%% real"), true); assert.equal(calls[0]?.detached, true); assert.equal(calls[0]?.shell, false); assert.equal(calls[0]?.stdin, "ignore"); assert.equal(calls[0]?.stdout, "ignore"); assert.equal(calls[0]?.stderr, "ignore"); }).pipe(Effect.provide(NodeFileSystem.layer)), ); it.effect("openInEditor detached launches survive service scope shutdown", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-zed-detached-" }); yield* writeExecutable(`${dir}/zed`); const calls: Array<SpawnCall> = []; const exitDeferred = yield* Deferred.make<void>(); let killCount = 0; yield* Effect.acquireUseRelease( Scope.make("sequential"), (scope) => Effect.gen(function* () { const runtimeServices = yield* Layer.build( provideOpen( { platform: "darwin", env: { PATH: dir }, }, spawnerLayer(calls, () => ({ awaitExit: exitDeferred, onKill: () => { killCount += 1; }, })), ), ).pipe(Scope.provide(scope)); const open = yield* readOpen.pipe(Effect.provide(runtimeServices), Scope.provide(scope)); yield* open.openInEditor({ cwd: "/tmp/workspace", editor: "zed", }); }), (scope) => Scope.close(scope, Exit.void), ); assert.equal(killCount, 0); assert.deepEqual(calls, [ { command: `${dir}/zed`, args: ["/tmp/workspace"], detached: true, shell: false, stdin: "ignore", stdout: "ignore", stderr: "ignore", }, ]); yield* Deferred.succeed(exitDeferred, void 0); yield* Effect.yieldNow; assert.equal(killCount, 0); }).pipe(Effect.provide(NodeFileSystem.layer)), ); it.effect("openInEditor uses the default opener for file-manager on macOS", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-macos-" }); yield* writeExecutable(`${dir}/open`); const calls: Array<SpawnCall> = []; const open = yield* runOpen( { platform: "darwin", env: { PATH: dir }, }, spawnerLayer(calls), readOpen, ); yield* open.openInEditor({ cwd: "/tmp/workspace", editor: "file-manager", }); assert.deepEqual(calls, [ { command: `${dir}/open`, args: ["/tmp/workspace"], detached: undefined, shell: false, stdin: undefined, stdout: undefined, stderr: undefined, }, ]); }).pipe(Effect.provide(NodeFileSystem.layer)), ); it.effect("openBrowser uses macOS open flags and app arguments", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-browser-macos-" }); yield* writeExecutable(`${dir}/open`); const calls: Array<SpawnCall> = []; const open = yield* runOpen( { platform: "darwin", env: { PATH: dir }, }, spawnerLayer(calls), readOpen, ); yield* open.openBrowser("https://example.com", { wait: true, background: true, newInstance: true, app: { name: "google chrome", arguments: ["--profile-directory=Work"], }, }); assert.deepEqual(calls, [ { command: `${dir}/open`, args: [ "--wait-apps", "--background", "--new", "-a", "google chrome", "https://example.com", "--args", "--profile-directory=Work", ], detached: undefined, shell: false, stdin: undefined, stdout: undefined, stderr: undefined, }, ]); }).pipe(Effect.provide(NodeFileSystem.layer)), ); it.effect("openBrowser uses PowerShell on win32", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-browser-win32-" }); yield* fs.writeFileString(`${dir}/powershell.exe`, "MZ"); const calls: Array<SpawnCall> = []; const open = yield* runOpen( { platform: "win32", env: { PATH: dir, PATHEXT: ".COM;.EXE;.BAT;.CMD", }, }, spawnerLayer(calls), readOpen, ); yield* open.openBrowser("https://example.com"); assert.equal(calls.length, 1); assert.equal(calls[0]?.command, `${dir}/powershell.exe`); assert.equal(calls[0]?.args.at(-2), "-EncodedCommand"); assert.equal( decodePowerShellCommand(calls[0]?.args.at(-1) ?? ""), "Start 'https://example.com'", ); }).pipe(Effect.provide(NodeFileSystem.layer)), ); it.effect("openBrowser falls back from WSL PowerShell to xdg-open", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-browser-wsl-" }); yield* fs.makeDirectory(`${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0`, { recursive: true, }); yield* writeExecutable(`${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`); yield* writeExecutable(`${dir}/xdg-open`); const calls: Array<SpawnCall> = []; const open = yield* runOpen( { platform: "linux", env: { PATH: `${dir}:${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0`, WSL_DISTRO_NAME: "Ubuntu", }, isWsl: true, isInsideContainer: false, powerShellCommand: `${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`, }, spawnerLayer(calls, (call) => call.command.includes("powershell") ? { fail: "powershell unavailable" } : { code: 0, }, ), readOpen, ); yield* open.openBrowser("https://example.com"); assert.equal(calls[0]?.command.includes("powershell"), true); assert.equal(calls[1]?.command, `${dir}/xdg-open`); assert.deepEqual(calls[1]?.args, ["https://example.com"]); }).pipe(Effect.provide(NodeFileSystem.layer)), ); it.effect("openInEditor uses xdg-open first for WSL file-manager paths", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-wsl-file-manager-" }); yield* fs.makeDirectory(`${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0`, { recursive: true, }); yield* writeExecutable(`${dir}/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`); yield* writeExecutable(`${dir}/xdg-open`); const calls: Array<SpawnCall> = [];