diff --git a/bin/oh-my-opencode.js b/bin/oh-my-opencode.js index 0e644afc2..1a151a8c1 100755 --- a/bin/oh-my-opencode.js +++ b/bin/oh-my-opencode.js @@ -6,6 +6,7 @@ import { spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { createRequire } from "node:module"; import { basename } from "node:path"; +import { fileURLToPath } from "node:url"; import { getPlatformPackageCandidates, getBinaryPath, @@ -77,9 +78,13 @@ function getSignalExitCode(signal) { } function getPackageBaseName() { + return resolvePlatformPackageBaseName(getWrapperPackageName()); +} + +function getWrapperPackageName() { try { const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")); - return resolvePlatformPackageBaseName(packageJson.name || "oh-my-opencode"); + return packageJson.name || "oh-my-opencode"; } catch { return "oh-my-opencode"; } @@ -102,10 +107,44 @@ function getInvocationName() { return basename(argv1, ".js").replace(/\.exe$/, ""); } +function shouldRunBundledLazyCodexCli(packageName, invocationName) { + return packageName === "lazycodex" && invocationName === "lazycodex"; +} + +function runBundledLazyCodexCli(invocationName) { + const cliPath = fileURLToPath(new URL("../dist/cli/index.js", import.meta.url)); + const result = spawnSync("bun", [cliPath, ...process.argv.slice(2)], { + stdio: "inherit", + env: { + ...process.env, + OMO_INVOCATION_NAME: invocationName, + }, + }); + + if (result.error) { + console.error("\nlazycodex: Failed to execute bundled CLI with Bun."); + console.error("Install Bun or run through `bunx lazycodex`."); + console.error(`Error: ${result.error.message}\n`); + process.exit(2); + } + + if (result.signal) { + process.exit(getSignalExitCode(result.signal)); + } + + process.exit(result.status ?? 1); +} + function main() { const { platform, arch } = process; const libcFamily = getLibcFamily(); - const packageBaseName = getPackageBaseName(); + const wrapperPackageName = getWrapperPackageName(); + const invocationName = getInvocationName(); + if (shouldRunBundledLazyCodexCli(wrapperPackageName, invocationName)) { + runBundledLazyCodexCli(invocationName); + } + + const packageBaseName = resolvePlatformPackageBaseName(wrapperPackageName); const avx2Supported = supportsAvx2(); let packageCandidates; @@ -141,7 +180,6 @@ function main() { process.exit(1); } - const invocationName = getInvocationName(); const childEnv = { ...process.env, OMO_INVOCATION_NAME: invocationName, diff --git a/bin/oh-my-opencode.test.ts b/bin/oh-my-opencode.test.ts new file mode 100644 index 000000000..5350292b9 --- /dev/null +++ b/bin/oh-my-opencode.test.ts @@ -0,0 +1,78 @@ +import { afterEach, describe, expect, test } from "bun:test"; +import { spawnSync } from "node:child_process"; +import { chmod, cp, mkdir, mkdtemp, readFile, realpath, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const testRoots: string[] = []; + +afterEach(async () => { + await Promise.all(testRoots.splice(0).map((path) => rm(path, { recursive: true, force: true }))); +}); + +describe("lazycodex bin wrapper", () => { + test("runs the bundled Bun CLI instead of stale OMO platform binaries", async () => { + // #given + const fixture = await createLazyCodexFixture(); + const nodePath = Bun.which("node") ?? "node"; + + // #when + const result = spawnSync(nodePath, [fixture.lazycodexBin, "install", "--no-tui"], { + encoding: "utf8", + env: { + ...process.env, + CAPTURE_DIR: fixture.captureDir, + PATH: `${fixture.fakeBinDir}:${process.env.PATH ?? ""}`, + }, + }); + + // #then + expect(result.status).toBe(23); + expect((await readFile(join(fixture.captureDir, "env"), "utf8")).trim()).toBe("lazycodex"); + expect((await readFile(join(fixture.captureDir, "args"), "utf8")).trim().split("\n")).toEqual([ + await realpath(fixture.bundledCli), + "install", + "--no-tui", + ]); + }); +}); + +async function createLazyCodexFixture() { + const root = await mkdtemp(join(tmpdir(), "lazycodex-bin-wrapper-")); + testRoots.push(root); + + const binDir = join(root, "bin"); + const distCli = join(root, "dist", "cli", "index.js"); + const fakeBinDir = join(root, "fake-bin"); + const captureDir = join(root, "capture"); + await mkdir(binDir, { recursive: true }); + await mkdir(dirname(distCli), { recursive: true }); + await mkdir(fakeBinDir, { recursive: true }); + await mkdir(captureDir, { recursive: true }); + + await cp(fileURLToPath(new URL("./oh-my-opencode.js", import.meta.url)), join(binDir, "lazycodex")); + await cp(fileURLToPath(new URL("./platform.js", import.meta.url)), join(binDir, "platform.js")); + await writeFile(join(root, "package.json"), JSON.stringify({ name: "lazycodex", type: "module" })); + await writeFile(distCli, "#!/usr/bin/env bun\n"); + + const fakeBun = join(fakeBinDir, "bun"); + await writeFile( + fakeBun, + [ + "#!/bin/sh", + "printf '%s\\n' \"$OMO_INVOCATION_NAME\" > \"$CAPTURE_DIR/env\"", + "printf '%s\\n' \"$@\" > \"$CAPTURE_DIR/args\"", + "exit 23", + "", + ].join("\n"), + ); + await chmod(fakeBun, 0o755); + + return { + bundledCli: distCli, + captureDir, + fakeBinDir, + lazycodexBin: join(binDir, "lazycodex"), + }; +}