970 lines · 36.8 KB · TypeScript
RawDownload
// @effect-diagnostics nodeBuiltinImport:off - the Windows reveal smoke test drives a real PowerShell through Node process and filesystem APIs.import * as NodeChildProcess from "node:child_process";import * as NodeFS from "node:fs";import * as NodeOS from "node:os";import * as NodePath from "node:path";import * as NodeServices from "@effect/platform-node/NodeServices";import { assert, it } from "@effect/vitest";import * as ConfigProvider from "effect/ConfigProvider";import * as Effect from "effect/Effect";import * as Fiber from "effect/Fiber";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Path from "effect/Path";import * as Sink from "effect/Sink";import * as Stream from "effect/Stream";import * as TestClock from "effect/testing/TestClock";import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { HostProcessPlatform } from "@t3tools/shared/hostProcess";import { SpawnExecutableResolution } from "@t3tools/shared/shell";import * as ExternalLauncher from "./externalLauncher.ts"; // Tests below write `#!/bin/sh` stubs into a real temp dir and hand that// directory to a posix-mocked resolver as PATH. On a Windows host the temp// path carries a drive letter, so the posix `:` split shatters it; there is// no posix executable to find there anyway.const windowsHost = HostProcessPlatform.defaultValue() === "win32"; interface MockSpawnResult {  readonly exitCode?: number;  readonly stdout?: string;  /** Never deliver an exit code, like a child wedged on a broken desktop session. */  readonly stall?: boolean;} function makeMockDetachedHandle(input: MockSpawnResult & { readonly onUnref?: () => void } = {}) {  return ChildProcessSpawner.makeHandle({    pid: ChildProcessSpawner.ProcessId(1),    exitCode: input.stall      ? Effect.never      : Effect.succeed(ChildProcessSpawner.ExitCode(input.exitCode ?? 0)),    isRunning: Effect.succeed(true),    kill: () => Effect.void,    unref: Effect.sync(() => {      input.onUnref?.();      return Effect.void;    }),    stdin: Sink.drain,    stdout:      input.stdout === undefined        ? Stream.empty        : Stream.make(new TextEncoder().encode(input.stdout)),    stderr: Stream.empty,    all: Stream.empty,    getInputFd: () => Sink.drain,    getOutputFd: () => Stream.empty,  });} const testLayer = (input: {  readonly platform: NodeJS.Platform;  readonly env?: Record<string, string>;  readonly resolveExecutable?: (command: string) => string | undefined;  readonly onSpawn?: (command: ChildProcess.StandardCommand) => void;  readonly onUnref?: () => void;  readonly spawnResult?: (command: ChildProcess.StandardCommand) => MockSpawnResult | undefined;}) => {  const spawnerLayer = Layer.succeed(    ChildProcessSpawner.ChildProcessSpawner,    ChildProcessSpawner.make((command) =>      Effect.sync(() => {        assert.equal(ChildProcess.isStandardCommand(command), true);        if (!ChildProcess.isStandardCommand(command)) {          throw new Error("Expected a standard command");        }        input.onSpawn?.(command);        return makeMockDetachedHandle({          ...(input.onUnref === undefined ? {} : { onUnref: input.onUnref }),          ...input.spawnResult?.(command),        });      }),    ),  );   return Layer.mergeAll(    ExternalLauncher.layer.pipe(Layer.provide(Layer.merge(NodeServices.layer, spawnerLayer))),    Layer.succeed(HostProcessPlatform, input.platform),    Layer.succeed(      SpawnExecutableResolution,      (command) => input.resolveExecutable?.(command) ?? command,    ),    ConfigProvider.layer(ConfigProvider.fromEnv({ env: input.env ?? {} })),  );}; it.effect("launches the default browser through the platform command", () => {  let spawned: ChildProcess.StandardCommand | undefined;  let didUnref = false;  return Effect.gen(function* () {    const launcher = yield* ExternalLauncher.ExternalLauncher;     yield* launcher.launchBrowser("https://example.com/some path");     assert.ok(spawned);    assert.equal(spawned.command, "xdg-open");    assert.deepEqual(spawned.args, ["https://example.com/some path"]);    assert.equal(spawned.options.detached, true);    assert.equal(didUnref, true);  }).pipe(    Effect.provide(      testLayer({        platform: "linux",        onSpawn: (command) => {          spawned = command;        },        onUnref: () => {          didUnref = true;        },      }),    ),  );}); it.effect("launches an installed editor with platform-safe arguments", () =>  Effect.gen(function* () {    const fileSystem = yield* FileSystem.FileSystem;    const path = yield* Path.Path;    const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });    yield* fileSystem.writeFileString(path.join(binDir, "code.CMD"), "@echo off\r\n");     let spawned: ChildProcess.StandardCommand | undefined;    yield* Effect.gen(function* () {      const launcher = yield* ExternalLauncher.ExternalLauncher;      yield* launcher.launchEditor({        editor: "vscode",        cwd: "C:\\workspace with spaces\\src\\index.ts:12:4",      });    }).pipe(      Effect.provide(        testLayer({          platform: "win32",          env: { PATH: binDir, PATHEXT: ".COM;.EXE;.BAT;.CMD" },          resolveExecutable: (command) =>            command === "code" ? "C:\\Program Files\\Microsoft VS Code\\bin\\code.CMD" : command,          onSpawn: (command) => {            spawned = command;          },        }),      ),    );     assert.ok(spawned);    assert.equal(spawned.command, '^"C:\\Program^ Files\\Microsoft^ VS^ Code\\bin\\code.CMD^"');    assert.deepEqual(spawned.args, [      '^"--goto^"',      '^"C:\\workspace^ with^ spaces\\src\\index.ts:12:4^"',    ]);    assert.equal(spawned.options.shell, true);  }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),); it.effect.skipIf(windowsHost)("reveals a file in Finder with open -R on macOS", () =>  Effect.gen(function* () {    const fileSystem = yield* FileSystem.FileSystem;    const path = yield* Path.Path;    const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });    const openPath = path.join(binDir, "open");    yield* fileSystem.writeFileString(openPath, "#!/bin/sh\n");    yield* fileSystem.chmod(openPath, 0o755);     let spawned: ChildProcess.StandardCommand | undefined;    yield* Effect.gen(function* () {      const launcher = yield* ExternalLauncher.ExternalLauncher;      yield* launcher.launchEditor({        editor: "file-manager",        cwd: "/workspace/media/linux-mini-v2.mp4",        reveal: true,      });    }).pipe(      Effect.provide(        testLayer({          platform: "darwin",          env: { PATH: binDir },          onSpawn: (command) => {            spawned = command;          },        }),      ),    );     assert.ok(spawned);    assert.equal(spawned.command, "open");    assert.deepEqual(spawned.args, ["-R", "/workspace/media/linux-mini-v2.mp4"]);  }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),); it.effect("reveals a file in File Explorer through PowerShell on Windows", () =>  Effect.gen(function* () {    const fileSystem = yield* FileSystem.FileSystem;    const path = yield* Path.Path;    const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });    yield* fileSystem.writeFileString(path.join(binDir, "explorer.CMD"), "@echo off\r\n");    // resolvePowerShellPath builds `${SYSTEMROOT}\System32\...` with Windows    // separators, which on the posix test filesystem is one file name.    const systemRoot = path.join(binDir, "system-root");    const powerShellPath = `${systemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;    yield* fileSystem.makeDirectory(path.dirname(powerShellPath), { recursive: true });    yield* fileSystem.writeFileString(powerShellPath, "");     let spawned: ChildProcess.StandardCommand | undefined;    const kind = yield* Effect.gen(function* () {      const launcher = yield* ExternalLauncher.ExternalLauncher;      yield* launcher.launchEditor({        editor: "file-manager",        // Web file links normalize separators even when the server runs on        // Windows. Explorer's `/select` switch requires Windows separators.        cwd: "C:/workspace with spaces/media/author's clip.mp4",        reveal: true,      });      return yield* launcher.resolveFileManagerRevealKind();    }).pipe(      Effect.provide(        testLayer({          platform: "win32",          env: { PATH: binDir, PATHEXT: ".COM;.EXE;.BAT;.CMD", SYSTEMROOT: systemRoot },          onSpawn: (command) => {            spawned = command;          },        }),      ),    );     assert.equal(kind, "file-explorer");    assert.ok(spawned);    assert.equal(spawned.command, powerShellPath);    assert.deepEqual(spawned.args.slice(0, -1), [      "-NoProfile",      "-NonInteractive",      "-ExecutionPolicy",      "Bypass",      "-EncodedCommand",    ]);    const encodedCommand = spawned.args[spawned.args.length - 1] ?? "";    const decodedCommand = Buffer.from(encodedCommand, "base64").toString("utf16le");    // explorer.exe expects `/select,"<path>"` with only the path quoted;    // PowerShell 5.1's Start-Process passes the argument string verbatim.    assert.equal(      decodedCommand,      "$ProgressPreference = 'SilentlyContinue'; Start-Process 'explorer.exe' -ArgumentList ('/select,\"' + 'C:\\workspace with spaces\\media\\author''s clip.mp4' + '\"')",    );    assert.equal(spawned.options.shell, false);  }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),); // Real-chain smoke check for the Explorer selection contract: runs the exact// PowerShell source the reveal launch encodes, against a stub that records// the raw argument tail it receives, and asserts a spaced path arrives as the// single `/select,"<path>"` switch. Mock argv assertions cannot prove this —// only Windows' own PowerShell -> CreateProcess quoting chain can, so the// test runs only where that chain exists.// oxlint-disable-next-line t3code/no-global-process-runtime -- the skip decision needs the real host platform, outside any Effect runtime.it.skipIf(process.platform !== "win32")(  "delivers the raw /select switch for spaced paths through real PowerShell",  { timeout: 60_000 },  async () => {    const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-reveal-smoke-"));    try {      const recorderPath = NodePath.join(tempDir, "recorder.cmd");      const outputPath = NodePath.join(tempDir, "argv.txt");      NodeFS.writeFileSync(recorderPath, `@echo off\r\n>"${outputPath}" echo(%*\r\n`);       const target = "C:/workspace with spaces/media/author's clip.mp4";      const explorerTarget = target.replaceAll("/", "\\");      const source = ExternalLauncher.buildFileExplorerRevealPowerShellSource(        recorderPath,        explorerTarget,      );      const powerShellPath = `${process.env.SYSTEMROOT ?? "C:\\Windows"}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;      NodeChildProcess.execFileSync(        powerShellPath,        [          "-NoProfile",          "-NonInteractive",          "-ExecutionPolicy",          "Bypass",          "-EncodedCommand",          Buffer.from(source, "utf16le").toString("base64"),        ],        { timeout: 30_000 },      );       // Start-Process returns before the recorder runs; wait for its output.      // The waits run outside the Effect runtime on purpose: the test      // exercises the real Windows process chain in real time.      // @effect-diagnostics-next-line globalTimers:off      const sleep = (millis: number) => new Promise((resolve) => setTimeout(resolve, millis));      // @effect-diagnostics-next-line globalDate:off      const deadline = Date.now() + 20_000;      // @effect-diagnostics-next-line globalDate:off      while (!NodeFS.existsSync(outputPath) && Date.now() < deadline) {        await sleep(100);      }      await sleep(200);      const recorded = NodeFS.readFileSync(outputPath, "utf8").trim();      assert.equal(recorded, `/select,"${explorerTarget}"`);    } finally {      NodeFS.rmSync(tempDir, { recursive: true, force: true });    }  },); it.effect("does not advertise reveal on Windows when PowerShell is missing", () =>  Effect.gen(function* () {    const fileSystem = yield* FileSystem.FileSystem;    const path = yield* Path.Path;    const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });    yield* fileSystem.writeFileString(path.join(binDir, "explorer.CMD"), "@echo off\r\n");     const result = yield* Effect.gen(function* () {      const launcher = yield* ExternalLauncher.ExternalLauncher;      return {        kind: yield* launcher.resolveFileManagerRevealKind(),        editors: yield* launcher.resolveAvailableEditors(),      };    }).pipe(      Effect.provide(        testLayer({          platform: "win32",          env: {            PATH: binDir,            PATHEXT: ".COM;.EXE;.BAT;.CMD",            SYSTEMROOT: path.join(binDir, "missing-system-root"),          },        }),      ),    );     // Plain "open in file manager" still works through explorer; only the    // reveal capability, which launches PowerShell, must stay hidden.    assert.equal(result.editors.includes("file-manager"), true);    assert.isUndefined(result.kind);  }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),); it.effect.skipIf(windowsHost)(  "reveals a WSL file in Windows File Explorer through its UNC path",  () =>    Effect.gen(function* () {      const fileSystem = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });      for (const name of ["explorer.exe", "powershell.exe", "xdg-open"]) {        const filePath = path.join(binDir, name);        yield* fileSystem.writeFileString(filePath, "#!/bin/sh\n");        yield* fileSystem.chmod(filePath, 0o755);      }       let spawned: ChildProcess.StandardCommand | undefined;      const result = yield* Effect.gen(function* () {        const launcher = yield* ExternalLauncher.ExternalLauncher;        const kind = yield* launcher.resolveFileManagerRevealKind();        const editors = yield* launcher.resolveAvailableEditors();        yield* launcher.launchEditor({          editor: "file-manager",          cwd: "/home/t3/workspace/media/clip.mp4",          reveal: true,        });        return { kind, editors };      }).pipe(        Effect.provide(          testLayer({            platform: "linux",            env: {              PATH: binDir,              WSL_DISTRO_NAME: "Ubuntu-24.04",              WSL_INTEROP: "/run/WSL/1_interop",            },            onSpawn: (command) => {              spawned = command;            },          }),        ),      );       assert.equal(result.kind, "file-explorer");      assert.equal(result.editors.includes("file-manager"), true);      assert.ok(spawned);      // The reveal routes through interop PowerShell so Explorer receives its      // raw `/select,"<path>"` switch even for spaced paths.      assert.equal(spawned.command, "powershell.exe");      const encodedCommand = spawned.args[spawned.args.length - 1] ?? "";      const decodedCommand = Buffer.from(encodedCommand, "base64").toString("utf16le");      assert.equal(        decodedCommand,        "$ProgressPreference = 'SilentlyContinue'; Start-Process 'explorer.exe' -ArgumentList ('/select,\"' + '\\\\wsl.localhost\\Ubuntu-24.04\\home\\t3\\workspace\\media\\clip.mp4' + '\"')",      );    }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),); it.effect.skipIf(windowsHost)(  "does not advertise reveal from WSL when interop PowerShell is missing",  () =>    Effect.gen(function* () {      const fileSystem = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });      const explorerPath = path.join(binDir, "explorer.exe");      yield* fileSystem.writeFileString(explorerPath, "");      yield* fileSystem.chmod(explorerPath, 0o755);       const result = yield* Effect.gen(function* () {        const launcher = yield* ExternalLauncher.ExternalLauncher;        return {          kind: yield* launcher.resolveFileManagerRevealKind(),          editors: yield* launcher.resolveAvailableEditors(),        };      }).pipe(        Effect.provide(          testLayer({            platform: "linux",            env: {              PATH: binDir,              WSL_DISTRO_NAME: "Ubuntu-24.04",              WSL_INTEROP: "/run/WSL/1_interop",            },          }),        ),      );       assert.equal(result.editors.includes("file-manager"), true);      assert.isUndefined(result.kind);    }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),); // When interop PowerShell is missing the capability advertises the Linux// "files" kind (or nothing), so the reveal must open the Linux file manager// the label promised even though plain open still prefers File Explorer.it.effect.skipIf(windowsHost)(  "reveals through the Linux file manager when WSL lacks interop PowerShell",  () =>    Effect.gen(function* () {      const fileSystem = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });      for (const name of ["explorer.exe", "xdg-open", "xdg-mime"]) {        const filePath = path.join(binDir, name);        yield* fileSystem.writeFileString(filePath, "#!/bin/sh\n");        yield* fileSystem.chmod(filePath, 0o755);      }       const spawnedCommands: ChildProcess.StandardCommand[] = [];      const kind = yield* Effect.gen(function* () {        const launcher = yield* ExternalLauncher.ExternalLauncher;        const revealKind = yield* launcher.resolveFileManagerRevealKind();        yield* launcher.launchEditor({          editor: "file-manager",          cwd: "/home/t3/workspace/media/clip.mp4",          reveal: true,        });        return revealKind;      }).pipe(        Effect.provide(          testLayer({            platform: "linux",            env: {              PATH: binDir,              WSL_DISTRO_NAME: "Ubuntu-24.04",              WSL_INTEROP: "/run/WSL/1_interop",              DISPLAY: ":0",            },            onSpawn: (command) => {              spawnedCommands.push(command);            },            spawnResult: (command) =>              command.command === "xdg-mime"                ? { stdout: "org.gnome.Nautilus.desktop\n" }                : undefined,          }),        ),      );       assert.equal(kind, "files");      const launch = spawnedCommands.find((command) => command.command === "xdg-open");      assert.ok(launch);      assert.deepEqual(launch.args, ["/home/t3/workspace/media"]);      assert.isUndefined(spawnedCommands.find((command) => command.command === "explorer.exe"));    }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),); // Interop can exist without `explorer.exe` on PATH (appendWindowsPath=false)// while WSLg still provides a working Linux file manager; the host must keep// the Linux open/reveal path instead of losing the editor entirely.it.effect.skipIf(windowsHost)(  "falls back to the Linux file manager when WSL lacks the Explorer bridge",  () =>    Effect.gen(function* () {      const fileSystem = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });      for (const name of ["xdg-open", "xdg-mime"]) {

Lines 1–500 of 970.