UNPKG

@huangjunsen/vault-cli

Version:

macOS vault CLI: hide/unhide directories with .noindex and manage encrypted sparsebundles

129 lines (114 loc) 4.15 kB
const fs = require('fs'); const path = require('path'); const os = require('os'); const { execSync, spawnSync } = require('child_process'); function isMacOS() { return process.platform === 'darwin'; } function printInfo(msg) { console.log(`[i] ${msg}`); } function printSuccess(msg) { console.log(`[✓] ${msg}`); } function printError(msg) { console.error(`[x] ${msg}`); } function loadConfig(configPath) { if (!fs.existsSync(configPath)) { throw new Error(`Config not found: ${configPath}\nTwo supported formats:\n1) Simple array of paths (recommended):\n[\n "/Users/you/Projects/SecretA",\n "/Users/you/Projects/SecretB"\n]\n2) Advanced targets array (back-compat):\n{\n "targets": [\n { "name": "company", "path": "/Users/you/Company", "mode": "rename_noindex" },\n { "name": "vault", "image": "/Users/you/SecureVault.sparsebundle", "mountpoint": "/Volumes/SecureVault", "mode": "sparsebundle" }\n ]\n}`); } const raw = fs.readFileSync(configPath, 'utf8'); let cfg; try { cfg = JSON.parse(raw); } catch (e) { throw new Error(`Invalid JSON in config: ${configPath}`); } // If config is a plain array of strings -> treat as paths if (Array.isArray(cfg)) { const targets = cfg.map((p, idx) => { if (typeof p !== 'string') throw new Error('Path entries must be strings.'); const base = path.basename(p); const name = base.replace(/[^a-zA-Z0-9_.-]/g, '_') || `t${idx+1}`; return { name, path: p, mode: 'rename_noindex' }; }); return { targets }; } // Advanced object form with targets if (!cfg.targets || !Array.isArray(cfg.targets)) { throw new Error('Config must be an array of paths OR an object with "targets" array.'); } for (const t of cfg.targets) { if (!t.name) throw new Error('Each target must have a "name".'); if (!t.mode) throw new Error(`Target ${t.name} missing "mode".`); if (t.group && typeof t.group !== 'string') throw new Error(`Target ${t.name} has invalid group; expected string.`); if (t.mode === 'rename_noindex') { if (!t.path) throw new Error(`Target ${t.name} needs "path" for rename_noindex mode.`); } else if (t.mode === 'sparsebundle') { if (!t.image || !t.mountpoint) { throw new Error(`Target ${t.name} needs "image" and "mountpoint" for sparsebundle mode.`); } } else { throw new Error(`Unknown mode for target ${t.name}: ${t.mode}`); } } return cfg; } function findTarget(targets, nameOrPath) { const list = targets || []; const foundByName = list.find(t => t.name === nameOrPath); if (foundByName) return foundByName; // Match by exact path if provided const norm = nameOrPath && path.resolve(nameOrPath); const foundByPath = list.find(t => t.path && path.resolve(t.path) === norm); return foundByPath; } function filterTargets(targets, { names, group, all }) { let list = targets || []; if (all) return list; if (group) list = list.filter(t => t.group === group); if (names && names.length) list = list.filter(t => names.includes(t.name)); return list; } function ensureDir(dir) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } function run(cmd, args = [], opts = {}) { const res = spawnSync(cmd, args, { stdio: 'pipe', encoding: 'utf8', ...opts }); if (res.error) throw res.error; if (res.status !== 0) { const msg = res.stderr || res.stdout || `Command failed: ${cmd} ${args.join(' ')}`; const err = new Error(msg.trim()); err.code = res.status; throw err; } return res.stdout.trim(); } function runInherit(cmd, args = [], opts = {}) { const res = spawnSync(cmd, args, { stdio: 'inherit', ...opts }); if (res.error) throw res.error; if (res.status !== 0) { const err = new Error(`Command failed (${res.status}): ${cmd} ${args.join(' ')}`); err.code = res.status; throw err; } } function pathExists(p) { try { fs.accessSync(p); return true; } catch { return false; } } module.exports = { isMacOS, printInfo, printSuccess, printError, loadConfig, findTarget, filterTargets, ensureDir, run, runInherit, pathExists, };