fix(cache): Add package.json to plugin directory for cache dependency resolution

The bundled hook scripts use `external: ['better-sqlite3']` during esbuild,
meaning the dependency must be resolved at runtime. When hooks run from the
cache directory (~/.claude/plugins/cache/thedotmack/claude-mem/X.X.X/),
they couldn't find better-sqlite3 because:

1. Cache directory had no package.json
2. smart-install.js was hardcoded to install in marketplace directory only

This fix:
- Adds plugin/package.json declaring runtime dependencies (better-sqlite3)
- Updates build-hooks.js to auto-generate plugin/package.json from main package.json
- Updates smart-install.js to detect execution context (cache vs marketplace)
  and install dependencies in the correct location

The script now detects if it's running from cache (via path pattern matching)
and installs dependencies there, where the hooks actually execute.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kat-bell
2025-12-09 05:27:26 -06:00
parent 1f2e5f1a9c
commit d7dc29498c
3 changed files with 78 additions and 28 deletions
+13
View File
@@ -0,0 +1,13 @@
{
"name": "claude-mem-plugin",
"version": "7.0.3",
"private": true,
"description": "Runtime dependencies for claude-mem bundled hooks",
"type": "module",
"dependencies": {
"better-sqlite3": "^12.5.0"
},
"engines": {
"node": ">=18.0.0"
}
}
+45 -28
View File
@@ -4,6 +4,8 @@
* Smart Install Script for claude-mem
*
* Features:
* - Detects execution context (cache vs marketplace directory)
* - Installs dependencies where the hooks actually run (cache directory)
* - Only runs npm install when necessary (version change or missing deps)
* - Caches installation state with version marker
* - Provides helpful Windows-specific error messages
@@ -12,20 +14,30 @@
*/
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { execSync, spawnSync, spawn } from 'child_process';
import { join } from 'path';
import { execSync } from 'child_process';
import { join, dirname } from 'path';
import { homedir } from 'os';
import { createRequire } from 'module';
import { fileURLToPath } from 'url';
// CRITICAL: Always use marketplace directory for ALL operations
// This script may run from the cache directory (plugin/scripts/) but must
// operate on the marketplace directory where package.json and node_modules live.
// This ensures cross-platform compatibility and avoids cache directory confusion.
const MARKETPLACE_ROOT = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
// Determine the directory where THIS script is running from
// This could be either:
// 1. Cache: ~/.claude/plugins/cache/thedotmack/claude-mem/X.X.X/scripts/
// 2. Marketplace: ~/.claude/plugins/marketplaces/thedotmack/plugin/scripts/
const __dirname = dirname(fileURLToPath(import.meta.url));
const SCRIPT_ROOT = dirname(__dirname); // Parent of scripts/ directory
// Detect if running from cache directory (has version number in path)
const CACHE_PATTERN = /[/\\]cache[/\\]thedotmack[/\\]claude-mem[/\\]\d+\.\d+\.\d+/;
const IS_RUNNING_FROM_CACHE = CACHE_PATTERN.test(__dirname);
// Set PLUGIN_ROOT based on where we're running
// If from cache, install dependencies IN the cache directory (where hooks run)
// If from marketplace, use marketplace directory
const PLUGIN_ROOT = IS_RUNNING_FROM_CACHE
? SCRIPT_ROOT // Cache directory (e.g., ~/.claude/plugins/cache/thedotmack/claude-mem/7.0.3/)
: join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
// Use MARKETPLACE_ROOT for all paths - this script can be deployed anywhere
// but always operates on the marketplace directory
const PLUGIN_ROOT = MARKETPLACE_ROOT;
const PACKAGE_JSON_PATH = join(PLUGIN_ROOT, 'package.json');
const VERSION_MARKER_PATH = join(PLUGIN_ROOT, '.install-version');
const NODE_MODULES_PATH = join(PLUGIN_ROOT, 'node_modules');
@@ -102,6 +114,12 @@ function setInstalledVersion(packageVersion, nodeVersion) {
}
function needsInstall() {
// Check if package.json exists (required for npm install)
if (!existsSync(PACKAGE_JSON_PATH)) {
log(`⚠️ No package.json found at ${PLUGIN_ROOT}`, colors.yellow);
return false; // Can't install without package.json
}
// Check if node_modules exists
if (!existsSync(NODE_MODULES_PATH)) {
log('📦 Dependencies not found - first time setup', colors.cyan);
@@ -155,9 +173,8 @@ async function verifyNativeModules() {
try {
log('🔍 Verifying native modules...', colors.dim);
// CRITICAL: Use createRequire() to resolve from MARKETPLACE_ROOT
// This script may run from cache but must load modules from marketplace's node_modules
const require = createRequire(join(MARKETPLACE_ROOT, 'package.json'));
// Use createRequire() to resolve from PLUGIN_ROOT's node_modules
const require = createRequire(join(PLUGIN_ROOT, 'package.json'));
const Database = require('better-sqlite3');
// Try to create a test in-memory database
@@ -247,7 +264,8 @@ async function runNpmInstall() {
const isWindows = process.platform === 'win32';
log('', colors.cyan);
log('🔨 Installing dependencies...', colors.bright);
log(`🔨 Installing dependencies in ${IS_RUNNING_FROM_CACHE ? 'cache' : 'marketplace'}...`, colors.bright);
log(` ${PLUGIN_ROOT}`, colors.dim);
log('', colors.reset);
// Try normal install first, then retry with force if it fails
@@ -274,7 +292,7 @@ async function runNpmInstall() {
throw new Error('better-sqlite3 installation verification failed');
}
// NEW: Verify native modules actually work
// Verify native modules actually work
const nativeModulesWork = await verifyNativeModules();
if (!nativeModulesWork) {
throw new Error('Native modules failed to load after install');
@@ -331,16 +349,15 @@ async function runNpmInstall() {
return false;
}
/**
* Check if we should fail when worker startup fails
* Returns true if worker failed AND dependencies are missing
*/
function shouldFailOnWorkerStartup(workerStarted) {
return !workerStarted && !existsSync(NODE_MODULES_PATH);
}
async function main() {
try {
// Log execution context for debugging
if (IS_RUNNING_FROM_CACHE) {
log('📍 Running from cache directory', colors.dim);
} else {
log('📍 Running from marketplace directory', colors.dim);
}
// Check if we need to install dependencies
const installNeeded = needsInstall();
@@ -355,7 +372,7 @@ async function main() {
process.exit(1);
}
} else {
// NEW: Even if install not needed, verify native modules work
// Even if install not needed, verify native modules work
const nativeModulesWork = await verifyNativeModules();
if (!nativeModulesWork) {
@@ -371,10 +388,10 @@ async function main() {
}
}
// NOTE: Worker auto-start disabled in smart-install.js
// The context-hook.js calls ensureWorkerRunning() which handles worker startup
// This avoids potential process management conflicts during plugin initialization
log('✅ Installation complete', colors.green);
// NOTE: Worker auto-start disabled in smart-install.js
// The context-hook.js calls ensureWorkerRunning() which handles worker startup
// This avoids potential process management conflicts during plugin initialization
log('✅ Installation complete', colors.green);
// Success - dependencies installed (if needed)
process.exit(0);
+20
View File
@@ -58,6 +58,26 @@ async function buildHooks() {
}
console.log('✓ Output directories ready');
// Generate plugin/package.json for cache directory dependency installation
// The bundled hooks use `external: ['better-sqlite3']` so dependencies must be
// installed at runtime. This package.json enables npm install in the cache directory.
console.log('\n📦 Generating plugin package.json...');
const pluginPackageJson = {
name: 'claude-mem-plugin',
version: version,
private: true,
description: 'Runtime dependencies for claude-mem bundled hooks',
type: 'module',
dependencies: {
'better-sqlite3': packageJson.dependencies['better-sqlite3']
},
engines: {
node: '>=18.0.0'
}
};
fs.writeFileSync('plugin/package.json', JSON.stringify(pluginPackageJson, null, 2) + '\n');
console.log('✓ plugin/package.json generated');
// Build React viewer
console.log('\n📋 Building React viewer...');
const { spawn } = await import('child_process');