Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions packages/cli/src/commands/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,24 @@ async function runRegistryAddCore(
npmSpin?.fail(`Failed to update ${packageJsonPath}`)
throw error
}

// Run bun install to actually install the dependencies
const packageDir = isFlattened ? cwd : join(cwd, ".opencode")
const installSpin2 = options.quiet
? null
: createSpinner({ text: "Installing npm dependencies..." })
installSpin2?.start()

try {
await runBunInstall(packageDir)
installSpin2?.succeed("Installed npm dependencies")
} catch (_error) {
installSpin2?.fail("Failed to install npm dependencies")
logger.warn(
`Could not run 'bun install' in ${packageDir}. ` +
"You may need to install dependencies manually.",
)
}
}

// Save lock file
Expand Down Expand Up @@ -886,6 +904,26 @@ async function updateOpencodeDevDependencies(
}
}

/**
* Run `bun install` in the given directory to install dependencies
* declared in package.json.
*
* @param cwd - Directory containing the package.json
* @throws Error if the install process exits with a non-zero code
*/
async function runBunInstall(cwd: string): Promise<void> {
const proc = Bun.spawn(["bun", "install"], {
cwd,
stdout: "pipe",
stderr: "pipe",
})
const exitCode = await proc.exited
if (exitCode !== 0) {
const stderr = await new Response(proc.stderr).text()
throw new Error(`bun install failed (exit ${exitCode}): ${stderr.trim()}`)
}
}

/**
* Find which component installed a given file path
*/
Expand Down
32 changes: 32 additions & 0 deletions packages/cli/tests/add.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,38 @@ describe("ocx add", () => {
expect(opencode.plugin).toContain("no-mcp-plugin")
})

it("should install npm dependencies after writing package.json", async () => {
testDir = await createTempDir("add-npm-install")

// Init and add registry
await runCLI(["init", "--force"], testDir)

const configPath = join(testDir, ".opencode", "ocx.jsonc")
const config = parseJsonc(await readFile(configPath, "utf-8"))
config.registries = {
kdco: { url: registry.url },
}
await writeFile(configPath, JSON.stringify(config, null, 2))

// Install component that has npmDependencies (test-plugin has lodash@^4.17.21)
const { exitCode, output } = await runCLI(["add", "kdco/test-plugin", "--force"], testDir)

if (exitCode !== 0) {
console.log(output)
}
expect(exitCode).toBe(0)

// Verify package.json was written
const packageJsonPath = join(testDir, ".opencode", "package.json")
expect(existsSync(packageJsonPath)).toBe(true)

// Verify node_modules was created (bun install ran)
expect(existsSync(join(testDir, ".opencode", "node_modules"))).toBe(true)

// Verify the dependency was actually installed
expect(existsSync(join(testDir, ".opencode", "node_modules", "lodash"))).toBe(true)
})

it("should fail if integrity check fails", async () => {
testDir = await createTempDir("add-integrity-fail")

Expand Down
Loading