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"
}
}
+41 -24
View File
@@ -4,6 +4,8 @@
* Smart Install Script for claude-mem * Smart Install Script for claude-mem
* *
* Features: * 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) * - Only runs npm install when necessary (version change or missing deps)
* - Caches installation state with version marker * - Caches installation state with version marker
* - Provides helpful Windows-specific error messages * - Provides helpful Windows-specific error messages
@@ -12,20 +14,30 @@
*/ */
import { existsSync, readFileSync, writeFileSync } from 'fs'; import { existsSync, readFileSync, writeFileSync } from 'fs';
import { execSync, spawnSync, spawn } from 'child_process'; import { execSync } from 'child_process';
import { join } from 'path'; import { join, dirname } from 'path';
import { homedir } from 'os'; import { homedir } from 'os';
import { createRequire } from 'module'; import { createRequire } from 'module';
import { fileURLToPath } from 'url';
// CRITICAL: Always use marketplace directory for ALL operations // Determine the directory where THIS script is running from
// This script may run from the cache directory (plugin/scripts/) but must // This could be either:
// operate on the marketplace directory where package.json and node_modules live. // 1. Cache: ~/.claude/plugins/cache/thedotmack/claude-mem/X.X.X/scripts/
// This ensures cross-platform compatibility and avoids cache directory confusion. // 2. Marketplace: ~/.claude/plugins/marketplaces/thedotmack/plugin/scripts/
const MARKETPLACE_ROOT = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack'); 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 PACKAGE_JSON_PATH = join(PLUGIN_ROOT, 'package.json');
const VERSION_MARKER_PATH = join(PLUGIN_ROOT, '.install-version'); const VERSION_MARKER_PATH = join(PLUGIN_ROOT, '.install-version');
const NODE_MODULES_PATH = join(PLUGIN_ROOT, 'node_modules'); const NODE_MODULES_PATH = join(PLUGIN_ROOT, 'node_modules');
@@ -102,6 +114,12 @@ function setInstalledVersion(packageVersion, nodeVersion) {
} }
function needsInstall() { 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 // Check if node_modules exists
if (!existsSync(NODE_MODULES_PATH)) { if (!existsSync(NODE_MODULES_PATH)) {
log('📦 Dependencies not found - first time setup', colors.cyan); log('📦 Dependencies not found - first time setup', colors.cyan);
@@ -155,9 +173,8 @@ async function verifyNativeModules() {
try { try {
log('🔍 Verifying native modules...', colors.dim); log('🔍 Verifying native modules...', colors.dim);
// CRITICAL: Use createRequire() to resolve from MARKETPLACE_ROOT // Use createRequire() to resolve from PLUGIN_ROOT's node_modules
// This script may run from cache but must load modules from marketplace's node_modules const require = createRequire(join(PLUGIN_ROOT, 'package.json'));
const require = createRequire(join(MARKETPLACE_ROOT, 'package.json'));
const Database = require('better-sqlite3'); const Database = require('better-sqlite3');
// Try to create a test in-memory database // Try to create a test in-memory database
@@ -247,7 +264,8 @@ async function runNpmInstall() {
const isWindows = process.platform === 'win32'; const isWindows = process.platform === 'win32';
log('', colors.cyan); 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); log('', colors.reset);
// Try normal install first, then retry with force if it fails // 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'); throw new Error('better-sqlite3 installation verification failed');
} }
// NEW: Verify native modules actually work // Verify native modules actually work
const nativeModulesWork = await verifyNativeModules(); const nativeModulesWork = await verifyNativeModules();
if (!nativeModulesWork) { if (!nativeModulesWork) {
throw new Error('Native modules failed to load after install'); throw new Error('Native modules failed to load after install');
@@ -331,16 +349,15 @@ async function runNpmInstall() {
return false; 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() { async function main() {
try { 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 // Check if we need to install dependencies
const installNeeded = needsInstall(); const installNeeded = needsInstall();
@@ -355,7 +372,7 @@ async function main() {
process.exit(1); process.exit(1);
} }
} else { } else {
// NEW: Even if install not needed, verify native modules work // Even if install not needed, verify native modules work
const nativeModulesWork = await verifyNativeModules(); const nativeModulesWork = await verifyNativeModules();
if (!nativeModulesWork) { if (!nativeModulesWork) {
+20
View File
@@ -58,6 +58,26 @@ async function buildHooks() {
} }
console.log('✓ Output directories ready'); 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 // Build React viewer
console.log('\n📋 Building React viewer...'); console.log('\n📋 Building React viewer...');
const { spawn } = await import('child_process'); const { spawn } = await import('child_process');