c7c68e81f4
- README: add language specifier to fenced code block - paths.ts: guard npmPackageRootDirectory() against bundle structure drift - OpenCodeInstaller: resolve bundle from import.meta.url, not process.cwd() - OpenCodeInstaller: log warnings on AGENTS.md injection failures - WindsurfHooksInstaller: key registry by full workspace path, not basename - uninstall.ts: poll health endpoint to wait for worker exit before file deletion - uninstall.ts: call IDE-specific uninstallers (Gemini, Windsurf, OpenCode, OpenClaw, Codex) - opencode-plugin: cap session tracking Map at 1000 entries with LRU eviction - GeminiCliHooksInstaller: document intentional JSON double-escaping Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
157 lines
5.4 KiB
TypeScript
157 lines
5.4 KiB
TypeScript
/**
|
|
* Shared path utilities for the NPX CLI.
|
|
*
|
|
* All platform-specific path logic is centralized here so that every command
|
|
* resolves directories in exactly the same way, regardless of OS.
|
|
*/
|
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
import { homedir } from 'os';
|
|
import { dirname, join } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Platform detection
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export const IS_WINDOWS = process.platform === 'win32';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Core paths
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/** Root of the Claude Code config directory. */
|
|
export function claudeConfigDirectory(): string {
|
|
return process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
|
|
}
|
|
|
|
/** Marketplace install directory for thedotmack. */
|
|
export function marketplaceDirectory(): string {
|
|
return join(claudeConfigDirectory(), 'plugins', 'marketplaces', 'thedotmack');
|
|
}
|
|
|
|
/** Top-level plugins directory. */
|
|
export function pluginsDirectory(): string {
|
|
return join(claudeConfigDirectory(), 'plugins');
|
|
}
|
|
|
|
/** Path to `known_marketplaces.json`. */
|
|
export function knownMarketplacesPath(): string {
|
|
return join(pluginsDirectory(), 'known_marketplaces.json');
|
|
}
|
|
|
|
/** Path to `installed_plugins.json`. */
|
|
export function installedPluginsPath(): string {
|
|
return join(pluginsDirectory(), 'installed_plugins.json');
|
|
}
|
|
|
|
/** Path to `~/.claude/settings.json`. */
|
|
export function claudeSettingsPath(): string {
|
|
return join(claudeConfigDirectory(), 'settings.json');
|
|
}
|
|
|
|
/** Plugin cache directory for a specific version. */
|
|
export function pluginCacheDirectory(version: string): string {
|
|
return join(pluginsDirectory(), 'cache', 'thedotmack', 'claude-mem', version);
|
|
}
|
|
|
|
/** claude-mem data directory (default `~/.claude-mem`). */
|
|
export function claudeMemDataDirectory(): string {
|
|
return join(homedir(), '.claude-mem');
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// NPM package root (where the NPX package lives on disk)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Resolve the root of the installed npm package.
|
|
*
|
|
* After bundling, the CLI entry point lives at `<pkg>/dist/npx-cli/index.js`.
|
|
* Walking up 2 levels from `import.meta.url` reaches the package root
|
|
* where `plugin/` and `package.json` can be found.
|
|
*/
|
|
export function npmPackageRootDirectory(): string {
|
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
// <pkg>/dist/npx-cli/index.js -> up 2 levels -> <pkg>
|
|
const root = join(dirname(currentFilePath), '..', '..');
|
|
if (!existsSync(join(root, 'package.json'))) {
|
|
throw new Error(
|
|
`npmPackageRootDirectory: expected package.json at ${root}. ` +
|
|
`Bundle structure may have changed — update the path walk.`,
|
|
);
|
|
}
|
|
return root;
|
|
}
|
|
|
|
/**
|
|
* Path to the `plugin/` directory bundled inside the npm package.
|
|
*/
|
|
export function npmPackagePluginDirectory(): string {
|
|
return join(npmPackageRootDirectory(), 'plugin');
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Version helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Read the current plugin version from the npm package's
|
|
* `plugin/.claude-plugin/plugin.json` (preferred) or from `package.json`.
|
|
*/
|
|
export function readPluginVersion(): string {
|
|
// Try plugin.json first (authoritative for plugin version)
|
|
const pluginJsonPath = join(npmPackagePluginDirectory(), '.claude-plugin', 'plugin.json');
|
|
if (existsSync(pluginJsonPath)) {
|
|
try {
|
|
const pluginJson = JSON.parse(readFileSync(pluginJsonPath, 'utf-8'));
|
|
if (pluginJson.version) return pluginJson.version;
|
|
} catch {
|
|
// Fall through to package.json
|
|
}
|
|
}
|
|
|
|
// Fall back to package.json at package root
|
|
const packageJsonPath = join(npmPackageRootDirectory(), 'package.json');
|
|
if (existsSync(packageJsonPath)) {
|
|
try {
|
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
if (packageJson.version) return packageJson.version;
|
|
} catch {
|
|
// Unable to read
|
|
}
|
|
}
|
|
|
|
return '0.0.0';
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Installation detection
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/** Returns true if the plugin appears to be installed in the marketplace dir. */
|
|
export function isPluginInstalled(): boolean {
|
|
const marketplaceDir = marketplaceDirectory();
|
|
return existsSync(join(marketplaceDir, 'plugin', '.claude-plugin', 'plugin.json'));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// JSON file helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function ensureDirectoryExists(directoryPath: string): void {
|
|
if (!existsSync(directoryPath)) {
|
|
mkdirSync(directoryPath, { recursive: true });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @deprecated Use `readJsonSafe` from `../../utils/json-utils.js` instead.
|
|
* Kept as re-export for backward compatibility.
|
|
*/
|
|
export { readJsonSafe } from '../../utils/json-utils.js';
|
|
|
|
export function writeJsonFileAtomic(filepath: string, data: any): void {
|
|
ensureDirectoryExists(dirname(filepath));
|
|
writeFileSync(filepath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
}
|