| … | … | @@ -166,6 +166,11 @@ export class FileManager { |
| 166 | 166 | */ |
| 167 | 167 | static getHyperidentPath() { |
| 168 | 168 | if (FileManager._customPath) return FileManager._customPath |
| 169 | // HYPERIDENT_HOME redirects the vault WITHOUT a code change, so a test run or CI job can be | |
| 170 | // isolated from the operator's real identity. This existed only as setBasePath(), which the test | |
| 171 | // suite never called — so `npm test` deleted the real ~/.hyperident of whoever ran it. That cost a | |
| 172 | // live vault on 2026-08-17 (hyper-social #47). | |
| 173 | if (process.env.HYPERIDENT_HOME) return path.join(process.env.HYPERIDENT_HOME, '.hyperident') | |
| 169 | 174 | const homeDir = os.homedir() |
| 170 | 175 | return path.join(homeDir, '.hyperident') |
| 171 | 176 | } |
| … | … | @@ -1,6 +1,6 @@ |
| 1 | 1 | { |
| 2 | 2 | "name": "hyperident", |
| 3 | "version": "0.2.4", | |
| 3 | "version": "0.2.5", | |
| 4 | 4 | "main": "index.js", |
| 5 | 5 | "bin": { |
| 6 | 6 | "hyperident": "./index.js" |
| … | … | @@ -6,10 +6,28 @@ import fs from 'fs' |
| 6 | 6 | import path from 'path' |
| 7 | 7 | import os from 'os' |
| 8 | 8 | |
| 9 | // ISOLATION. This suite used to resolve the vault via os.homedir() and delete it — so `npm test` | |
| 10 | // destroyed the real ~/.hyperident of whoever ran it, on the FIRST test, every time. It did exactly | |
| 11 | // that on 2026-08-17 (hyper-social #47). Two defences, because an env var alone fails OPEN if someone | |
| 12 | // forgets it: | |
| 13 | // 1. redirect the vault into a temp dir for the whole run | |
| 14 | // 2. a hard guard that REFUSES to delete the real path even if 1 is missing | |
| 15 | const TEST_HOME = fs.mkdtempSync(path.join(os.tmpdir(), 'hyperident-test-')) | |
| 16 | process.env.HYPERIDENT_HOME = TEST_HOME | |
| 17 | FileManager.setBasePath(path.join(TEST_HOME, '.hyperident')) | |
| 18 | ||
| 19 | const REAL_VAULT = path.resolve(path.join(os.homedir(), '.hyperident')) | |
| 20 | function assertNotRealVault (p) { | |
| 21 | if (path.resolve(p) === REAL_VAULT) { | |
| 22 | throw new Error('REFUSING to delete the real vault at ' + p + ' — a test must redirect via FileManager.setBasePath()/HYPERIDENT_HOME first') | |
| 23 | } | |
| 24 | } | |
| 25 | ||
| 9 | 26 | // Test helper to clean up test files |
| 10 | 27 | async function cleanupTestFiles() { |
| 11 | 28 | try { |
| 12 | const hyperidentPath = path.join(os.homedir(), '.hyperident') | |
| 29 | const hyperidentPath = FileManager.getHyperidentPath() | |
| 30 | assertNotRealVault(hyperidentPath) | |
| 13 | 31 | if (fs.existsSync(hyperidentPath)) { |
| 14 | 32 | // Remove all files in the directory |
| 15 | 33 | const files = fs.readdirSync(hyperidentPath) |
| … | … | @@ -1,10 +1,18 @@ |
| 1 | 1 | import test from 'brittle' |
| 2 | 2 | import { parseArgs, validateArgs, displayError, showHelp, showVersion, handleInit, handleCreateProfile, handleListProfiles, handleDeleteProfile, handleAddress } from '../index.js' |
| 3 | 3 | import { IdentityManager } from '../lib/identity-manager.js' |
| 4 | import { FileManager } from '../lib/file-ops.js' | |
| 4 | 5 | import fs from 'fs' |
| 5 | 6 | import path from 'path' |
| 6 | 7 | import os from 'os' |
| 7 | 8 | |
| 9 | // ISOLATION (hyper-social #47): this suite used to rmSync the operator's REAL ~/.hyperident. Redirect | |
| 10 | // the vault into a temp dir for the whole run; the guard in cleanupTestIdentity() is the second line | |
| 11 | // of defence if this is ever removed. | |
| 12 | const TEST_HOME = fs.mkdtempSync(path.join(os.tmpdir(), 'hyperident-idx-')) | |
| 13 | process.env.HYPERIDENT_HOME = TEST_HOME | |
| 14 | FileManager.setBasePath(path.join(TEST_HOME, '.hyperident')) | |
| 15 | ||
| 8 | 16 | // Mock console methods for testing output |
| 9 | 17 | let consoleOutput = [] |
| 10 | 18 | let consoleErrorOutput = [] |
| … | … | @@ -373,7 +381,13 @@ test('Complex argument parsing scenarios', (t) => { |
| 373 | 381 | // Helper function to clean up test identity |
| 374 | 382 | async function cleanupTestIdentity() { |
| 375 | 383 | try { |
| 376 | const hyperidentPath = path.join(os.homedir(), '.hyperident') | |
| 384 | // See identity-manager.test.js: this used to rmSync the operator's REAL vault (#47). | |
| 385 | const hyperidentPath = process.env.HYPERIDENT_HOME | |
| 386 | ? path.join(process.env.HYPERIDENT_HOME, '.hyperident') | |
| 387 | : path.join(os.homedir(), '.hyperident') | |
| 388 | if (path.resolve(hyperidentPath) === path.resolve(path.join(os.homedir(), '.hyperident'))) { | |
| 389 | throw new Error('REFUSING to delete the real vault — set HYPERIDENT_HOME to a temp dir first') | |
| 390 | } | |
| 377 | 391 | if (fs.existsSync(hyperidentPath)) { |
| 378 | 392 | fs.rmSync(hyperidentPath, { recursive: true, force: true }) |
| 379 | 393 | } |