558 lines · 16.2 KB · TypeScript
RawDownload
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.