Files
claude-mem/scripts/build-hooks.js
T
Alex Newman 6fb9570291 feat: Release v4.0.5 - Auto-install dependencies via bootstrap hooks
Fixed better-sqlite3 distribution by implementing self-bootstrapping hooks
that auto-install dependencies on first run. This eliminates the need for
users to have native compilation tools or manually install dependencies.

## Solution

Instead of bundling 25MB of better-sqlite3 binaries in git or requiring
manual npm install, hooks now bootstrap themselves on first execution:

1. Created `src/shared/bootstrap.ts` with `ensureDependencies()` function
2. Added bootstrap calls to all hook entry points (context, new, save, summary, cleanup)
3. Created `plugin/scripts/package.json` declaring better-sqlite3 dependency
4. Bootstrap checks if `node_modules` exists, runs `npm install` if missing
5. npm automatically downloads prebuilt better-sqlite3 binary for user's platform

## Changes

**Core Bootstrap System:**
- Added src/shared/bootstrap.ts: Auto-install dependencies using npm
- Modified all hooks (context, new, save, summary, cleanup) to call ensureDependencies()
- Created plugin/scripts/package.json with better-sqlite3 dependency

**Build & Distribution:**
- Removed node_modules copying logic from build script
- Build output is now compact (hooks + package.json, no binaries)
- Updated marketplace.json to point to GitHub for direct installation

**Documentation:**
- Updated README: GitHub Marketplace installation is now recommended method
- Installation instructions emphasize no compilation needed
- Version bumped to 4.0.5 throughout

## Benefits

-  No git bloat (repo stays small, no 25MB binaries committed)
-  No compilation needed (npm downloads prebuilt binaries)
-  Works on all platforms (npm handles platform-specific binaries)
-  Zero manual steps (hooks bootstrap themselves automatically)
-  Idempotent (skips install if dependencies already exist)

Installation now works via simple:
```
/plugin marketplace add https://github.com/thedotmack/claude-mem
/plugin install claude-mem
```

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 16:44:26 -04:00

149 lines
4.3 KiB
JavaScript

#!/usr/bin/env node
/**
* Build script for claude-mem hooks
* Bundles TypeScript hooks into individual standalone executables using esbuild
*/
import { build } from 'esbuild';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const HOOKS = [
{ name: 'context-hook', source: 'src/bin/hooks/context-hook.ts' },
{ name: 'new-hook', source: 'src/bin/hooks/new-hook.ts' },
{ name: 'save-hook', source: 'src/bin/hooks/save-hook.ts' },
{ name: 'summary-hook', source: 'src/bin/hooks/summary-hook.ts' },
{ name: 'cleanup-hook', source: 'src/bin/hooks/cleanup-hook.ts' }
];
const WORKER_SERVICE = {
name: 'worker-service',
source: 'src/services/worker-service.ts'
};
const SEARCH_SERVER = {
name: 'search-server',
source: 'src/servers/search-server.ts'
};
async function buildHooks() {
console.log('🔨 Building claude-mem hooks, worker service, and search server...\n');
try {
// Read version from package.json
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
const version = packageJson.version;
console.log(`📌 Version: ${version}`);
// Create output directory
console.log('\n📦 Preparing output directory...');
const hooksDir = 'plugin/scripts';
if (!fs.existsSync(hooksDir)) {
fs.mkdirSync(hooksDir, { recursive: true });
}
console.log('✓ Output directory ready');
// Build worker service
console.log(`\n🔧 Building worker service...`);
await build({
entryPoints: [WORKER_SERVICE.source],
bundle: true,
platform: 'node',
target: 'node18',
format: 'cjs',
outfile: `${hooksDir}/${WORKER_SERVICE.name}.cjs`,
minify: true,
external: ['better-sqlite3'],
define: {
'__DEFAULT_PACKAGE_VERSION__': `"${version}"`
},
banner: {
js: '#!/usr/bin/env node'
}
});
// Make worker service executable
fs.chmodSync(`${hooksDir}/${WORKER_SERVICE.name}.cjs`, 0o755);
const workerStats = fs.statSync(`${hooksDir}/${WORKER_SERVICE.name}.cjs`);
console.log(`✓ worker-service built (${(workerStats.size / 1024).toFixed(2)} KB)`);
// Build each hook
for (const hook of HOOKS) {
console.log(`\n🔧 Building ${hook.name}...`);
const outfile = `${hooksDir}/${hook.name}.js`;
await build({
entryPoints: [hook.source],
bundle: true,
platform: 'node',
target: 'node18',
format: 'esm',
outfile,
minify: true,
external: ['better-sqlite3'],
define: {
'__DEFAULT_PACKAGE_VERSION__': `"${version}"`
},
banner: {
js: '#!/usr/bin/env node'
}
});
// Make executable
fs.chmodSync(outfile, 0o755);
// Check file size
const stats = fs.statSync(outfile);
const sizeInKB = (stats.size / 1024).toFixed(2);
console.log(`${hook.name} built (${sizeInKB} KB)`);
}
// Build search server
console.log(`\n🔧 Building search server...`);
await build({
entryPoints: [SEARCH_SERVER.source],
bundle: true,
platform: 'node',
target: 'node18',
format: 'esm',
outfile: `${hooksDir}/${SEARCH_SERVER.name}.js`,
minify: true,
external: ['better-sqlite3'],
define: {
'__DEFAULT_PACKAGE_VERSION__': `"${version}"`
},
banner: {
js: '#!/usr/bin/env node'
}
});
// Make search server executable
fs.chmodSync(`${hooksDir}/${SEARCH_SERVER.name}.js`, 0o755);
const searchStats = fs.statSync(`${hooksDir}/${SEARCH_SERVER.name}.js`);
console.log(`✓ search-server built (${(searchStats.size / 1024).toFixed(2)} KB)`);
console.log('\n✅ All hooks, worker service, and search server built successfully!');
console.log(` Output: ${hooksDir}/`);
console.log(` - Hooks: *-hook.js`);
console.log(` - Worker: worker-service.cjs`);
console.log(` - Search: search-server.js`);
console.log('\n💡 Note: Dependencies will be auto-installed on first hook execution');
} catch (error) {
console.error('\n❌ Build failed:', error.message);
if (error.errors) {
console.error('\nBuild errors:');
error.errors.forEach(err => console.error(` - ${err.text}`));
}
process.exit(1);
}
}
buildHooks();