788 lines · 27.4 KB · TypeScript
RawDownload
// @effect-diagnostics nodeBuiltinImport:offimport { expect, it } from "@effect/vitest";import * as NodeChildProcess from "node:child_process";import * as NodeFS from "node:fs";import * as NodeServices from "@effect/platform-node/NodeServices";import * as NodeOS from "node:os";import * as NodePath from "node:path";import { ProviderDriverKind, ProviderInstanceId, type ServerProvider } from "@t3tools/contracts";import { HostProcessPlatform } from "@t3tools/shared/hostProcess";import * as Crypto from "effect/Crypto";import * as Effect from "effect/Effect";import * as Sink from "effect/Sink";import * as Stream from "effect/Stream";import { HttpClient } from "effect/unstable/http";import { ChildProcessSpawner } from "effect/unstable/process";import {  createProviderVersionAdvisory,  enrichProviderSnapshotWithVersionAdvisory,  homebrewOwnershipFromCommandPath,  makeCachedProviderMaintenanceResolution,  makePackageManagedProviderMaintenanceResolver,  makeProviderMaintenanceCapabilities,  normalizeCommandPath,  npmGlobalPrefixFromCommandPath,  parseHomebrewLatestVersion,  ProviderVersionCache,  resolveLatestProviderVersion,  resolvePackageManagedProviderMaintenance,  resolveProviderMaintenanceCapabilitiesEffect,  type ProviderMaintenanceCapabilities,} from "./providerMaintenance.ts";import { symlinksSupported } from "@t3tools/shared/testing/symlinks"; const driver = (value: string) => ProviderDriverKind.make(value);// These write `#!/bin/sh` stubs and evaluate them with darwin/linux path// semantics; a Windows temp path cannot be split on `:`.const windowsHost = HostProcessPlatform.defaultValue() === "win32";const makeTempDir = (name: string) =>  Crypto.Crypto.pipe(    Effect.flatMap((crypto) => crypto.randomUUIDv4),    Effect.map((id) => NodePath.join(NodeOS.tmpdir(), `${name}-${id}`)),  );const isNativeTestCommandPath =  (expectedPathSegment: string) =>  (commandPath: string): boolean =>    normalizeCommandPath(commandPath).includes(expectedPathSegment);const packageToolUpdate = makePackageManagedProviderMaintenanceResolver({  provider: driver("packageTool"),  npmPackageName: "@example/package-tool",  nativeUpdate: null,});const nativePackageToolUpdate = makePackageManagedProviderMaintenanceResolver({  provider: driver("nativePackageTool"),  npmPackageName: "@example/native-package-tool",  nativeUpdate: {    args: ["update"],    isCommandPath: isNativeTestCommandPath("/.local/bin/native-package-tool"),  },});const installedPackageToolProvider: ServerProvider = {  instanceId: ProviderInstanceId.make("packageTool"),  driver: driver("packageTool"),  enabled: true,  installed: true,  version: "1.0.0",  status: "ready",  auth: { status: "authenticated" },  checkedAt: "2026-04-10T00:00:00.000Z",  models: [],  slashCommands: [],  skills: [],};const manualPackageTool: ProviderMaintenanceCapabilities = {  provider: driver("packageTool"),  packageName: "@example/package-tool",  update: null,}; function writeExecutable(path: string) {  NodeFS.mkdirSync(NodePath.dirname(path), { recursive: true });  NodeFS.writeFileSync(path, "#!/bin/sh\n");  NodeFS.chmodSync(path, 0o755);} /** Symlink `<tempDir>/bin/<name>` into a package entry point, like npm/pnpm do. */function linkIntoPackage(tempDir: string, name: string, packageSegments: ReadonlyArray<string>) {  const target = NodePath.join(tempDir, ...packageSegments, "bin", `${name}.js`);  writeExecutable(target);  const link = NodePath.join(tempDir, "bin", name);  NodeFS.mkdirSync(NodePath.dirname(link), { recursive: true });  NodeFS.symlinkSync(target, link);  return link;} const noSpawn = ChildProcessSpawner.make(() =>  Effect.die("maintenance resolution should not spawn a process here"),); function stdoutSpawner(onSpawn: (command: string, args: ReadonlyArray<string>) => string) {  return ChildProcessSpawner.make((command) => {    const { command: executable, args } = command as unknown as {      readonly command: string;      readonly args: ReadonlyArray<string>;    };    return Effect.succeed(      ChildProcessSpawner.makeHandle({        pid: ChildProcessSpawner.ProcessId(1),        exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)),        isRunning: Effect.succeed(false),        kill: () => Effect.void,        unref: Effect.succeed(Effect.void),        stdin: Sink.drain,        stdout: Stream.encodeText(Stream.make(onSpawn(executable, args))),        stderr: Stream.empty,        all: Stream.empty,        getInputFd: () => Sink.drain,        getOutputFd: () => Stream.empty,      }),    );  });} it.layer(NodeServices.layer)("providerMaintenance", (it) => {  it.effect("reads cached versions through the injectable cache reference", () =>    resolveLatestProviderVersion(manualPackageTool).pipe(      Effect.provideService(        ProviderVersionCache,        new Map([          [            "@example/package-tool",            {              expiresAt: Number.MAX_SAFE_INTEGER,              version: "9.9.9",            },          ],        ]),      ),      Effect.provideService(        HttpClient.HttpClient,        HttpClient.make(() =>          Effect.die("cached provider version should not make an HTTP request"),        ),      ),      Effect.map((version) => {        expect(version).toBe("9.9.9");      }),    ),  );   it.effect("prefers the installer's own latest version over the npm registry", () =>    resolveLatestProviderVersion({ ...manualPackageTool, latestVersion: "1.2.0" }).pipe(      Effect.provideService(ProviderVersionCache, new Map()),      Effect.provideService(        HttpClient.HttpClient,        HttpClient.make(() =>          Effect.die("installer-reported latest should not make an HTTP request"),        ),      ),      Effect.map((version) => {        expect(version).toBe("1.2.0");      }),    ),  );   it.effect("does not fetch latest provider versions when update checks are disabled", () =>    enrichProviderSnapshotWithVersionAdvisory(installedPackageToolProvider, manualPackageTool, {      enableProviderUpdateChecks: false,    }).pipe(      Effect.provideService(ProviderVersionCache, new Map()),      Effect.provideService(        HttpClient.HttpClient,        HttpClient.make(() =>          Effect.die("disabled provider update checks should not make an HTTP request"),        ),      ),      Effect.map((provider) => {        expect(provider.versionAdvisory).toMatchObject({          status: "unknown",          currentVersion: "1.0.0",          latestVersion: null,          checkedAt: "2026-04-10T00:00:00.000Z",        });      }),    ),  );   it("marks providers with unknown current versions as unknown", () => {    expect(      createProviderVersionAdvisory({        driver: driver("packageTool"),        currentVersion: null,        latestVersion: "9.9.9",      }),    ).toMatchObject({      status: "unknown",      currentVersion: null,      latestVersion: "9.9.9",    });  });   it("marks providers with unknown latest versions as unknown", () => {    expect(      createProviderVersionAdvisory({        driver: driver("packageTool"),        currentVersion: "1.0.0",        latestVersion: null,      }),    ).toMatchObject({      status: "unknown",      currentVersion: "1.0.0",      latestVersion: null,      message: null,    });  });   it("keeps the manual update hint when the install is behind but unowned", () => {    expect(      createProviderVersionAdvisory({        driver: driver("packageTool"),        currentVersion: "2.1.110",        latestVersion: "2.1.117",        maintenanceCapabilities: manualPackageTool,      }),    ).toMatchObject({      status: "behind_latest",      latestVersion: "2.1.117",      updateCommand: null,      canUpdate: false,      message: "Install the update now or review provider settings.",    });  });   it.effect("stays manual-only when the binary cannot be located", () =>    resolveProviderMaintenanceCapabilitiesEffect(packageToolUpdate, {      binaryPath: "package-tool",      env: { PATH: "" },    }).pipe(      Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, noSpawn),      Effect.map((capabilities) => {        expect(capabilities).toEqual(manualPackageTool);      }),    ),  );   it.effect.skipIf(!symlinksSupported)(    "pins npm updates to the global prefix that owns the package",    () =>      Effect.gen(function* () {        const tempDir = yield* makeTempDir("t3-npm-capabilities");        const link = linkIntoPackage(tempDir, "package-tool", [          "lib",          "node_modules",          "@example",          "package-tool",        ]);         const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(          packageToolUpdate,          {            binaryPath: link,            env: { PATH: "" },          },        ).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, noSpawn));         expect(capabilities).toEqual({          provider: driver("packageTool"),          packageName: "@example/package-tool",          update: {            command: `npm install -g --prefix ${tempDir} --allow-scripts=@example/package-tool @example/package-tool@latest`,            executable: "npm",            args: [              "install",              "-g",              "--prefix",              tempDir,              "--allow-scripts=@example/package-tool",              "@example/package-tool@latest",            ],            lockKey: `npm-global:${normalizeCommandPath(tempDir)}`,          },        });      }),  );   it("derives the npm prefix only from the global lib/node_modules layout", () => {    expect(      npmGlobalPrefixFromCommandPath(        "/usr/local/lib/node_modules/@openai/codex/bin/codex.js",        "@openai/codex",      ),    ).toBe("/usr/local");    // A copy nested inside another package is not a global install.    expect(      npmGlobalPrefixFromCommandPath(        "/usr/local/lib/node_modules/other/node_modules/@openai/codex/bin/codex.js",        "@openai/codex",      ),    ).toBeNull();    expect(      npmGlobalPrefixFromCommandPath(        "/lib/node_modules/@openai/codex/bin/codex.js",        "@openai/codex",      ),    ).toBe("/");    // Neither is a project-local dependency.    expect(      npmGlobalPrefixFromCommandPath(        "/work/app/node_modules/@openai/codex/bin/codex.js",        "@openai/codex",      ),    ).toBeNull();  });   // The Codex Windows installer exposes `%LOCALAPPDATA%\\Programs\\OpenAI\\Codex\\bin`  // as a junction into `%CODEX_HOME%\\packages\\standalone\\current\\bin`. Node's  // realpath follows junctions, so the real path carries the standalone marker  // even though the visible path does not.  it.effect("recognizes a Windows standalone install through its junctioned bin dir", () =>    Effect.gen(function* () {      const visiblePath =        "C:\\Users\\Theo\\AppData\\Local\\Programs\\OpenAI\\Codex\\bin\\codex.exe";      const realPath =        "C:\\Users\\Theo\\.codex\\packages\\standalone\\releases\\0.120.0-x86_64\\bin\\codex.exe";      const capabilities = yield* resolvePackageManagedProviderMaintenance(        {          provider: driver("codex"),          npmPackageName: "@openai/codex",          nativeUpdate: {            args: ["update"],            isCommandPath: isNativeTestCommandPath("/packages/standalone/"),          },        },        {          binaryPath: "codex",          resolvedCommandPath: visiblePath,          realCommandPath: realPath,          env: {},          platform: "win32",        },      ).pipe(Effect.provideService(HostProcessPlatform, "win32"));       expect(capabilities.update).toMatchObject({        executable: visiblePath,        args: ["update"],        lockKey: "codex-native",      });    }),  );   it.effect("proves Windows npm ownership from the package manifest beside the shim", () =>    Effect.gen(function* () {      const tempDir = yield* makeTempDir("t3-npm-windows-capabilities");      const shim = NodePath.join(tempDir, "package-tool.cmd");      NodeFS.mkdirSync(tempDir, { recursive: true });      NodeFS.writeFileSync(shim, "@echo off\r\n");      NodeFS.mkdirSync(NodePath.join(tempDir, "node_modules", "@example", "package-tool"), {        recursive: true,      });      NodeFS.writeFileSync(        NodePath.join(tempDir, "node_modules", "@example", "package-tool", "package.json"),        "{}",      );       const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(packageToolUpdate, {        binaryPath: shim,        env: { PATH: "", PATHEXT: ".COM;.EXE;.BAT;.CMD" },      }).pipe(        Effect.provideService(HostProcessPlatform, "win32"),        Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, noSpawn),      );       expect(capabilities.update).toMatchObject({        executable: "npm",        args: ["install", "-g", "--prefix", tempDir, expect.any(String), expect.any(String)],      });       // The same layout on POSIX is a project checkout, not a global install.      const script = NodePath.join(tempDir, "package-tool");      writeExecutable(script);      const posix = yield* resolveProviderMaintenanceCapabilitiesEffect(packageToolUpdate, {        binaryPath: script,        env: { PATH: "" },      }).pipe(        Effect.provideService(HostProcessPlatform, "linux"),        Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, noSpawn),      );      expect(posix.update).toBeNull();    }),  );   it.effect.skipIf(!symlinksSupported)(    "switches to pnpm updates when the real path lives in pnpm's global store",    () =>      Effect.gen(function* () {        const tempDir = yield* makeTempDir("t3-pnpm-capabilities");        const link = linkIntoPackage(tempDir, "package-tool", [          ".local",          "share",          "pnpm",          "global",          "5",          "node_modules",          "@example",          "package-tool",        ]);         const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(          packageToolUpdate,          {            binaryPath: link,            env: { PATH: "" },          },        ).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, noSpawn));         expect(capabilities.update).toMatchObject({          command: "pnpm add -g @example/package-tool@latest",          lockKey: "pnpm-global",        });      }),  );   it.effect.skipIf(windowsHost)(    "switches to bun updates when the resolved binary lives in bun's global bin",    () =>      Effect.gen(function* () {        const tempDir = yield* makeTempDir("t3-bun-capabilities");        const bunBinDir = NodePath.join(tempDir, ".bun", "bin");        writeExecutable(NodePath.join(bunBinDir, "package-tool"));         const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(          packageToolUpdate,          {            binaryPath: "package-tool",            env: { PATH: bunBinDir },          },        ).pipe(          Effect.provideService(HostProcessPlatform, "darwin"),          Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, noSpawn),        );         expect(capabilities.update).toMatchObject({          command: "bun i -g @example/package-tool@latest",          lockKey: "bun-global",        });      }),  );   it.effect.skipIf(windowsHost)("switches to native updates and runs the resolved executable", () =>    Effect.gen(function* () {      const tempDir = yield* makeTempDir("t3-native-capabilities");      const nativeBinDir = NodePath.join(tempDir, ".local", "bin");      const nativePath = NodePath.join(nativeBinDir, "native-package-tool");      writeExecutable(nativePath);       const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(        nativePackageToolUpdate,        {          binaryPath: "native-package-tool",          env: { PATH: nativeBinDir },        },      ).pipe(        Effect.provideService(HostProcessPlatform, "darwin"),        Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, noSpawn),      );       expect(capabilities).toEqual({        provider: driver("nativePackageTool"),        packageName: "@example/native-package-tool",        update: {          command: `${nativePath} update`,          executable: nativePath,          args: ["update"],          lockKey: "nativePackageTool-native",        },      });    }),  );   // Regression for #9850: an explicit native path outside PATH, with spaces,  // must be what actually gets spawned.  it.effect.skipIf(windowsHost)("runs an explicit native updater outside PATH", () =>    Effect.gen(function* () {      const tempDir = yield* makeTempDir("t3-native-update");      const nativePath = NodePath.join(        tempDir,        "with spaces",        ".local",        "bin",        "native-package-tool",      );      NodeFS.mkdirSync(NodePath.dirname(nativePath), { recursive: true });      NodeFS.writeFileSync(nativePath, "#!/bin/sh\nprintf '%s' \"$1\"\n");      NodeFS.chmodSync(nativePath, 0o755);       const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(        nativePackageToolUpdate,        { binaryPath: nativePath, env: { PATH: "" } },      ).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, noSpawn));       expect(capabilities.update?.executable).toBe(nativePath);

Lines 1–500 of 788.