First foundation lib for v1.12.0 auto-KB-update. Resolves per-OS paths:
- macOS: ~/Library/{Caches,Logs,Application Support}/<app>/
- Linux: XDG_CACHE_HOME / XDG_STATE_HOME with ~/.cache, ~/.local/state fallbacks
- Windows: %LOCALAPPDATA%\<app>\{Cache,Logs,State}
Plus getBackupDir(pluginRoot) → <pluginRoot>/.kb-backup (gitignored).
All four functions auto-mkdir target. Dependency-injection via opts
({platform, homedir, env}) makes the lib pure-testable; 13/13 tests
pass under tmpdir isolation without touching real ~/ paths.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
105 lines
3.4 KiB
JavaScript
105 lines
3.4 KiB
JavaScript
// cross-platform-paths.mjs — Cache/Log/State/Backup dir resolution per OS.
|
|
// Zero dependencies. macOS uses ~/Library/{Caches,Logs,Application Support},
|
|
// Linux uses XDG with fallbacks, Windows uses %LOCALAPPDATA%.
|
|
|
|
import { mkdirSync } from 'node:fs';
|
|
import { homedir as osHomedir, platform as osPlatform } from 'node:os';
|
|
import { join } from 'node:path';
|
|
|
|
function resolveOpts(opts = {}) {
|
|
return {
|
|
platform: opts.platform ?? osPlatform(),
|
|
homedir: opts.homedir ?? osHomedir,
|
|
env: opts.env ?? process.env,
|
|
};
|
|
}
|
|
|
|
function ensureDir(path) {
|
|
mkdirSync(path, { recursive: true });
|
|
return path;
|
|
}
|
|
|
|
function requireApp(appName) {
|
|
if (!appName || typeof appName !== 'string') {
|
|
throw new Error('cross-platform-paths: appName is required and must be a non-empty string');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve a per-app cache directory.
|
|
* @param {string} appName — application identifier, e.g. "ms-ai-architect"
|
|
* @param {object} [opts] — { platform, homedir, env } for testing
|
|
* @returns {string} absolute path to the cache directory (created if missing)
|
|
*/
|
|
export function getCacheDir(appName, opts) {
|
|
requireApp(appName);
|
|
const { platform, homedir, env } = resolveOpts(opts);
|
|
const home = homedir();
|
|
|
|
if (platform === 'darwin') {
|
|
return ensureDir(join(home, 'Library', 'Caches', appName));
|
|
}
|
|
if (platform === 'win32') {
|
|
const lad = env.LOCALAPPDATA || join(home, 'AppData', 'Local');
|
|
return ensureDir(join(lad, appName, 'Cache'));
|
|
}
|
|
// linux + everything else
|
|
const xdg = env.XDG_CACHE_HOME || join(home, '.cache');
|
|
return ensureDir(join(xdg, appName));
|
|
}
|
|
|
|
/**
|
|
* Resolve a per-app log directory.
|
|
* @param {string} appName
|
|
* @param {object} [opts]
|
|
* @returns {string} absolute path to the log directory (created if missing)
|
|
*/
|
|
export function getLogDir(appName, opts) {
|
|
requireApp(appName);
|
|
const { platform, homedir, env } = resolveOpts(opts);
|
|
const home = homedir();
|
|
|
|
if (platform === 'darwin') {
|
|
return ensureDir(join(home, 'Library', 'Logs', appName));
|
|
}
|
|
if (platform === 'win32') {
|
|
const lad = env.LOCALAPPDATA || join(home, 'AppData', 'Local');
|
|
return ensureDir(join(lad, appName, 'Logs'));
|
|
}
|
|
const xdg = env.XDG_STATE_HOME || join(home, '.local', 'state');
|
|
return ensureDir(join(xdg, appName, 'logs'));
|
|
}
|
|
|
|
/**
|
|
* Resolve a per-app state/data directory (persistent app state, not cache).
|
|
* @param {string} appName
|
|
* @param {object} [opts]
|
|
* @returns {string} absolute path (created if missing)
|
|
*/
|
|
export function getStateDir(appName, opts) {
|
|
requireApp(appName);
|
|
const { platform, homedir, env } = resolveOpts(opts);
|
|
const home = homedir();
|
|
|
|
if (platform === 'darwin') {
|
|
return ensureDir(join(home, 'Library', 'Application Support', appName));
|
|
}
|
|
if (platform === 'win32') {
|
|
const lad = env.LOCALAPPDATA || join(home, 'AppData', 'Local');
|
|
return ensureDir(join(lad, appName, 'State'));
|
|
}
|
|
const xdg = env.XDG_STATE_HOME || join(home, '.local', 'state');
|
|
return ensureDir(join(xdg, appName));
|
|
}
|
|
|
|
/**
|
|
* Resolve the backup directory under a plugin root.
|
|
* @param {string} pluginRoot — absolute path to the plugin root
|
|
* @returns {string} absolute path to <pluginRoot>/.kb-backup (created if missing)
|
|
*/
|
|
export function getBackupDir(pluginRoot) {
|
|
if (!pluginRoot || typeof pluginRoot !== 'string') {
|
|
throw new Error('cross-platform-paths: pluginRoot is required and must be a non-empty string');
|
|
}
|
|
return ensureDir(join(pluginRoot, '.kb-backup'));
|
|
}
|