fix: one-step codex installs by preferring bundled plugins (#383)
Co-authored-by: The Future is Work <future@Thes-Mac-Studio.local>
This commit is contained in:
@@ -2,6 +2,7 @@ import { defineCommand } from "citty"
|
|||||||
import { promises as fs } from "fs"
|
import { promises as fs } from "fs"
|
||||||
import os from "os"
|
import os from "os"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
import { fileURLToPath } from "url"
|
||||||
import { loadClaudePlugin } from "../parsers/claude"
|
import { loadClaudePlugin } from "../parsers/claude"
|
||||||
import { targets, validateScope } from "../targets"
|
import { targets, validateScope } from "../targets"
|
||||||
import { pathExists } from "../utils/files"
|
import { pathExists } from "../utils/files"
|
||||||
@@ -233,7 +234,12 @@ async function resolvePluginPath(input: string): Promise<ResolvedPluginPath> {
|
|||||||
throw new Error(`Local plugin path not found: ${directPath}`)
|
throw new Error(`Local plugin path not found: ${directPath}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, always fetch the latest from GitHub
|
const bundledPluginPath = await resolveBundledPluginPath(input)
|
||||||
|
if (bundledPluginPath) {
|
||||||
|
return { path: bundledPluginPath }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, fetch the latest from GitHub
|
||||||
return await resolveGitHubPluginPath(input)
|
return await resolveGitHubPluginPath(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,6 +261,16 @@ function resolveOutputRoot(value: unknown): string {
|
|||||||
return path.join(os.homedir(), ".config", "opencode")
|
return path.join(os.homedir(), ".config", "opencode")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function resolveBundledPluginPath(pluginName: string): Promise<string | null> {
|
||||||
|
const bundledRoot = fileURLToPath(new URL("../../plugins/", import.meta.url))
|
||||||
|
const pluginPath = path.join(bundledRoot, pluginName)
|
||||||
|
const manifestPath = path.join(pluginPath, ".claude-plugin", "plugin.json")
|
||||||
|
if (await pathExists(manifestPath)) {
|
||||||
|
return pluginPath
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
async function resolveGitHubPluginPath(pluginName: string): Promise<ResolvedPluginPath> {
|
async function resolveGitHubPluginPath(pluginName: string): Promise<ResolvedPluginPath> {
|
||||||
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "compound-plugin-"))
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "compound-plugin-"))
|
||||||
const source = resolveGitHubSource()
|
const source = resolveGitHubSource()
|
||||||
|
|||||||
@@ -180,6 +180,46 @@ describe("CLI", () => {
|
|||||||
expect(await exists(path.join(tempRoot, ".config", "opencode", "agents", "repo-research-analyst.md"))).toBe(true)
|
expect(await exists(path.join(tempRoot, ".config", "opencode", "agents", "repo-research-analyst.md"))).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("install uses bundled compound-engineering plugin for codex output", async () => {
|
||||||
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "cli-bundled-codex-home-"))
|
||||||
|
const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "cli-bundled-codex-workspace-"))
|
||||||
|
const projectRoot = path.join(import.meta.dir, "..")
|
||||||
|
const codexRoot = path.join(tempRoot, ".codex")
|
||||||
|
|
||||||
|
const proc = Bun.spawn([
|
||||||
|
"bun",
|
||||||
|
"run",
|
||||||
|
path.join(projectRoot, "src", "index.ts"),
|
||||||
|
"install",
|
||||||
|
"compound-engineering",
|
||||||
|
"--to",
|
||||||
|
"codex",
|
||||||
|
], {
|
||||||
|
cwd: workspaceRoot,
|
||||||
|
stdout: "pipe",
|
||||||
|
stderr: "pipe",
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
HOME: tempRoot,
|
||||||
|
COMPOUND_PLUGIN_GITHUB_SOURCE: "/definitely-not-a-valid-plugin-source",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const exitCode = await proc.exited
|
||||||
|
const stdout = await new Response(proc.stdout).text()
|
||||||
|
const stderr = await new Response(proc.stderr).text()
|
||||||
|
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
throw new Error(`CLI failed (exit ${exitCode}).\nstdout: ${stdout}\nstderr: ${stderr}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(stdout).toContain("Installed compound-engineering")
|
||||||
|
expect(stdout).toContain(codexRoot)
|
||||||
|
expect(await exists(path.join(codexRoot, "prompts", "ce-plan.md"))).toBe(true)
|
||||||
|
expect(await exists(path.join(codexRoot, "skills", "ce:plan", "SKILL.md"))).toBe(true)
|
||||||
|
expect(await exists(path.join(codexRoot, "AGENTS.md"))).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
test("install by name ignores same-named local directory", async () => {
|
test("install by name ignores same-named local directory", async () => {
|
||||||
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "cli-shadow-"))
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "cli-shadow-"))
|
||||||
const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "cli-shadow-workspace-"))
|
const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "cli-shadow-workspace-"))
|
||||||
|
|||||||
Reference in New Issue
Block a user