835 lines · 24.4 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 {  make as makeDesktopLauncher,  type DetachedSpawnInput,  type LaunchRuntimeOptions,} from "./DesktopLauncher";import { DesktopLauncher, DesktopLauncherSpawnError } from "../Services/DesktopLauncher"; const encoder = new TextEncoder(); interface SpawnCall {  readonly command: string;  readonly args: ReadonlyArray<string>;  readonly detached?: boolean | undefined;  readonly shell?: boolean | string | undefined;  readonly windowsVerbatimArguments?: boolean | undefined;  readonly windowsHide?: boolean | 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,        }),      );    }),  );} function spawnHarness(  calls: Array<SpawnCall>,  handler: (call: SpawnCall) => {    readonly code?: number;    readonly fail?: string;    readonly awaitExit?: Deferred.Deferred<void>;    readonly onKill?: () => void;  } = () => ({    code: 0,  }),) {  return {    layer: spawnerLayer(calls, handler),    spawnDetached: (      input: DetachedSpawnInput,      context: {        readonly operation: string;        readonly target?: string;        readonly editor?: string;      },    ) => {      const call: SpawnCall = {        command: input.command,        args: [...input.args],        detached: input.detached,        shell: input.shell,        windowsVerbatimArguments: input.windowsVerbatimArguments,        windowsHide: input.windowsHide,        stdin: input.stdin,        stdout: input.stdout,        stderr: input.stderr,      };      calls.push(call);       const result = handler(call);      return result.fail        ? Effect.fail(            new DesktopLauncherSpawnError({              operation: context.operation,              command: input.command,              args: [...input.args],              ...(context.target !== undefined ? { target: context.target } : {}),              ...(context.editor !== undefined ? { editor: context.editor } : {}),            }),          )        : Effect.void;    },  } as const;} const provideOpen = (  options: LaunchRuntimeOptions,  harness: {    readonly layer: Layer.Layer<ChildProcessSpawner.ChildProcessSpawner>;    readonly spawnDetached: (      input: DetachedSpawnInput,      context: {        readonly operation: string;        readonly target?: string;        readonly editor?: string;      },    ) => Effect.Effect<void, DesktopLauncherSpawnError>;  },) =>  Layer.effect(    DesktopLauncher,    makeDesktopLauncher({ ...options, spawnDetached: harness.spawnDetached }),  ).pipe(Layer.provide(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer, harness.layer))); const runOpen = <A>(  options: LaunchRuntimeOptions,  harness: {    readonly layer: Layer.Layer<ChildProcessSpawner.ChildProcessSpawner>;    readonly spawnDetached: (      input: DetachedSpawnInput,      context: {        readonly operation: string;        readonly target?: string;        readonly editor?: string;      },    ) => Effect.Effect<void, DesktopLauncherSpawnError>;  },  effect: Effect.Effect<A, never, DesktopLauncher>,) => effect.pipe(Effect.provide(provideOpen(options, harness))); const readOpen = Effect.service(DesktopLauncher); 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",        },      },      spawnHarness(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`,      },      spawnHarness(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 },      },      spawnHarness(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 uses explorer with a VS Code protocol target on Windows", () =>  Effect.gen(function* () {    const fs = yield* FileSystem.FileSystem;    const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-vscode-win32-" });    yield* fs.writeFileString(`${dir}/explorer.exe`, "MZ");     const calls: Array<SpawnCall> = [];    const open = yield* runOpen(      {        platform: "win32",        env: {          PATH: dir,          PATHEXT: ".COM;.EXE;.BAT;.CMD",        },      },      spawnHarness(calls),      readOpen,    );     yield* open.openInEditor({      cwd: "C:\\work\\100% real\\file.ts:12:4",      editor: "vscode",    });     assert.deepEqual(calls, [      {        command: `${dir}/explorer.exe`,        args: ["vscode://file/C:/work/100%25%20real/file.ts:12:4"],        detached: true,        shell: false,        stdin: "ignore",        stdout: "ignore",        stderr: "ignore",      },    ]);  }).pipe(Effect.provide(NodeFileSystem.layer)),); it.effect("openInEditor detached launches bypass the scoped child-process spawner", () =>  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 harness = spawnHarness(calls);     yield* Effect.acquireUseRelease(      Scope.make("sequential"),      (scope) =>        Effect.gen(function* () {          const runtimeServices = yield* Layer.build(            provideOpen(              {                platform: "darwin",                env: { PATH: dir },              },              harness,            ),          ).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.deepEqual(calls, [      {        command: `${dir}/zed`,        args: ["/tmp/workspace"],        detached: true,        shell: false,        stdin: "ignore",        stdout: "ignore",        stderr: "ignore",      },    ]);  }).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 },      },      spawnHarness(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 },      },      spawnHarness(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",        },      },

Lines 1–500 of 835.