@ztffn/presentation-generator-plugin 1.4.2 → 1.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/index.js +89 -27
  2. package/package.json +1 -1
package/bin/index.js CHANGED
@@ -244,9 +244,9 @@ function cleanOldCacheVersions(keepVersion) {
244
244
  }
245
245
  }
246
246
 
247
- function purgeInstalledPluginsJson() {
248
- // Directly remove all entries for this plugin from Claude's global registry
249
- // so that a subsequent `claude plugin install` always writes a fresh entry.
247
+ function clearInstalledPluginsEntry() {
248
+ // Remove all registry entries for this plugin so `claude plugin install`
249
+ // starts from a clean slate and cannot restore a stale cache path/version.
250
250
  const registryPath = path.join(
251
251
  os.homedir(), ".claude", "plugins", "installed_plugins.json"
252
252
  );
@@ -258,25 +258,59 @@ function purgeInstalledPluginsJson() {
258
258
  delete registry.plugins[key];
259
259
  fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2) + "\n");
260
260
  }
261
- } catch {
262
- // registry unreadable/corrupt — leave it alone
261
+ } catch {}
262
+ }
263
+
264
+ function writeInstalledPluginsJson(version, installDir, scope) {
265
+ // Claude Code's `claude plugin install` writes stale version/installPath data
266
+ // (a known bug: https://github.com/anthropics/claude-code/issues/15642).
267
+ // We overwrite the entry directly with the correct values after install.
268
+ const registryPath = path.join(
269
+ os.homedir(), ".claude", "plugins", "installed_plugins.json"
270
+ );
271
+
272
+ // Ensure the cache dir exists and contains the plugin files.
273
+ const cacheDir = path.join(
274
+ os.homedir(), ".claude", "plugins", "cache", MARKETPLACE_NAME, PLUGIN_NAME, version
275
+ );
276
+ if (!fs.existsSync(cacheDir)) {
277
+ execSync(`cp -r "${installDir}/." "${cacheDir}"`, { stdio: "pipe" });
263
278
  }
279
+
280
+ let registry = { version: 2, plugins: {} };
281
+ if (fs.existsSync(registryPath)) {
282
+ try { registry = JSON.parse(fs.readFileSync(registryPath, "utf8")); } catch {}
283
+ }
284
+
285
+ const key = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
286
+ const now = new Date().toISOString();
287
+ const existing = (registry.plugins[key] || []).find((e) => e.scope === scope);
288
+ const entry = {
289
+ scope,
290
+ installPath: cacheDir,
291
+ version,
292
+ installedAt: existing?.installedAt || now,
293
+ lastUpdated: now,
294
+ projectPath: process.cwd(),
295
+ };
296
+
297
+ // Replace all entries for this scope, keep entries for other scopes.
298
+ const others = (registry.plugins[key] || []).filter((e) => e.scope !== scope);
299
+ registry.plugins[key] = [...others, entry];
300
+ fs.mkdirSync(path.dirname(registryPath), { recursive: true });
301
+ fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2) + "\n");
264
302
  }
265
303
 
266
- function registerWithClaude(installDir, scope) {
304
+ function registerWithClaude(installDir, scope, version) {
267
305
  const pluginsDir = path.dirname(installDir);
268
306
  ensureMarketplaceJson(pluginsDir);
269
307
 
270
- // Remove any stale registration (e.g. from a previous install at a different path)
271
- // before adding the current one. Errors are ignored — it may not exist yet.
308
+ // Remove any stale marketplace registration before re-adding.
272
309
  try {
273
310
  execSync(`claude plugin marketplace remove "${MARKETPLACE_NAME}"`, { stdio: "pipe" });
274
- } catch {
275
- // didn't exist or not removable — ignore
276
- }
311
+ } catch {}
277
312
 
278
- // Uninstall any stale plugin entries from both scopes before re-installing.
279
- // This prevents Claude from reusing a cached entry pointing at an old version path.
313
+ // Uninstall stale plugin entries from both scopes before re-installing.
280
314
  try {
281
315
  execSync(`claude plugin uninstall ${PLUGIN_NAME}@${MARKETPLACE_NAME} --scope project`, { stdio: "pipe" });
282
316
  } catch {}
@@ -284,14 +318,26 @@ function registerWithClaude(installDir, scope) {
284
318
  execSync(`claude plugin uninstall ${PLUGIN_NAME}@${MARKETPLACE_NAME} --scope user`, { stdio: "pipe" });
285
319
  } catch {}
286
320
 
287
- // Belt-and-suspenders: also purge the registry JSON directly in case
288
- // `claude plugin uninstall` doesn't clean installed_plugins.json reliably.
289
- purgeInstalledPluginsJson();
321
+ // Directly clear the registry entry regardless of whether the CLI uninstall
322
+ // removed it (the CLI skips entries whose projectPath != cwd).
323
+ clearInstalledPluginsEntry();
324
+
325
+ // Remove stale cache dirs so `claude plugin install` has nothing stale to
326
+ // restore. writeInstalledPluginsJson will recreate the correct version dir.
327
+ const cachePluginDir = path.join(
328
+ os.homedir(), ".claude", "plugins", "cache", MARKETPLACE_NAME, PLUGIN_NAME
329
+ );
330
+ if (fs.existsSync(cachePluginDir)) {
331
+ fs.rmSync(cachePluginDir, { recursive: true, force: true });
332
+ }
290
333
 
291
334
  execSync(`claude plugin marketplace add "${pluginsDir}"`, { stdio: "pipe" });
292
335
 
293
336
  const scopeFlag = scope === "project" ? "--scope project" : "--scope user";
294
337
  execSync(`claude plugin install ${PLUGIN_NAME}@${MARKETPLACE_NAME} ${scopeFlag}`, { stdio: "pipe" });
338
+
339
+ // Overwrite whatever Claude wrote with the correct version and cache path.
340
+ writeInstalledPluginsJson(version, installDir, scope);
295
341
  }
