crbro-memory 2.9.1 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +256 -456
- package/SECURITY.md +135 -8
- package/bin/crbro.mjs +555 -299
- package/dist/build-id.json +10 -0
- package/dist/cli/common.js +211 -0
- package/dist/cli/export.js +522 -0
- package/dist/cli/import.js +683 -0
- package/dist/cli/markdown.js +313 -0
- package/dist/cli/mcpclient.js +107 -0
- package/dist/cli/serve.js +453 -0
- package/dist/cli/view.js +358 -0
- package/dist/cli/viewer-page.js +378 -0
- package/dist/daemon/daemon.js +119 -11
- package/dist/daemon/endpoint.js +222 -21
- package/dist/daemon/lines.js +102 -13
- package/dist/daemon/proxy.js +480 -125
- package/dist/daemon/surface.js +116 -0
- package/dist/daemon/trace.js +73 -0
- package/dist/engine/atrest.js +132 -0
- package/dist/engine/backup.js +143 -19
- package/dist/engine/brain.js +22 -6
- package/dist/engine/codexconfig.js +476 -0
- package/dist/engine/cortex.js +762 -311
- package/dist/engine/doctor.js +676 -0
- package/dist/engine/evidence.js +171 -0
- package/dist/engine/heat.js +81 -37
- package/dist/engine/hippocampus.js +50 -36
- package/dist/engine/hookrefresh.js +296 -0
- package/dist/engine/hookverify.js +15 -3
- package/dist/engine/installer.js +1022 -0
- package/dist/engine/installio.js +279 -0
- package/dist/engine/keychain.js +735 -144
- package/dist/engine/maintenance.js +355 -87
- package/dist/engine/modinstall.js +98 -45
- package/dist/engine/prefrontal.js +126 -69
- package/dist/engine/projects.js +80 -0
- package/dist/engine/quarantine.js +204 -0
- package/dist/engine/secrets.js +41 -5
- package/dist/engine/shelf.js +34 -11
- package/dist/engine/synapses.js +183 -80
- package/dist/engine/triggers.js +141 -11
- package/dist/index.js +37 -3
- package/dist/mcp-surface.json +1 -0
- package/dist/miner/extractor.js +64 -17
- package/dist/miner/scheduler.js +144 -68
- package/dist/search/index.js +810 -176
- package/dist/search/quality.js +31 -0
- package/dist/search/semantic.js +208 -18
- package/dist/search/tokenize.js +44 -0
- package/dist/server.js +649 -343
- package/dist/sync/git.js +203 -72
- package/dist/sync/materialize.js +33 -8
- package/dist/sync/space.js +286 -101
- package/dist/sync/validate.js +204 -0
- package/dist/utils/budget.js +15 -2
- package/dist/utils/fs.js +40 -6
- package/dist/utils/ids.js +128 -14
- package/dist/utils/lock.js +219 -52
- package/hooks/crbro-codex.mjs +284 -0
- package/hooks/crbro-guard.mjs +281 -173
- package/hooks/crbro-hooks.shipped.json +20 -0
- package/hooks/crbro-lifecycle.mjs +16 -4
- package/hooks/crbro-subagent.mjs +180 -180
- package/package.json +12 -8
- package/dist/daemon/daemon.d.ts +0 -25
- package/dist/daemon/daemon.d.ts.map +0 -1
- package/dist/daemon/daemon.js.map +0 -1
- package/dist/daemon/endpoint.d.ts +0 -66
- package/dist/daemon/endpoint.d.ts.map +0 -1
- package/dist/daemon/endpoint.js.map +0 -1
- package/dist/daemon/lines.d.ts +0 -55
- package/dist/daemon/lines.d.ts.map +0 -1
- package/dist/daemon/lines.js.map +0 -1
- package/dist/daemon/proxy.d.ts +0 -55
- package/dist/daemon/proxy.d.ts.map +0 -1
- package/dist/daemon/proxy.js.map +0 -1
- package/dist/engine/backup.d.ts +0 -68
- package/dist/engine/backup.d.ts.map +0 -1
- package/dist/engine/backup.js.map +0 -1
- package/dist/engine/brain.d.ts +0 -79
- package/dist/engine/brain.d.ts.map +0 -1
- package/dist/engine/brain.js.map +0 -1
- package/dist/engine/cortex.d.ts +0 -670
- package/dist/engine/cortex.d.ts.map +0 -1
- package/dist/engine/cortex.js.map +0 -1
- package/dist/engine/dates.d.ts +0 -28
- package/dist/engine/dates.d.ts.map +0 -1
- package/dist/engine/dates.js.map +0 -1
- package/dist/engine/heat.d.ts +0 -28
- package/dist/engine/heat.d.ts.map +0 -1
- package/dist/engine/heat.js.map +0 -1
- package/dist/engine/hippocampus.d.ts +0 -60
- package/dist/engine/hippocampus.d.ts.map +0 -1
- package/dist/engine/hippocampus.js.map +0 -1
- package/dist/engine/hookverify.d.ts +0 -42
- package/dist/engine/hookverify.d.ts.map +0 -1
- package/dist/engine/hookverify.js.map +0 -1
- package/dist/engine/keychain.d.ts +0 -32
- package/dist/engine/keychain.d.ts.map +0 -1
- package/dist/engine/keychain.js.map +0 -1
- package/dist/engine/maintenance.d.ts +0 -218
- package/dist/engine/maintenance.d.ts.map +0 -1
- package/dist/engine/maintenance.js.map +0 -1
- package/dist/engine/modinstall.d.ts +0 -215
- package/dist/engine/modinstall.d.ts.map +0 -1
- package/dist/engine/modinstall.js.map +0 -1
- package/dist/engine/postmortem.d.ts +0 -81
- package/dist/engine/postmortem.d.ts.map +0 -1
- package/dist/engine/postmortem.js.map +0 -1
- package/dist/engine/prefrontal.d.ts +0 -61
- package/dist/engine/prefrontal.d.ts.map +0 -1
- package/dist/engine/prefrontal.js.map +0 -1
- package/dist/engine/secrets.d.ts +0 -30
- package/dist/engine/secrets.d.ts.map +0 -1
- package/dist/engine/secrets.js.map +0 -1
- package/dist/engine/shelf.d.ts +0 -156
- package/dist/engine/shelf.d.ts.map +0 -1
- package/dist/engine/shelf.js.map +0 -1
- package/dist/engine/source.d.ts +0 -6
- package/dist/engine/source.d.ts.map +0 -1
- package/dist/engine/source.js.map +0 -1
- package/dist/engine/synapses.d.ts +0 -82
- package/dist/engine/synapses.d.ts.map +0 -1
- package/dist/engine/synapses.js.map +0 -1
- package/dist/engine/triggers.d.ts +0 -53
- package/dist/engine/triggers.d.ts.map +0 -1
- package/dist/engine/triggers.js.map +0 -1
- package/dist/engine/usage.d.ts +0 -61
- package/dist/engine/usage.d.ts.map +0 -1
- package/dist/engine/usage.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/miner/extractor.d.ts +0 -12
- package/dist/miner/extractor.d.ts.map +0 -1
- package/dist/miner/extractor.js.map +0 -1
- package/dist/miner/index.d.ts +0 -61
- package/dist/miner/index.d.ts.map +0 -1
- package/dist/miner/index.js.map +0 -1
- package/dist/miner/scheduler.d.ts +0 -30
- package/dist/miner/scheduler.d.ts.map +0 -1
- package/dist/miner/scheduler.js.map +0 -1
- package/dist/miner/types.d.ts +0 -11
- package/dist/miner/types.d.ts.map +0 -1
- package/dist/miner/types.js.map +0 -1
- package/dist/search/index.d.ts +0 -323
- package/dist/search/index.d.ts.map +0 -1
- package/dist/search/index.js.map +0 -1
- package/dist/search/semantic.d.ts +0 -80
- package/dist/search/semantic.d.ts.map +0 -1
- package/dist/search/semantic.js.map +0 -1
- package/dist/search/synonyms.d.ts +0 -2
- package/dist/search/synonyms.d.ts.map +0 -1
- package/dist/search/synonyms.js.map +0 -1
- package/dist/search/tokenize.d.ts +0 -39
- package/dist/search/tokenize.d.ts.map +0 -1
- package/dist/search/tokenize.js.map +0 -1
- package/dist/server.d.ts +0 -41
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/sync/git.d.ts +0 -36
- package/dist/sync/git.d.ts.map +0 -1
- package/dist/sync/git.js.map +0 -1
- package/dist/sync/materialize.d.ts +0 -35
- package/dist/sync/materialize.d.ts.map +0 -1
- package/dist/sync/materialize.js.map +0 -1
- package/dist/sync/ops.d.ts +0 -142
- package/dist/sync/ops.d.ts.map +0 -1
- package/dist/sync/ops.js.map +0 -1
- package/dist/sync/space.d.ts +0 -116
- package/dist/sync/space.d.ts.map +0 -1
- package/dist/sync/space.js.map +0 -1
- package/dist/types/index.d.ts +0 -346
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/utils/budget.d.ts +0 -44
- package/dist/utils/budget.d.ts.map +0 -1
- package/dist/utils/budget.js.map +0 -1
- package/dist/utils/fs.d.ts +0 -59
- package/dist/utils/fs.d.ts.map +0 -1
- package/dist/utils/fs.js.map +0 -1
- package/dist/utils/hash.d.ts +0 -20
- package/dist/utils/hash.d.ts.map +0 -1
- package/dist/utils/hash.js.map +0 -1
- package/dist/utils/ids.d.ts +0 -47
- package/dist/utils/ids.d.ts.map +0 -1
- package/dist/utils/ids.js.map +0 -1
- package/dist/utils/lock.d.ts +0 -11
- package/dist/utils/lock.d.ts.map +0 -1
- package/dist/utils/lock.js.map +0 -1
- package/dist/utils/transcripts.d.ts +0 -63
- package/dist/utils/transcripts.d.ts.map +0 -1
- package/dist/utils/transcripts.js.map +0 -1
- package/dist/version.d.ts +0 -26
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─── Where the clients keep their config, and how CRBRO writes to it ─────
|
|
3
|
+
//
|
|
4
|
+
// Every installer in this package writes into files that belong to another
|
|
5
|
+
// program: Claude Code's settings.json, Codex's hooks.json and config.toml,
|
|
6
|
+
// Claude Desktop's and Cursor's MCP lists. Those files are the user's, and an
|
|
7
|
+
// editor, the client itself or a dotfiles manager may be holding them. So
|
|
8
|
+
// every write goes through writeGuarded():
|
|
9
|
+
//
|
|
10
|
+
// - a timestamped copy of the file as it was is kept beside it first
|
|
11
|
+
// (<file>.crbro-bak-<UTC stamp>; the newest few per file are kept);
|
|
12
|
+
// - the file is hashed again right before the rename, and nothing is written
|
|
13
|
+
// when it changed after it was read (ESETTINGSCHANGED: read and merge again);
|
|
14
|
+
// - a symlinked file is written at its target, so the link stays a link, and
|
|
15
|
+
// the permission bits and line endings it had are kept.
|
|
16
|
+
//
|
|
17
|
+
// modinstall.ts grew this first for the Claude Code mod; it now uses this one.
|
|
18
|
+
//
|
|
19
|
+
// The folders honour the variables the clients themselves honour:
|
|
20
|
+
// CLAUDE_CONFIG_DIR (Claude Code) and CODEX_HOME (Codex).
|
|
21
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
22
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.BACKUPS_KEPT = exports.BACKUP_TAG = exports.FILE_CHANGED = void 0;
|
|
26
|
+
exports.sha256 = sha256;
|
|
27
|
+
exports.lfSha = lfSha;
|
|
28
|
+
exports.claudeDir = claudeDir;
|
|
29
|
+
exports.claudeJson = claudeJson;
|
|
30
|
+
exports.codexHome = codexHome;
|
|
31
|
+
exports.claudeDesktopConfig = claudeDesktopConfig;
|
|
32
|
+
exports.cursorConfig = cursorConfig;
|
|
33
|
+
exports.engineRoot = engineRoot;
|
|
34
|
+
exports.findOnPath = findOnPath;
|
|
35
|
+
exports.readText = readText;
|
|
36
|
+
exports.fingerprint = fingerprint;
|
|
37
|
+
exports.readJsonObject = readJsonObject;
|
|
38
|
+
exports.backupsOf = backupsOf;
|
|
39
|
+
exports.backupFile = backupFile;
|
|
40
|
+
exports.writeGuarded = writeGuarded;
|
|
41
|
+
exports.jsonText = jsonText;
|
|
42
|
+
exports.updateJsonFile = updateJsonFile;
|
|
43
|
+
const crypto_1 = require("crypto");
|
|
44
|
+
const fs_1 = require("fs");
|
|
45
|
+
const path_1 = __importDefault(require("path"));
|
|
46
|
+
function sha256(buf) {
|
|
47
|
+
return (0, crypto_1.createHash)('sha256').update(buf).digest('hex');
|
|
48
|
+
}
|
|
49
|
+
/** SHA-256 of a text read with LF line endings: a CRLF checkout and the npm copy hash the same. */
|
|
50
|
+
function lfSha(buf) {
|
|
51
|
+
const text = typeof buf === 'string' ? buf : buf.toString('utf8');
|
|
52
|
+
return sha256(Buffer.from(text.replace(/\r\n/g, '\n'), 'utf8'));
|
|
53
|
+
}
|
|
54
|
+
// ─── Folders ─────────────────────────────────────────────────────
|
|
55
|
+
function fromEnv(value, home) {
|
|
56
|
+
const raw = (value || '').trim();
|
|
57
|
+
if (!raw || /\$\{|%[A-Za-z_][A-Za-z0-9_]*%/.test(raw))
|
|
58
|
+
return null;
|
|
59
|
+
if (raw === '~' || raw.startsWith('~/') || raw.startsWith('~\\'))
|
|
60
|
+
return path_1.default.join(home, raw.slice(1));
|
|
61
|
+
return path_1.default.resolve(raw);
|
|
62
|
+
}
|
|
63
|
+
/** Claude Code's config folder: CLAUDE_CONFIG_DIR, else ~/.claude. */
|
|
64
|
+
function claudeDir(home, env = process.env) {
|
|
65
|
+
return fromEnv(env.CLAUDE_CONFIG_DIR, home) ?? path_1.default.join(home, '.claude');
|
|
66
|
+
}
|
|
67
|
+
/** Where Claude Code keeps user-scope MCP servers: <CLAUDE_CONFIG_DIR>/.claude.json, else ~/.claude.json. */
|
|
68
|
+
function claudeJson(home, env = process.env) {
|
|
69
|
+
const dir = fromEnv(env.CLAUDE_CONFIG_DIR, home);
|
|
70
|
+
const inHome = path_1.default.join(home, '.claude.json');
|
|
71
|
+
if (!dir)
|
|
72
|
+
return inHome;
|
|
73
|
+
// With CLAUDE_CONFIG_DIR set, Claude Code keeps it in that folder; an older
|
|
74
|
+
// ~/.claude.json is still read when the folder has none.
|
|
75
|
+
const inDir = path_1.default.join(dir, '.claude.json');
|
|
76
|
+
return (0, fs_1.existsSync)(inDir) || !(0, fs_1.existsSync)(inHome) ? inDir : inHome;
|
|
77
|
+
}
|
|
78
|
+
/** Codex's folder: CODEX_HOME, else ~/.codex. */
|
|
79
|
+
function codexHome(home, env = process.env) {
|
|
80
|
+
return fromEnv(env.CODEX_HOME, home) ?? path_1.default.join(home, '.codex');
|
|
81
|
+
}
|
|
82
|
+
function claudeDesktopConfig(home, env = process.env, platform = process.platform) {
|
|
83
|
+
if (platform === 'win32')
|
|
84
|
+
return path_1.default.join(env.APPDATA || path_1.default.join(home, 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');
|
|
85
|
+
if (platform === 'darwin')
|
|
86
|
+
return path_1.default.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
87
|
+
return path_1.default.join(env.XDG_CONFIG_HOME || path_1.default.join(home, '.config'), 'Claude', 'claude_desktop_config.json');
|
|
88
|
+
}
|
|
89
|
+
function cursorConfig(home) {
|
|
90
|
+
return path_1.default.join(home, '.cursor', 'mcp.json');
|
|
91
|
+
}
|
|
92
|
+
/** The stable engine folder `crbro-memory install` copies the package into. */
|
|
93
|
+
function engineRoot(home, env = process.env) {
|
|
94
|
+
return fromEnv(env.CRBRO_ENGINE_DIR, home) ?? path_1.default.join(home, '.crbro-engine');
|
|
95
|
+
}
|
|
96
|
+
/** `claude`, `claude.cmd`… on PATH; null when absent. */
|
|
97
|
+
function findOnPath(name, env = process.env, platform = process.platform) {
|
|
98
|
+
const dirs = (env.PATH || env.Path || '').split(platform === 'win32' ? ';' : ':').filter(Boolean);
|
|
99
|
+
const exts = platform === 'win32' ? (env.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean) : [''];
|
|
100
|
+
for (const dir of dirs) {
|
|
101
|
+
for (const ext of exts) {
|
|
102
|
+
const p = path_1.default.join(dir.replace(/^"|"$/g, ''), name + ext.toLowerCase());
|
|
103
|
+
try {
|
|
104
|
+
if ((0, fs_1.statSync)(p).isFile())
|
|
105
|
+
return p;
|
|
106
|
+
}
|
|
107
|
+
catch { /* next */ }
|
|
108
|
+
if (ext) {
|
|
109
|
+
const upper = path_1.default.join(dir.replace(/^"|"$/g, ''), name + ext);
|
|
110
|
+
try {
|
|
111
|
+
if ((0, fs_1.statSync)(upper).isFile())
|
|
112
|
+
return upper;
|
|
113
|
+
}
|
|
114
|
+
catch { /* next */ }
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
function readText(file) {
|
|
121
|
+
if (!(0, fs_1.existsSync)(file))
|
|
122
|
+
return { exists: false, text: '', hash: 'absent', eol: '\n', bom: false };
|
|
123
|
+
const buf = (0, fs_1.readFileSync)(file);
|
|
124
|
+
let text = buf.toString('utf8');
|
|
125
|
+
const bom = text.charCodeAt(0) === 0xfeff;
|
|
126
|
+
if (bom)
|
|
127
|
+
text = text.slice(1);
|
|
128
|
+
return { exists: true, text, hash: sha256(buf), eol: /\r\n/.test(text) ? '\r\n' : '\n', bom };
|
|
129
|
+
}
|
|
130
|
+
/** What a file holds right now, as a hash, or "absent". */
|
|
131
|
+
function fingerprint(file) {
|
|
132
|
+
try {
|
|
133
|
+
return sha256((0, fs_1.readFileSync)(file));
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return 'absent';
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const isObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
140
|
+
/** A JSON object file; null when absent. Anything that is not an object is refused, never guessed at. */
|
|
141
|
+
function readJsonObject(file) {
|
|
142
|
+
let read;
|
|
143
|
+
try {
|
|
144
|
+
read = readText(file);
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
return { ok: false, error: `${file} could not be read — not touching it.\n ${e.message}` };
|
|
148
|
+
}
|
|
149
|
+
if (!read.exists)
|
|
150
|
+
return { ok: true, value: null, indent: 2, eol: '\n', bom: false, hash: 'absent' };
|
|
151
|
+
let value;
|
|
152
|
+
try {
|
|
153
|
+
value = read.text.trim() ? JSON.parse(read.text) : {};
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
return { ok: false, error: `${file} exists but could not be parsed — not touching it.\n ${e.message}` };
|
|
157
|
+
}
|
|
158
|
+
if (!isObject(value))
|
|
159
|
+
return { ok: false, error: `${file} is not a JSON object — not touching it.` };
|
|
160
|
+
const indent = /\n([ \t]+)"/.exec(read.text)?.[1] ?? 2;
|
|
161
|
+
return { ok: true, value, indent, eol: read.eol, bom: read.bom, hash: read.hash };
|
|
162
|
+
}
|
|
163
|
+
// ─── Writing ─────────────────────────────────────────────────────
|
|
164
|
+
/** Thrown by writeGuarded when the file changed after it was read: the caller reads and merges again. */
|
|
165
|
+
exports.FILE_CHANGED = 'ESETTINGSCHANGED';
|
|
166
|
+
exports.BACKUP_TAG = '.crbro-bak-';
|
|
167
|
+
/** Backups kept per file; older ones of the same file go. */
|
|
168
|
+
exports.BACKUPS_KEPT = 5;
|
|
169
|
+
function stampOf(d) {
|
|
170
|
+
return d.toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z');
|
|
171
|
+
}
|
|
172
|
+
/** The backups of one file, oldest first. */
|
|
173
|
+
function backupsOf(file) {
|
|
174
|
+
const dir = path_1.default.dirname(file);
|
|
175
|
+
const prefix = path_1.default.basename(file) + exports.BACKUP_TAG;
|
|
176
|
+
try {
|
|
177
|
+
return (0, fs_1.readdirSync)(dir).filter(n => n.startsWith(prefix)).sort().map(n => path_1.default.join(dir, n));
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/** Copies the file aside, returns the copy's path, or null when there was nothing to copy. */
|
|
184
|
+
function backupFile(file, now = () => new Date()) {
|
|
185
|
+
if (!(0, fs_1.existsSync)(file))
|
|
186
|
+
return null;
|
|
187
|
+
let target = `${file}${exports.BACKUP_TAG}${stampOf(now())}`;
|
|
188
|
+
for (let i = 1; (0, fs_1.existsSync)(target); i++)
|
|
189
|
+
target = `${file}${exports.BACKUP_TAG}${stampOf(now())}-${i}`;
|
|
190
|
+
(0, fs_1.copyFileSync)(file, target);
|
|
191
|
+
const all = backupsOf(file);
|
|
192
|
+
for (const old of all.slice(0, Math.max(0, all.length - exports.BACKUPS_KEPT))) {
|
|
193
|
+
try {
|
|
194
|
+
(0, fs_1.rmSync)(old, { force: true });
|
|
195
|
+
}
|
|
196
|
+
catch { /* the next write tries again */ }
|
|
197
|
+
}
|
|
198
|
+
return target;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Writes text through .tmp + rename, after a backup, and only if the file is
|
|
202
|
+
* still what was read. Returns the backup path (or null when the file did not
|
|
203
|
+
* exist before). On Windows the renamed file takes the folder's inherited ACL.
|
|
204
|
+
*/
|
|
205
|
+
function writeGuarded(file, text, opts = {}) {
|
|
206
|
+
let target = file;
|
|
207
|
+
try {
|
|
208
|
+
if ((0, fs_1.lstatSync)(file).isSymbolicLink())
|
|
209
|
+
target = (0, fs_1.realpathSync)(file);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// Not there yet: written as a plain file.
|
|
213
|
+
}
|
|
214
|
+
let mode;
|
|
215
|
+
try {
|
|
216
|
+
mode = (0, fs_1.statSync)(target).mode & 0o777;
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
mode = undefined;
|
|
220
|
+
}
|
|
221
|
+
if (opts.expectHash !== undefined && fingerprint(target) !== opts.expectHash)
|
|
222
|
+
throw changedError(file);
|
|
223
|
+
const backup = opts.backup === false ? null : backupFile(target, opts.now);
|
|
224
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
225
|
+
try {
|
|
226
|
+
(0, fs_1.writeFileSync)(tmp, text, mode === undefined ? 'utf8' : { encoding: 'utf8', mode });
|
|
227
|
+
if (opts.expectHash !== undefined && fingerprint(target) !== opts.expectHash)
|
|
228
|
+
throw changedError(file);
|
|
229
|
+
(0, fs_1.renameSync)(tmp, target);
|
|
230
|
+
}
|
|
231
|
+
catch (e) {
|
|
232
|
+
(0, fs_1.rmSync)(tmp, { force: true });
|
|
233
|
+
throw e;
|
|
234
|
+
}
|
|
235
|
+
return backup;
|
|
236
|
+
}
|
|
237
|
+
function changedError(file) {
|
|
238
|
+
const changed = new Error(`${file} changed while it was being written.`);
|
|
239
|
+
changed.code = exports.FILE_CHANGED;
|
|
240
|
+
return changed;
|
|
241
|
+
}
|
|
242
|
+
/** JSON in the indentation, line endings and BOM the file came with. */
|
|
243
|
+
function jsonText(value, indent = 2, eol = '\n', bom = false) {
|
|
244
|
+
let text = JSON.stringify(value, null, indent) + '\n';
|
|
245
|
+
if (eol !== '\n')
|
|
246
|
+
text = text.replace(/\n/g, eol);
|
|
247
|
+
return (bom ? '' : '') + text;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Read, change, write a JSON object file, merging again when somebody else
|
|
251
|
+
* wrote it in between (three tries). `change` returns false to write nothing.
|
|
252
|
+
*/
|
|
253
|
+
function updateJsonFile(file, change, opts = {}) {
|
|
254
|
+
for (let attempt = 0;; attempt++) {
|
|
255
|
+
const read = readJsonObject(file);
|
|
256
|
+
if (!read.ok)
|
|
257
|
+
return read;
|
|
258
|
+
const value = read.value ?? {};
|
|
259
|
+
let changed;
|
|
260
|
+
try {
|
|
261
|
+
changed = change(value, read.value !== null);
|
|
262
|
+
}
|
|
263
|
+
catch (e) {
|
|
264
|
+
return { ok: false, error: e.message };
|
|
265
|
+
}
|
|
266
|
+
if (!changed)
|
|
267
|
+
return { ok: true, changed: false, backup: null };
|
|
268
|
+
try {
|
|
269
|
+
const backup = writeGuarded(file, jsonText(value, read.indent, read.eol, read.bom), { expectHash: read.hash, backup: opts.backup });
|
|
270
|
+
return { ok: true, changed: true, backup };
|
|
271
|
+
}
|
|
272
|
+
catch (e) {
|
|
273
|
+
if (e.code === exports.FILE_CHANGED && attempt < 2)
|
|
274
|
+
continue;
|
|
275
|
+
return { ok: false, error: e.message };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=installio.js.map
|