History

Merge remote-tracking branch 'origin/main' into fix/retain-runtime-diagnostic-tail

Julius Marminge committed 2 months ago

Commit e67db5c2 parents 519beab 2271a273 files · +184 −14Browse files

Unified · Split

3 files
  1. apps/server/src/provider/Drivers/CodexDriver.test.ts +151 −0
  2. apps/server/src/provider/providerMaintenance.test.ts +29 −14
  3. apps/server/src/provider/providerMaintenance.ts +4 −0
apps/server/src/provider/Drivers/CodexDriver.test.ts+151−0
@@ -8,7 +8,10 @@ import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; · 7 unmodified lines
88 import * as Effect from "effect/Effect";
99 import * as FileSystem from "effect/FileSystem";
1010 import * as Layer from "effect/Layer";
11+import * as Sink from "effect/Sink";
12+import * as Stream from "effect/Stream";
1113 import { HttpClient } from "effect/unstable/http";
14+import * as ChildProcess from "effect/unstable/process/ChildProcess";
1215 import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";
1316
1417 import * as BackgroundPolicy from "../../background/BackgroundPolicy.ts";
@@ -17,6 +20,11 @@ import { ServerSettingsService } from "../../serverSettings.ts"; · 2 unmodified lines
1720 import { layerTest as codexResetCreditLayerTest } from "../Layers/codexResetCredit.ts";
1821 import { NoOpProviderEventLoggers, ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts";
1922 import * as ModelManifest from "../ModelManifest.ts";
23+import {
24+ createProviderVersionAdvisory,
25+ ProviderVersionCache,
26+ resolveLatestProviderVersion,
27+} from "../providerMaintenance.ts";
2028 import { CodexDriver } from "./CodexDriver.ts";
2129
2230 const testLayer = ServerConfig.layerTest(process.cwd(), {
@@ -218,4 +226,147 @@ it.layer(testLayer)("CodexDriver", (it) => { · 195 unmodified lines
218226 ),
219227 );
220228 }
229+
230+ it.effect.each([
231+ {
232+ name: "conventional shim",
233+ dataRoot: "mise",
234+ commandName: "codex",
235+ version: "0.153.4",
236+ nodeFirst: false,
237+ },
238+ {
239+ name: "custom data directory",
240+ dataRoot: "custom-tool-data",
241+ commandName: "codex",
242+ version: "0.153.4",
243+ nodeFirst: false,
244+ },
245+ {
246+ name: "renamed configured command",
247+ dataRoot: "mise",
248+ commandName: "custom-codex",
249+ version: "0.153.4",
250+ nodeFirst: false,
251+ },
252+ {
253+ name: "outdated provider",
254+ dataRoot: "mise",
255+ commandName: "codex",
256+ version: "0.153.3",
257+ nodeFirst: false,
258+ },
259+ {
260+ name: "npm before shim",
261+ dataRoot: "mise",
262+ commandName: "codex",
263+ version: "0.153.4",
264+ nodeFirst: true,
265+ },
266+ ])(
267+ "does not mistake Homebrew mise for Codex's installer: $name",
268+ (fixture) =>
269+ Effect.gen(function* () {
270+ const fs = yield* FileSystem.FileSystem;
271+ const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-codex-mise-shim-" });
272+ const brewPrefix = NodePath.join(tempDir, "homebrew");
273+ const brewPath = NodePath.join(brewPrefix, "bin", "brew");
274+ const misePath = NodePath.join(brewPrefix, "Cellar", "mise", "2026.9.1", "bin", "mise");
275+ const shimDir = NodePath.join(tempDir, fixture.dataRoot, "shims");
276+ const npmPrefix = NodePath.join(tempDir, "mise", "installs", "node", "24.13.0");
277+ const npmBin = NodePath.join(npmPrefix, "bin");
278+ const npmEntry = NodePath.join(
279+ npmPrefix,
280+ "lib",
281+ "node_modules",
282+ "@openai",
283+ "codex",
284+ "bin",
285+ "codex.js",
286+ );
287+ for (const file of [brewPath, misePath, npmEntry]) {
288+ yield* fs.makeDirectory(NodePath.dirname(file), { recursive: true });
289+ yield* fs.writeFileString(file, "#!/bin/sh\n");
290+ yield* fs.chmod(file, 0o755);
291+ }
292+ yield* fs.makeDirectory(shimDir, { recursive: true });
293+ yield* fs.makeDirectory(npmBin, { recursive: true });
294+ yield* fs.symlink(misePath, NodePath.join(shimDir, fixture.commandName));
295+ yield* fs.symlink(npmEntry, NodePath.join(npmBin, fixture.commandName));
296+ const lookupPath = [
297+ ...(fixture.nodeFirst ? [npmBin, shimDir] : [shimDir, npmBin]),
298+ NodePath.dirname(brewPath),
299+ ].join(NodePath.delimiter);
300+ const probes: Array<ReadonlyArray<string>> = [];
301+ const metadataSpawner = ChildProcessSpawner.make((command) => {
302+ if (!ChildProcess.isStandardCommand(command) || command.command !== brewPath) {
303+ return Effect.die("Provider resolution must not execute a provider or updater");
304+ }
305+ probes.push(command.args);
306+ const stdout =
307+ command.args[0] === "--prefix"
308+ ? brewPrefix
309+ : JSON.stringify({ formulae: [{ versions: { stable: "2026.9.1" } }] });
310+ return Effect.succeed(
311+ ChildProcessSpawner.makeHandle({
312+ pid: ChildProcessSpawner.ProcessId(1),
313+ exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)),
314+ isRunning: Effect.succeed(false),
315+ kill: () => Effect.void,
316+ unref: Effect.succeed(Effect.void),
317+ stdin: Sink.drain,
318+ stdout: Stream.encodeText(Stream.make(stdout)),
319+ stderr: Stream.empty,
320+ all: Stream.empty,
321+ getInputFd: () => Sink.drain,
322+ getOutputFd: () => Stream.empty,
323+ }),
324+ );
325+ });
326+ const instance = yield* CodexDriver.create({
327+ instanceId: ProviderInstanceId.make("codex-mise-shim"),
328+ displayName: "Codex shim test",
329+ enabled: false,
330+ environment: [{ name: "PATH", value: lookupPath, sensitive: false }],
331+ config: {
332+ ...CodexDriver.defaultConfig(),
333+ binaryPath: fixture.commandName,
334+ homePath: NodePath.join(tempDir, "codex-home"),
335+ },
336+ }).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, metadataSpawner));
337+ const capabilities = yield* instance.snapshot.resolveMaintenance();
338+ const latestVersion = yield* resolveLatestProviderVersion(capabilities).pipe(
339+ Effect.provideService(
340+ ProviderVersionCache,
341+ new Map([
342+ ["@openai/codex", { expiresAt: Number.MAX_SAFE_INTEGER, version: "0.153.4" }],
343+ ]),
344+ ),
345+ );
346+ expect(probes).toEqual([]);
347+ expect(latestVersion).toBe("0.153.4");
348+ expect(
349+ createProviderVersionAdvisory({
350+ driver: CodexDriver.driverKind,
351+ currentVersion: fixture.version,
352+ latestVersion,
353+ maintenanceCapabilities: capabilities,
354+ }),
355+ ).toMatchObject({
356+ status: fixture.version === "0.153.4" ? "current" : "behind_latest",
357+ currentVersion: fixture.version,
358+ latestVersion: "0.153.4",
359+ canUpdate: fixture.nodeFirst,
360+ });
361+ if (fixture.nodeFirst) {
362+ expect(capabilities.update).toMatchObject({
363+ executable: "npm",
364+ args: expect.arrayContaining(["--prefix", npmPrefix, "@openai/codex@latest"]),
365+ });
366+ } else {
367+ expect(capabilities.update).toBeNull();
368+ }
369+ }).pipe(Effect.scoped),
370+ { skip: windowsHost },
371+ );
221372 });
apps/server/src/provider/providerMaintenance.test.ts+29−14
@@ -595,25 +595,29 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => { · 594 unmodified lines
595595 }),
596596 );
597597
598− it.effect.skipIf(!symlinksSupported)(
599− "upgrades the Homebrew cask that owns the binary and compares against its version",
600− () =>
598+ it.effect.each([
599+ { directory: "Caskroom", name: "package-tool", kind: "cask" },
600+ { directory: "Cellar", name: "package-tool", kind: "formula" },
601+ { directory: "Cellar", name: "package-tool@latest", kind: "formula" },
602+ ] as const)(
603+ "upgrades the owning Homebrew $kind $name through an executable alias",
604+ (fixture) =>
601605 Effect.gen(function* () {
602606 const tempDir = yield* makeTempDir("t3-homebrew-capabilities");
603607 const brewBinDir = NodePath.join(tempDir, "brew-bin");
604608 const brewPath = NodePath.join(brewBinDir, "brew");
605609 writeExecutable(brewPath);
606− const caskBinary = NodePath.join(
610+ const ownedBinary = NodePath.join(
607611 tempDir,
608− "Caskroom",
609− "package-tool",
612+ fixture.directory,
613+ fixture.name,
610614 "0.148.0",
611− "package-tool",
615+ "package-tool-0.148.0",
612616 );
613− writeExecutable(caskBinary);
614− const link = NodePath.join(tempDir, "bin", "package-tool");
617+ writeExecutable(ownedBinary);
618+ const link = NodePath.join(tempDir, "bin", "custom-package-tool");
615619 NodeFS.mkdirSync(NodePath.dirname(link), { recursive: true });
616− NodeFS.symlinkSync(caskBinary, link);
620+ NodeFS.symlinkSync(ownedBinary, link);
617621 const spawned: Array<ReadonlyArray<string>> = [];
618622
619623 const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(
@@ -630,27 +634,38 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => { · 10 unmodified lines
630634 spawned.push([command, ...args]);
631635 return args[0] === "--prefix"
632636 ? `${tempDir}\n`
633− : JSON.stringify({ casks: [{ version: "0.148.0,42" }] });
637+ : JSON.stringify(
638+ fixture.kind === "cask"
639+ ? { casks: [{ version: "0.148.0,42" }] }
640+ : { formulae: [{ versions: { stable: "0.148.0" } }] },
641+ );
634642 }),
635643 ),
636644 );
637645
638646 expect(spawned).toEqual([
639647 [brewPath, "--prefix"],
640− [brewPath, "info", "--json=v2", "package-tool"],
648+ [brewPath, "info", "--json=v2", fixture.name],
641649 ]);
642650 expect(capabilities).toEqual({
643651 provider: driver("packageTool"),
644652 packageName: "@example/package-tool",
645653 latestVersion: "0.148.0",
646654 update: {
647− command: "brew upgrade --cask package-tool",
655+ command:
656+ fixture.kind === "cask"
657+ ? `brew upgrade --cask ${fixture.name}`
658+ : `brew upgrade ${fixture.name}`,
648659 executable: brewPath,
649− args: ["upgrade", "--cask", "package-tool"],
660+ args:
661+ fixture.kind === "cask"
662+ ? ["upgrade", "--cask", fixture.name]
663+ : ["upgrade", fixture.name],
650664 lockKey: "homebrew",
651665 },
652666 });
653667 }),
668+ { skip: !symlinksSupported },
654669 );
655670
656671 it.effect.skipIf(windowsHost)(
apps/server/src/provider/providerMaintenance.ts+4−0
@@ -432,6 +432,10 @@ export const resolvePackageManagedProviderMaintenance = Effect.fn( · 431 unmodified lines
432432
433433 const homebrew = homebrewOwnershipFromCommandPath(context.realCommandPath);
434434 if (homebrew) {
435+ // Mise shims resolve to the version manager, not the provider.
436+ if (homebrew.kind === "formula" && homebrew.name.toLowerCase() === "mise") {
437+ return manual;
438+ }
435439 const brewPath = yield* resolveCommandPath("brew", { env: context.env }).pipe(
436440 Effect.catchTags({ CommandResolutionError: () => Effect.succeed(null) }),
437441 );