From 77adabb4ff69eed7a31cf44af62b4cce7d818027 Mon Sep 17 00:00:00 2001 From: melkeydev Date: Sun, 5 Apr 2026 19:01:02 -0700 Subject: [PATCH 1/3] pushing bug fix --- .github/workflows/add-plugin-ci.yml | 34 ++++++ packages/add-plugin/lib/install.ts | 57 +++++++-- packages/add-plugin/package.json | 4 +- .../add-plugin/test/install-staging.test.ts | 113 ++++++++++++++++++ 4 files changed, 199 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/add-plugin-ci.yml create mode 100644 packages/add-plugin/test/install-staging.test.ts diff --git a/.github/workflows/add-plugin-ci.yml b/.github/workflows/add-plugin-ci.yml new file mode 100644 index 0000000..4248275 --- /dev/null +++ b/.github/workflows/add-plugin-ci.yml @@ -0,0 +1,34 @@ +name: Add Plugin CI + +on: + push: + paths: + - ".github/workflows/add-plugin-ci.yml" + - "packages/add-plugin/**" + pull_request: + paths: + - ".github/workflows/add-plugin-ci.yml" + - "packages/add-plugin/**" + +jobs: + add-plugin: + runs-on: ubuntu-latest + defaults: + run: + working-directory: packages/add-plugin + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: npm install + + - name: Build + run: npm run build + + - name: Test + run: npm test diff --git a/packages/add-plugin/lib/install.ts b/packages/add-plugin/lib/install.ts index bf8760a..448fc62 100644 --- a/packages/add-plugin/lib/install.ts +++ b/packages/add-plugin/lib/install.ts @@ -10,10 +10,11 @@ */ import { join, relative } from "path"; -import { mkdir, cp, readFile, writeFile } from "fs/promises"; +import { mkdir, cp, readFile, writeFile, rm } from "fs/promises"; import { existsSync } from "fs"; import { execSync } from "child_process"; import { homedir } from "os"; +import { createHash } from "crypto"; import type { DiscoveredPlugin } from "./discover.js"; import type { Target } from "./targets.js"; import { c, step, stepDone, stepError, barLine, barEmpty, barDebug } from "./ui.js"; @@ -37,20 +38,60 @@ export async function installPlugins( source: string, ): Promise { switch (target.id) { - case "claude-code": - await installToClaudeCode(plugins, scope, repoPath, source); + case "claude-code": { + const workspace = await stageInstallWorkspace(plugins, repoPath, target.id); + await installToClaudeCode(workspace.plugins, scope, workspace.repoPath, source); break; - case "cursor": - await installToCursor(plugins, scope, repoPath, source); + } + case "cursor": { + if (cachePopulated) return; + const workspace = await stageInstallWorkspace(plugins, repoPath, target.id); + await installToCursor(workspace.plugins, scope, workspace.repoPath, source); break; - case "codex": - await installToCodex(plugins, scope, repoPath, source); + } + case "codex": { + const workspace = await stageInstallWorkspace(plugins, repoPath, target.id); + await installToCodex(workspace.plugins, scope, workspace.repoPath, source); break; + } default: throw new Error(`Unsupported target: ${target.id}`); } } +interface InstallWorkspace { + repoPath: string; + plugins: DiscoveredPlugin[]; +} + +export async function stageInstallWorkspace( + plugins: DiscoveredPlugin[], + repoPath: string, + targetId: string, + stagingBaseDir = join(homedir(), ".cache", "plugins", ".install-staging"), +): Promise { + const stageKey = createHash("sha1").update(repoPath).digest("hex"); + const stageRoot = join(stagingBaseDir, stageKey, targetId); + const stagedRepoPath = join(stageRoot, "repo"); + + await mkdir(stageRoot, { recursive: true }); + await rm(stagedRepoPath, { recursive: true, force: true }); + await cp(repoPath, stagedRepoPath, { recursive: true }); + + const stagedPlugins = plugins.map((plugin) => { + const relPath = relative(repoPath, plugin.path); + return { + ...plugin, + path: relPath === "" ? stagedRepoPath : join(stagedRepoPath, relPath), + }; + }); + + return { + repoPath: stagedRepoPath, + plugins: stagedPlugins, + }; +} + // --------------------------------------------------------------------------- // Claude Code installer (uses the `claude` CLI) // --------------------------------------------------------------------------- @@ -706,7 +747,7 @@ function findClaude(): string { * - Generate /plugin.json if neither .plugin/ nor vendor dir exist * - Translate ${PLUGIN_ROOT} -> ${} in config files */ -async function preparePluginDirForVendor( +export async function preparePluginDirForVendor( plugin: DiscoveredPlugin, vendorDir: string, envVar: string, diff --git a/packages/add-plugin/package.json b/packages/add-plugin/package.json index ade1ed2..7c9e0d0 100644 --- a/packages/add-plugin/package.json +++ b/packages/add-plugin/package.json @@ -11,9 +11,11 @@ ], "scripts": { "build": "tsup", - "start": "node dist/index.js" + "start": "node dist/index.js", + "test": "tsx --test test/**/*.test.ts" }, "devDependencies": { + "tsx": "^4.20.6", "tsup": "^8", "typescript": "^5" } diff --git a/packages/add-plugin/test/install-staging.test.ts b/packages/add-plugin/test/install-staging.test.ts new file mode 100644 index 0000000..393b912 --- /dev/null +++ b/packages/add-plugin/test/install-staging.test.ts @@ -0,0 +1,113 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import type { DiscoveredPlugin } from "../lib/discover.js"; +import { preparePluginDirForVendor, stageInstallWorkspace } from "../lib/install.js"; + +function createPlugin(repoPath: string, pluginPath: string): DiscoveredPlugin { + return { + name: "claude-hooks", + version: "1.0.0", + description: "Regression test fixture", + path: pluginPath, + marketplace: undefined, + skills: [], + commands: [], + agents: [], + rules: [], + hasHooks: true, + hasMcp: false, + hasLsp: false, + manifest: null, + explicitSkillPaths: undefined, + marketplaceEntry: undefined, + }; +} + +test("staged installs isolate plugin-root rewrites per target", async () => { + const root = await mkdtemp(join(tmpdir(), "add-plugin-install-test-")); + + try { + const repoPath = join(root, "repo"); + const pluginPath = join(repoPath, "plugins", "claude-hooks"); + const hooksPath = join(pluginPath, "hooks", "hooks.json"); + + await mkdir(join(pluginPath, ".plugin"), { recursive: true }); + await mkdir(join(pluginPath, "hooks"), { recursive: true }); + + await writeFile( + join(pluginPath, ".plugin", "plugin.json"), + JSON.stringify( + { + name: "claude-hooks", + version: "1.0.0", + description: "Regression test fixture", + }, + null, + 2, + ), + ); + + await writeFile( + hooksPath, + JSON.stringify( + { + hooks: { + SessionStart: [ + { + hooks: [ + { + type: "command", + command: "${PLUGIN_ROOT}/hooks/inject-claude-md.mjs", + }, + ], + }, + ], + }, + }, + null, + 2, + ), + ); + + const plugin = createPlugin(repoPath, pluginPath); + const stagingBase = join(root, "staging"); + + const claudeWorkspace = await stageInstallWorkspace([plugin], repoPath, "claude-code", stagingBase); + const codexWorkspace = await stageInstallWorkspace([plugin], repoPath, "codex", stagingBase); + + await preparePluginDirForVendor( + claudeWorkspace.plugins[0]!, + ".claude-plugin", + "CLAUDE_PLUGIN_ROOT", + ); + await preparePluginDirForVendor( + codexWorkspace.plugins[0]!, + ".codex-plugin", + "CODEX_PLUGIN_ROOT", + ); + + const originalHooks = await readFile(hooksPath, "utf-8"); + const claudeHooks = await readFile( + join(claudeWorkspace.plugins[0]!.path, "hooks", "hooks.json"), + "utf-8", + ); + const codexHooks = await readFile( + join(codexWorkspace.plugins[0]!.path, "hooks", "hooks.json"), + "utf-8", + ); + + assert.match(originalHooks, /\$\{PLUGIN_ROOT\}/); + assert.doesNotMatch(originalHooks, /\$\{CLAUDE_PLUGIN_ROOT\}|\$\{CODEX_PLUGIN_ROOT\}/); + + assert.match(claudeHooks, /\$\{CLAUDE_PLUGIN_ROOT\}/); + assert.doesNotMatch(claudeHooks, /\$\{CODEX_PLUGIN_ROOT\}/); + + assert.match(codexHooks, /\$\{CODEX_PLUGIN_ROOT\}/); + assert.doesNotMatch(codexHooks, /\$\{CLAUDE_PLUGIN_ROOT\}/); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); From fbbb4e5cdfb970f66750492475120e21de8ddf20 Mon Sep 17 00:00:00 2001 From: melkeydev Date: Sun, 5 Apr 2026 19:18:33 -0700 Subject: [PATCH 2/3] uptick version --- packages/add-plugin/index.ts | 2 +- packages/add-plugin/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/add-plugin/index.ts b/packages/add-plugin/index.ts index 5590367..18361d8 100644 --- a/packages/add-plugin/index.ts +++ b/packages/add-plugin/index.ts @@ -10,7 +10,7 @@ import { installPlugins } from "./lib/install.js"; import { c, S, banner, header, footer, step, stepDone, stepActive, stepError, barLine, barEmpty, barDebug, error, multiSelect, setDebug, type MultiSelectOption } from "./lib/ui.js"; import { setVersion, track } from "./lib/telemetry.js"; -setVersion("1.0.1"); +setVersion("1.2.8"); const { values, positionals } = parseArgs({ args: process.argv.slice(2), diff --git a/packages/add-plugin/package.json b/packages/add-plugin/package.json index 7c9e0d0..660df3a 100644 --- a/packages/add-plugin/package.json +++ b/packages/add-plugin/package.json @@ -1,6 +1,6 @@ { "name": "plugins", - "version": "1.2.7", + "version": "1.2.8", "description": "Install open-plugin format plugins into agent tools", "type": "module", "bin": { From 6bf5b23220728ff5d5e72506555624088f136488 Mon Sep 17 00:00:00 2001 From: melkeydev Date: Sun, 5 Apr 2026 19:43:25 -0700 Subject: [PATCH 3/3] fixing ci --- packages/add-plugin/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/add-plugin/package.json b/packages/add-plugin/package.json index 660df3a..18da75e 100644 --- a/packages/add-plugin/package.json +++ b/packages/add-plugin/package.json @@ -12,7 +12,7 @@ "scripts": { "build": "tsup", "start": "node dist/index.js", - "test": "tsx --test test/**/*.test.ts" + "test": "tsx --test test/*.test.ts" }, "devDependencies": { "tsx": "^4.20.6",