clckernel 1.2.19 → 2.0.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/.agents/skills/clckernel_cli/SKILL.md +23 -6
- package/.agents/skills/clckernel_cli/reference/audit.md +13 -3
- package/.agents/skills/clckernel_cli/reference/commit.md +1 -1
- package/.agents/skills/clckernel_cli/reference/doctor.md +5 -3
- package/.agents/skills/clckernel_cli/reference/feature.md +3 -3
- package/.agents/skills/clckernel_cli/reference/guard.md +48 -12
- package/.agents/skills/clckernel_cli/reference/phase.md +6 -3
- package/.agents/skills/clckernel_cli/reference/start.md +14 -4
- package/.agents/skills/clckernel_cli/reference/test.md +1 -1
- package/.agents/skills/clckernel_cli/reference/update.md +19 -19
- package/CHANGELOG.md +244 -0
- package/LICENSE +21 -0
- package/README.es.md +280 -51
- package/README.md +274 -50
- package/bin/cli.js +8 -0
- package/package.json +12 -2
- package/src/adapters/agent-os.js +161 -84
- package/src/adapters/astro.js +8 -6
- package/src/adapters/base.js +23 -38
- package/src/adapters/django.js +14 -13
- package/src/adapters/fastapi.js +16 -15
- package/src/adapters/go.js +11 -9
- package/src/adapters/laravel.js +12 -10
- package/src/adapters/nextjs.js +7 -5
- package/src/adapters/rails.js +13 -11
- package/src/adapters/rust.js +11 -9
- package/src/catalog.js +58 -49
- package/src/config.js +38 -240
- package/src/detector.js +243 -44
- package/src/doctor.js +18 -9
- package/src/generator.js +734 -302
- package/src/gitignore.js +60 -14
- package/src/index.js +257 -18
- package/src/io.js +409 -0
- package/src/known-hashes.json +309 -0
- package/src/onboarding.js +15 -0
- package/src/technologies/detector.js +1 -1
- package/src/technologies/guards/_shared.js +1 -1
- package/src/ui.js +233 -40
- package/src/updater.js +271 -38
- package/tools/_changes.js +279 -0
- package/tools/_changes.py +233 -0
- package/tools/_config.js +294 -0
- package/tools/_config.py +271 -0
- package/tools/_yaml.py +489 -0
- package/tools/audit.js +511 -111
- package/tools/audit.py +506 -128
- package/tools/celery_guard.py +13 -35
- package/tools/check_a11y.js +8 -28
- package/tools/check_api_contracts.js +13 -39
- package/tools/check_architecture.js +35 -44
- package/tools/check_architecture.py +172 -105
- package/tools/check_custom.js +108 -61
- package/tools/check_custom.py +108 -271
- package/tools/check_data_files.js +88 -0
- package/tools/check_db_efficiency.py +80 -65
- package/tools/check_hitl.js +112 -69
- package/tools/check_links.js +138 -0
- package/tools/check_migrations.py +143 -94
- package/tools/check_performance.js +35 -44
- package/tools/check_placeholders.js +104 -0
- package/tools/check_responsive.js +8 -28
- package/tools/check_scope.py +40 -31
- package/tools/check_seo.js +8 -28
- package/tools/check_skills.js +158 -0
- package/tools/check_storybook.js +8 -21
- package/tools/check_symlinks.js +40 -22
- package/tools/check_ui_reuse.js +17 -29
- package/tools/docker_guard.py +19 -41
- package/tools/postgres_guard.py +15 -36
- package/tools/redis_guard.py +13 -35
- package/tools/scan_secrets.js +134 -67
- package/tools/scan_secrets.py +163 -72
- package/tools/verify_tdd.js +867 -0
- package/tools/verify_tdd.py +407 -165
- package/tools/yaml.js +404 -132
- package/tools/check_domain_data.js +0 -100
- package/tools/check_test_calendar_sync.js +0 -111
package/src/io.js
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLC Kernel — Single write point for install and update (sdds/safe-update).
|
|
3
|
+
*
|
|
4
|
+
* Every write the harness makes in a project goes through an `io` session:
|
|
5
|
+
* files, copies, symlinks, moves, removals, .gitignore and git config. A real
|
|
6
|
+
* session applies each operation and records it; a dry-run session only
|
|
7
|
+
* records it, so the same code path yields the plan `--dry-run` prints.
|
|
8
|
+
*
|
|
9
|
+
* Files the kernel owns (tools/*, the pre-commit hook, the clckernel_cli
|
|
10
|
+
* skill) are written with `writeManaged`, which applies the ownership rule:
|
|
11
|
+
*
|
|
12
|
+
* missing -> write
|
|
13
|
+
* identical to the new content -> nothing
|
|
14
|
+
* sha256 equals the registry's hash, or any
|
|
15
|
+
* version the kernel published (known-hashes) -> overwrite
|
|
16
|
+
* anything else -> keep it, write <file>.clckernel-new
|
|
17
|
+
*
|
|
18
|
+
* The registry (tools/.clckernel-managed.json) records, per owned file, the
|
|
19
|
+
* sha256 of what the kernel last wrote: { kernelVersion, files: [{ path, sha256 }] }.
|
|
20
|
+
* The older format (a sorted array of paths) is still read; it carries no hashes.
|
|
21
|
+
*
|
|
22
|
+
* Paths the project config lists in `update.exclude` are never written,
|
|
23
|
+
* moved, linked or removed: each is recorded as excluded and left as it is.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
const crypto = require('crypto');
|
|
29
|
+
const { spawnSync } = require('child_process');
|
|
30
|
+
|
|
31
|
+
const MANAGED_REGISTRY = 'tools/.clckernel-managed.json';
|
|
32
|
+
const NEW_SUFFIX = '.clckernel-new';
|
|
33
|
+
|
|
34
|
+
const sha256 = (bytes) => crypto.createHash('sha256').update(bytes).digest('hex');
|
|
35
|
+
const toPosix = (p) => p.split(path.sep).join('/');
|
|
36
|
+
|
|
37
|
+
/** sha256 of every published version of each kernel file (scripts/build-known-hashes.js). */
|
|
38
|
+
function loadKnownHashes() {
|
|
39
|
+
try {
|
|
40
|
+
return require('./known-hashes.json');
|
|
41
|
+
} catch {
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Reads the registry in either format.
|
|
48
|
+
* @param {string} root - Project root
|
|
49
|
+
* @returns {{ format: 'none'|'list'|'hashed', paths: string[], hashes: Map<string, string> }}
|
|
50
|
+
*/
|
|
51
|
+
function readRegistry(root) {
|
|
52
|
+
let data;
|
|
53
|
+
try {
|
|
54
|
+
data = JSON.parse(fs.readFileSync(path.join(root, MANAGED_REGISTRY), 'utf-8'));
|
|
55
|
+
} catch {
|
|
56
|
+
return { format: 'none', paths: [], hashes: new Map() };
|
|
57
|
+
}
|
|
58
|
+
if (Array.isArray(data)) {
|
|
59
|
+
return { format: 'list', paths: data.filter((f) => typeof f === 'string'), hashes: new Map() };
|
|
60
|
+
}
|
|
61
|
+
const files = data && Array.isArray(data.files) ? data.files : [];
|
|
62
|
+
const hashes = new Map();
|
|
63
|
+
for (const f of files) {
|
|
64
|
+
if (f && typeof f.path === 'string' && typeof f.sha256 === 'string') hashes.set(f.path, f.sha256);
|
|
65
|
+
}
|
|
66
|
+
return { format: 'hashed', paths: [...hashes.keys()], hashes };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Registered paths in either format, the registry itself included (the
|
|
71
|
+
* snapshot helpers and whole-project linters exclude them).
|
|
72
|
+
* @param {string} root - Project root
|
|
73
|
+
* @returns {string[]}
|
|
74
|
+
*/
|
|
75
|
+
function registeredPaths(root) {
|
|
76
|
+
const { format, paths } = readRegistry(root);
|
|
77
|
+
return format === 'none' ? [] : [...new Set([...paths, MANAGED_REGISTRY])].sort();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Creates an io session for one install/update of `root`.
|
|
82
|
+
* @param {object} options
|
|
83
|
+
* @param {string} options.root - Project root
|
|
84
|
+
* @param {boolean} [options.dryRun] - Record only, never write
|
|
85
|
+
* @param {object} [options.knownHashes] - { path: [sha256] } (defaults to src/known-hashes.json)
|
|
86
|
+
* @param {'install'|'update'} [options.mode] - An update never adds guards or
|
|
87
|
+
* mirrors the project does not have (sdds/update-no-additions)
|
|
88
|
+
* @param {(rel: string) => boolean} [options.exclude] - Root-relative POSIX
|
|
89
|
+
* paths never touched (the config's update.exclude)
|
|
90
|
+
*/
|
|
91
|
+
function createIO({ root, dryRun = false, knownHashes = loadKnownHashes(), mode: sessionMode = 'install', exclude = () => false } = {}) {
|
|
92
|
+
const projectRoot = path.resolve(root);
|
|
93
|
+
const registry = readRegistry(projectRoot);
|
|
94
|
+
// Dry-run overlay: what the plan would have put on disk, so later reads in
|
|
95
|
+
// the same run see it (e.g. .gitignore entries added twice).
|
|
96
|
+
const overlay = new Map(); // abs -> { kind: 'file', bytes } | { kind: 'dir' } | { kind: 'link', target } | { kind: 'gone' }
|
|
97
|
+
const decisions = new Map(); // rel -> { status, sha }
|
|
98
|
+
const plan = {
|
|
99
|
+
written: [], unchanged: [], protected: [], moved: [], removed: [], gitignore: [], gitConfig: [],
|
|
100
|
+
// sdds/update-no-additions: what update left out on purpose.
|
|
101
|
+
available: [], mirrorsSkipped: [], trackedNotIgnored: [],
|
|
102
|
+
// sdds/config-foundation: paths the config's update.exclude keeps out.
|
|
103
|
+
excluded: [],
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const rel = (abs) => toPosix(path.relative(projectRoot, abs));
|
|
107
|
+
const abs = (p) => (path.isAbsolute(p) ? p : path.join(projectRoot, p));
|
|
108
|
+
const note = (list, value) => {
|
|
109
|
+
if (!list.includes(value)) list.push(value);
|
|
110
|
+
};
|
|
111
|
+
/** True (and recorded) when the config excludes this absolute path. */
|
|
112
|
+
const excluded = (p) => {
|
|
113
|
+
const relPath = rel(p);
|
|
114
|
+
if (!exclude(relPath)) return false;
|
|
115
|
+
note(plan.excluded, relPath);
|
|
116
|
+
return true;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
function lstat(p) {
|
|
120
|
+
const o = overlay.get(p);
|
|
121
|
+
if (o) return o.kind === 'gone' ? null : o;
|
|
122
|
+
try {
|
|
123
|
+
const st = fs.lstatSync(p);
|
|
124
|
+
if (st.isSymbolicLink()) return { kind: 'link', target: fs.readlinkSync(p) };
|
|
125
|
+
if (st.isDirectory()) return { kind: 'dir' };
|
|
126
|
+
if (st.isFile()) return { kind: 'file' };
|
|
127
|
+
return { kind: 'other' };
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function exists(p) {
|
|
134
|
+
const o = overlay.get(abs(p));
|
|
135
|
+
if (o) return o.kind !== 'gone';
|
|
136
|
+
return fs.existsSync(abs(p));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function read(p) {
|
|
140
|
+
const o = overlay.get(abs(p));
|
|
141
|
+
if (o && o.kind === 'file') return o.bytes;
|
|
142
|
+
if (o && o.kind === 'gone') return null;
|
|
143
|
+
try {
|
|
144
|
+
return fs.readFileSync(abs(p));
|
|
145
|
+
} catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function mkdir(p) {
|
|
151
|
+
if (excluded(abs(p))) return;
|
|
152
|
+
ensureDir(abs(p));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function ensureDir(dir) {
|
|
156
|
+
if (dryRun) {
|
|
157
|
+
for (let d = dir; d.startsWith(projectRoot) && !exists(d); d = path.dirname(d)) overlay.set(d, { kind: 'dir' });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function put(dest, bytes, mode) {
|
|
164
|
+
if (dryRun) {
|
|
165
|
+
overlay.set(dest, { kind: 'file', bytes });
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
ensureDir(path.dirname(dest));
|
|
169
|
+
fs.writeFileSync(dest, bytes);
|
|
170
|
+
if (mode) {
|
|
171
|
+
try { fs.chmodSync(dest, mode); } catch {}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Writes a file the project owns the content of (AGENTS.md when absent,
|
|
177
|
+
* sdds/.gitkeep, the registry, .clckernel.resolved.json): no ownership rule.
|
|
178
|
+
* @returns {'written'|'unchanged'}
|
|
179
|
+
*/
|
|
180
|
+
function writeFile(p, content, { mode } = {}) {
|
|
181
|
+
const dest = abs(p);
|
|
182
|
+
if (excluded(dest)) return 'excluded';
|
|
183
|
+
const bytes = Buffer.isBuffer(content) ? content : Buffer.from(String(content), 'utf-8');
|
|
184
|
+
const current = read(dest);
|
|
185
|
+
if (current && current.equals(bytes)) {
|
|
186
|
+
note(plan.unchanged, rel(dest));
|
|
187
|
+
return 'unchanged';
|
|
188
|
+
}
|
|
189
|
+
put(dest, bytes, mode);
|
|
190
|
+
note(plan.written, rel(dest));
|
|
191
|
+
return 'written';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Writes a kernel-owned file under the ownership rule.
|
|
196
|
+
* @param {string} relPath - Path relative to the project root
|
|
197
|
+
* @param {string|Buffer} content
|
|
198
|
+
* @param {{ mode?: string }} [options]
|
|
199
|
+
* @returns {'written'|'unchanged'|'protected'|'excluded'}
|
|
200
|
+
*/
|
|
201
|
+
function writeManaged(relPath, content, { mode } = {}) {
|
|
202
|
+
const relKey = toPosix(relPath);
|
|
203
|
+
const dest = abs(relKey);
|
|
204
|
+
if (excluded(dest)) return 'excluded';
|
|
205
|
+
const bytes = Buffer.isBuffer(content) ? content : Buffer.from(String(content), 'utf-8');
|
|
206
|
+
const sha = sha256(bytes);
|
|
207
|
+
const prior = decisions.get(relKey);
|
|
208
|
+
if (prior && prior.sha === sha) return prior.status;
|
|
209
|
+
|
|
210
|
+
const stat = lstat(dest);
|
|
211
|
+
const current = stat && stat.kind === 'file' ? read(dest) : null;
|
|
212
|
+
let status;
|
|
213
|
+
let reason = null;
|
|
214
|
+
if (!stat) {
|
|
215
|
+
status = 'written';
|
|
216
|
+
} else if (current && current.equals(bytes)) {
|
|
217
|
+
status = 'unchanged';
|
|
218
|
+
} else if (current && prior && prior.status !== 'protected') {
|
|
219
|
+
status = 'written'; // written earlier in this same run
|
|
220
|
+
} else if (current && (registry.hashes.get(relKey) === sha256(current) ||
|
|
221
|
+
(knownHashes[relKey] || []).includes(sha256(current)))) {
|
|
222
|
+
status = 'written';
|
|
223
|
+
} else {
|
|
224
|
+
status = 'protected';
|
|
225
|
+
if (!current) reason = 'not a regular file';
|
|
226
|
+
else if (registry.paths.includes(relKey)) reason = 'modified locally';
|
|
227
|
+
else reason = 'unknown to the kernel: edited locally, or a project file with the same name';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (status === 'written') {
|
|
231
|
+
put(dest, bytes, mode);
|
|
232
|
+
plan.unchanged = plan.unchanged.filter((p) => p !== relKey);
|
|
233
|
+
note(plan.written, relKey);
|
|
234
|
+
} else if (status === 'unchanged') {
|
|
235
|
+
if (!dryRun && mode) {
|
|
236
|
+
try { fs.chmodSync(dest, mode); } catch {}
|
|
237
|
+
}
|
|
238
|
+
if (!plan.written.includes(relKey)) note(plan.unchanged, relKey);
|
|
239
|
+
} else {
|
|
240
|
+
const newPath = relKey + NEW_SUFFIX;
|
|
241
|
+
put(abs(newPath), bytes, mode);
|
|
242
|
+
if (!plan.protected.some((x) => x.path === relKey)) plan.protected.push({ path: relKey, newPath, reason });
|
|
243
|
+
}
|
|
244
|
+
decisions.set(relKey, { status, sha });
|
|
245
|
+
return status;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** writeManaged with the bytes of a bundled file. */
|
|
249
|
+
function copyManaged(src, relPath, options) {
|
|
250
|
+
return writeManaged(relPath, fs.readFileSync(src), options);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Creates a symlink at `p` pointing to `target` (the caller decided it is safe). */
|
|
254
|
+
function symlink(target, p) {
|
|
255
|
+
const dest = abs(p);
|
|
256
|
+
if (excluded(dest)) return 'excluded';
|
|
257
|
+
const stat = lstat(dest);
|
|
258
|
+
if (stat && stat.kind === 'link' && stat.target === target) {
|
|
259
|
+
note(plan.unchanged, rel(dest));
|
|
260
|
+
return 'unchanged';
|
|
261
|
+
}
|
|
262
|
+
if (stat && stat.kind !== 'link') return 'skipped';
|
|
263
|
+
if (dryRun) {
|
|
264
|
+
overlay.set(dest, { kind: 'link', target });
|
|
265
|
+
} else {
|
|
266
|
+
if (stat) fs.unlinkSync(dest);
|
|
267
|
+
fs.symlinkSync(target, dest, 'file');
|
|
268
|
+
}
|
|
269
|
+
note(plan.written, `${rel(dest)} -> ${target}`);
|
|
270
|
+
return 'written';
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Creates or refreshes an AI IDE mirror (a symlink to AGENTS.md). An update
|
|
275
|
+
* only refreshes mirrors that exist: one the project deleted is reported as
|
|
276
|
+
* skipped and neither it nor its directory is created.
|
|
277
|
+
* @returns {'written'|'unchanged'|'skipped'}
|
|
278
|
+
*/
|
|
279
|
+
function mirror(target, p) {
|
|
280
|
+
const dest = abs(p);
|
|
281
|
+
if (excluded(dest)) return 'excluded';
|
|
282
|
+
if (sessionMode === 'update' && !lstat(dest)) {
|
|
283
|
+
note(plan.mirrorsSkipped, rel(dest));
|
|
284
|
+
return 'skipped';
|
|
285
|
+
}
|
|
286
|
+
if (!exists(path.dirname(dest))) ensureDir(path.dirname(dest));
|
|
287
|
+
return symlink(target, dest);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Records a guard of the manifest the project has not installed. */
|
|
291
|
+
function noteAvailable(name) {
|
|
292
|
+
note(plan.available, name);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Records a .gitignore entry left out because the project tracks that path. */
|
|
296
|
+
function noteTrackedNotIgnored(entry) {
|
|
297
|
+
note(plan.trackedNotIgnored, entry);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Removes a file, symlink or directory tree. */
|
|
301
|
+
function remove(p) {
|
|
302
|
+
const dest = abs(p);
|
|
303
|
+
if (excluded(dest)) return 'excluded';
|
|
304
|
+
if (dryRun) overlay.set(dest, { kind: 'gone' });
|
|
305
|
+
else fs.rmSync(dest, { recursive: true, force: true });
|
|
306
|
+
note(plan.removed, rel(dest));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** Moves `from` to `to` (both inside the project). */
|
|
310
|
+
function move(from, to) {
|
|
311
|
+
const src = abs(from);
|
|
312
|
+
const dest = abs(to);
|
|
313
|
+
if (excluded(src) || excluded(dest)) return 'excluded';
|
|
314
|
+
if (dryRun) {
|
|
315
|
+
overlay.set(dest, { kind: 'file', bytes: read(src) });
|
|
316
|
+
overlay.set(src, { kind: 'gone' });
|
|
317
|
+
} else {
|
|
318
|
+
ensureDir(path.dirname(dest));
|
|
319
|
+
fs.renameSync(src, dest);
|
|
320
|
+
}
|
|
321
|
+
plan.moved.push({ from: rel(src), to: rel(dest) });
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Rewrites .gitignore with `content`, recording the entries it adds. */
|
|
325
|
+
function appendGitignore(p, content, entries) {
|
|
326
|
+
const dest = abs(p);
|
|
327
|
+
if (excluded(dest)) return 'excluded';
|
|
328
|
+
put(dest, Buffer.from(content, 'utf-8'));
|
|
329
|
+
for (const entry of entries) note(plan.gitignore, entry);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** Sets a git config key in the repository at `cwd`. */
|
|
333
|
+
function gitConfig(cwd, key, value) {
|
|
334
|
+
plan.gitConfig.push({ key, value });
|
|
335
|
+
if (dryRun) return true;
|
|
336
|
+
const res = spawnSync('git', ['config', key, value], { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
337
|
+
return !res.error && res.status === 0;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Owned files whose current bytes are the kernel's (written or unchanged), for the registry. */
|
|
341
|
+
function managedEntries() {
|
|
342
|
+
return [...decisions.entries()]
|
|
343
|
+
.filter(([, d]) => d.status !== 'protected')
|
|
344
|
+
.map(([p, d]) => ({ path: p, sha256: d.sha }))
|
|
345
|
+
.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
root: projectRoot,
|
|
350
|
+
dryRun,
|
|
351
|
+
mode: sessionMode,
|
|
352
|
+
registry,
|
|
353
|
+
exists,
|
|
354
|
+
read,
|
|
355
|
+
mkdir,
|
|
356
|
+
writeFile,
|
|
357
|
+
writeManaged,
|
|
358
|
+
copyManaged,
|
|
359
|
+
symlink,
|
|
360
|
+
mirror,
|
|
361
|
+
noteAvailable,
|
|
362
|
+
noteTrackedNotIgnored,
|
|
363
|
+
remove,
|
|
364
|
+
move,
|
|
365
|
+
appendGitignore,
|
|
366
|
+
gitConfig,
|
|
367
|
+
managedEntries,
|
|
368
|
+
plan: () => plan,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// The active session. Install and update run synchronously, so a stack is
|
|
373
|
+
// enough to hand the session to the adapters without threading it through
|
|
374
|
+
// every provision() signature.
|
|
375
|
+
const stack = [];
|
|
376
|
+
|
|
377
|
+
/** Runs fn with `io` as the active session. */
|
|
378
|
+
function runWithIO(io, fn) {
|
|
379
|
+
stack.push(io);
|
|
380
|
+
try {
|
|
381
|
+
return fn();
|
|
382
|
+
} finally {
|
|
383
|
+
stack.pop();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** The active session, or null. */
|
|
388
|
+
function activeIO() {
|
|
389
|
+
return stack.length > 0 ? stack[stack.length - 1] : null;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** The active session, or a fresh real session for `root` when none is active. */
|
|
393
|
+
function currentIO(root) {
|
|
394
|
+
if (stack.length > 0) return stack[stack.length - 1];
|
|
395
|
+
return createIO({ root: root || process.cwd() });
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
module.exports = {
|
|
399
|
+
MANAGED_REGISTRY,
|
|
400
|
+
NEW_SUFFIX,
|
|
401
|
+
createIO,
|
|
402
|
+
runWithIO,
|
|
403
|
+
activeIO,
|
|
404
|
+
currentIO,
|
|
405
|
+
readRegistry,
|
|
406
|
+
registeredPaths,
|
|
407
|
+
loadKnownHashes,
|
|
408
|
+
sha256,
|
|
409
|
+
};
|