296
342
 
297
343
  function removeFromSettings(settingsPath) {
@@ -313,14 +359,36 @@ function removeFromSettings(settingsPath) {
313
359
  // ── Commands ──────────────────────────────────────────────────────────────────
314
360
 
315
361
  async function install() {
362
+ // Warn if this installer binary is outdated (npx caches old versions).
363
+ // Stale installers lack bug fixes — users should run with @latest.
364
+ const latestMeta = await getLatestNpmMeta();
365
+ if (latestMeta) {
366
+ const versionLt = (a, b) => {
367
+ const [aMaj, aMin, aPat] = a.split(".").map(Number);
368
+ const [bMaj, bMin, bPat] = b.split(".").map(Number);
369
+ return aMaj < bMaj || (aMaj === bMaj && aMin < bMin) || (aMaj === bMaj && aMin === bMin && aPat < bPat);
370
+ };
371
+ if (versionLt(CURRENT_VERSION, latestMeta.version)) {
372
+ console.log(`\nWarning: installer v${CURRENT_VERSION} is outdated (v${latestMeta.version} available).`);
373
+ console.log(`Run with @latest to get the current installer:`);
374
+ console.log(` npx ${NPM_PACKAGE}@latest install\n`);
375
+ return;
376
+ }
377
+ }
378
+
379
+ const meta = latestMeta;
380
+ if (!meta) {
381
+ console.error("\nCould not fetch latest version from npm registry. Check your internet connection.\n");
382
+ process.exit(1);
383
+ }
384
+
316
385
  const globalExists = fs.existsSync(GLOBAL_INSTALL_DIR);
317
386
  const projectExists = fs.existsSync(PROJECT_INSTALL_DIR);
318
387
 
319
388
  if (globalExists || projectExists) {
320
- const meta = await getLatestNpmMeta();
321
389
  if (globalExists) {
322
390
  const v = getInstalledVersion(GLOBAL_INSTALL_DIR);
323
- if (meta && v !== meta.version) {
391
+ if (v !== meta.version) {
324
392
  console.log(`\nGlobal install found (v${v}). v${meta.version} available — run: npx ${NPM_PACKAGE} update\n`);
325
393
  } else {
326
394
  console.log(`\nGlobal install already up to date (v${v || "unknown"}).\n`);
@@ -328,7 +396,7 @@ async function install() {
328
396
  }
329
397
  if (projectExists) {
330
398
  const v = getInstalledVersion(PROJECT_INSTALL_DIR);
331
- if (meta && v !== meta.version) {
399
+ if (v !== meta.version) {
332
400
  console.log(`\nProject install found (v${v}). v${meta.version} available — run: npx ${NPM_PACKAGE} update\n`);
333
401
  } else {
334
402
  console.log(`\nProject install already up to date (v${v || "unknown"}).\n`);
@@ -337,12 +405,6 @@ async function install() {
337
405
  return;
338
406
  }
339
407
 
340
- const meta = await getLatestNpmMeta();
341
- if (!meta) {
342
- console.error("\nCould not fetch latest version from npm registry. Check your internet connection.\n");
343
- process.exit(1);
344
- }
345
-
346
408
  const { installDir, scope } = await resolveInstallTarget();
347
409
 
348
410
  console.log(`\nInstalling presentation-generator plugin...`);
@@ -352,7 +414,7 @@ async function install() {
352
414
  console.log(`\nInstalled v${meta.version} to: ${label}`);
353
415
 
354
416
  console.log("\nRegistering plugin with Claude Code...");
355
- registerWithClaude(installDir, scope);
417
+ registerWithClaude(installDir, scope, meta.version);
356
418
  cleanOldCacheVersions(meta.version);
357
419
 
358
420
  const settingsLabel = scope === "project" ? ".claude/settings.json" : "~/.claude/settings.json";
@@ -390,7 +452,7 @@ async function update() {
390
452
  console.log(`\n${label}: updating v${before || "unknown"} → v${meta.version}...`);
391
453
  await downloadAndExtract(meta.version, meta.tarball, installDir);
392
454
  const updateScope = installDir === GLOBAL_INSTALL_DIR ? "user" : "project";
393
- registerWithClaude(installDir, updateScope);
455
+ registerWithClaude(installDir, updateScope, meta.version);
394
456
  cleanOldCacheVersions(meta.version);
395
457
  console.log(`${label}: updated to v${meta.version} ✓`);
396
458
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ztffn/presentation-generator-plugin",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "Claude Code plugin for generating graph-based presentations",
5
5
  "bin": {
6
6
  "presentation-generator-plugin": "bin/index.js"