fix(cli): protect existing Windows Codex shims
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
/// <reference path="../../../bun-test.d.ts" />
|
||||
/// <reference types="bun-types" />
|
||||
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { mkdir, mkdtemp, readFile, readlink, writeFile } from "node:fs/promises"
|
||||
import { tmpdir } from "node:os"
|
||||
@@ -35,8 +38,8 @@ describe("codex-cache", () => {
|
||||
await writeFile(join(pluginRoot, "dist", "cli.js"), "#!/usr/bin/env node\n")
|
||||
|
||||
// when
|
||||
const first = await linkCachedPluginBins({ binDir, pluginRoot })
|
||||
const second = await linkCachedPluginBins({ binDir, pluginRoot })
|
||||
const first = await linkCachedPluginBins({ binDir, pluginRoot, platform: "linux" })
|
||||
const second = await linkCachedPluginBins({ binDir, pluginRoot, platform: "linux" })
|
||||
|
||||
// then
|
||||
expect(first).toHaveLength(1)
|
||||
@@ -64,4 +67,29 @@ describe("codex-cache", () => {
|
||||
expect(commandShim).toContain("@echo off")
|
||||
expect(commandShim).toContain(`node "${join(pluginRoot, "dist", "cli.js")}" %*`)
|
||||
})
|
||||
|
||||
test("rejects existing non-generated Windows command shims", async () => {
|
||||
// given
|
||||
const root = await mkdtemp(join(tmpdir(), "omo-codex-cache-"))
|
||||
const pluginRoot = join(root, "plugin")
|
||||
const binDir = join(root, "bin")
|
||||
await mkdir(pluginRoot, { recursive: true })
|
||||
await mkdir(binDir, { recursive: true })
|
||||
await writeFile(join(pluginRoot, "package.json"), JSON.stringify({ name: "@scope/omo", bin: { "omo-hook": "dist/cli.js" } }))
|
||||
await mkdir(join(pluginRoot, "dist"), { recursive: true })
|
||||
await writeFile(join(pluginRoot, "dist", "cli.js"), "#!/usr/bin/env node\n")
|
||||
await writeFile(join(binDir, "omo-hook.cmd"), "@echo off\r\necho custom\r\n")
|
||||
|
||||
// when
|
||||
let rejected = false
|
||||
try {
|
||||
await linkCachedPluginBins({ binDir, pluginRoot, platform: "win32" })
|
||||
} catch (error) {
|
||||
rejected = error instanceof Error && error.message.includes("already exists and is not a generated command shim")
|
||||
}
|
||||
|
||||
// then
|
||||
expect(rejected).toBe(true)
|
||||
expect(await readFile(join(binDir, "omo-hook.cmd"), "utf8")).toContain("echo custom")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,6 +4,8 @@ import type { InstalledPlugin, RunCommand } from "./types"
|
||||
|
||||
type LinkPlatform = NodeJS.Platform
|
||||
|
||||
const COMMAND_SHIM_MARKER = ":: generated by oh-my-openagent Codex installer"
|
||||
|
||||
export async function installCachedPlugin(input: {
|
||||
readonly codexHome: string
|
||||
readonly marketplaceName: string
|
||||
@@ -163,13 +165,16 @@ async function replaceSymlink(linkPath: string, targetPath: string): Promise<voi
|
||||
|
||||
async function replaceCommandShim(linkPath: string, targetPath: string): Promise<void> {
|
||||
if (await existingNonShim(linkPath)) throw new Error(`${linkPath} already exists and is not a command shim`)
|
||||
await writeFile(linkPath, `@echo off\r\nnode "${targetPath}" %*\r\n`)
|
||||
await writeFile(linkPath, `@echo off\r\n${COMMAND_SHIM_MARKER}\r\nnode "${targetPath}" %*\r\n`)
|
||||
}
|
||||
|
||||
async function existingNonShim(path: string): Promise<boolean> {
|
||||
try {
|
||||
const stat = await lstat(path)
|
||||
return !stat.isFile()
|
||||
if (!stat.isFile()) return true
|
||||
const content = await readFile(path, "utf8")
|
||||
if (content.includes(COMMAND_SHIM_MARKER)) return false
|
||||
throw new Error(`${path} already exists and is not a generated command shim`)
|
||||
} catch (error) {
|
||||
if (isNodeErrorWithCode(error) && error.code === "ENOENT") return false
|
||||
throw error
|
||||
|
||||
Reference in New Issue
Block a user