@ebay-dt/bonfire-context 0.1.0-alpha.4
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 -0
- package/README.md +17 -0
- package/dist/discover.d.ts +9 -0
- package/dist/discover.d.ts.map +1 -0
- package/dist/discover.js +103 -0
- package/dist/discover.js.map +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +9 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +33 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +488 -0
- package/dist/install.js.map +1 -0
- package/dist/internal/filesystem.d.ts +5 -0
- package/dist/internal/filesystem.d.ts.map +1 -0
- package/dist/internal/filesystem.js +82 -0
- package/dist/internal/filesystem.js.map +1 -0
- package/dist/mcp.d.ts +26 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +178 -0
- package/dist/mcp.js.map +1 -0
- package/dist/migration.d.ts +23 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +206 -0
- package/dist/migration.js.map +1 -0
- package/dist/paths.d.ts +58 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +191 -0
- package/dist/paths.js.map +1 -0
- package/dist/prototype-project.d.ts +7 -0
- package/dist/prototype-project.d.ts.map +1 -0
- package/dist/prototype-project.js +38 -0
- package/dist/prototype-project.js.map +1 -0
- package/dist/registry.d.ts +36 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +189 -0
- package/dist/registry.js.map +1 -0
- package/dist/render.d.ts +15 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +64 -0
- package/dist/render.js.map +1 -0
- package/dist/root-project.d.ts +11 -0
- package/dist/root-project.d.ts.map +1 -0
- package/dist/root-project.js +152 -0
- package/dist/root-project.js.map +1 -0
- package/dist/skills.d.ts +13 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +170 -0
- package/dist/skills.js.map +1 -0
- package/dist/sync.d.ts +23 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +232 -0
- package/dist/sync.js.map +1 -0
- package/dist/template.d.ts +18 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +114 -0
- package/dist/template.js.map +1 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +44 -0
package/dist/install.js
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { lstat, mkdir, readFile, readdir, realpath, symlink, unlink, } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { readContextRegistry, resolveContextRegistryPaths, validateContextAppId, writeContextRegistry, } from "./registry.js";
|
|
6
|
+
import { resolveContextPaths, assertSafeContextPaths, } from "./paths.js";
|
|
7
|
+
import { applyMcpInstallation, planMcpInstallation, rollbackMcpInstallation, } from "./mcp.js";
|
|
8
|
+
import { planLegacyMigration, removeLegacyManifest, rollbackLegacyMigration, } from "./migration.js";
|
|
9
|
+
import { renderSkillFiles } from "./skills.js";
|
|
10
|
+
const MACHINE_SKILLS_DIRECTORY = join(".claude", "skills");
|
|
11
|
+
export class ContextInstallError extends Error {
|
|
12
|
+
path;
|
|
13
|
+
constructor(path, message) {
|
|
14
|
+
super(`Unable to install context at ${path}: ${message}`);
|
|
15
|
+
this.name = "ContextInstallError";
|
|
16
|
+
this.path = path;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function compareFiles(left, right) {
|
|
20
|
+
return left.relativePath < right.relativePath
|
|
21
|
+
? -1
|
|
22
|
+
: left.relativePath > right.relativePath
|
|
23
|
+
? 1
|
|
24
|
+
: 0;
|
|
25
|
+
}
|
|
26
|
+
function hashFiles(files) {
|
|
27
|
+
const hash = createHash("sha256");
|
|
28
|
+
for (const file of [...files].sort(compareFiles)) {
|
|
29
|
+
hash.update(file.relativePath);
|
|
30
|
+
hash.update("\0");
|
|
31
|
+
hash.update(file.content);
|
|
32
|
+
hash.update("\0");
|
|
33
|
+
}
|
|
34
|
+
return hash.digest("hex");
|
|
35
|
+
}
|
|
36
|
+
function sourceSkillFiles(name, files) {
|
|
37
|
+
const prefix = `${name}/`;
|
|
38
|
+
return files
|
|
39
|
+
.filter((file) => file.relativePath.startsWith(prefix))
|
|
40
|
+
.map((file) => ({
|
|
41
|
+
relativePath: file.relativePath.slice(prefix.length),
|
|
42
|
+
content: file.content,
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
async function readSkillFiles(root, relativeDirectory = "") {
|
|
46
|
+
let entries;
|
|
47
|
+
try {
|
|
48
|
+
entries = await readdir(root, { withFileTypes: true });
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
throw new ContextInstallError(root, "skill directory could not be read");
|
|
52
|
+
}
|
|
53
|
+
const files = [];
|
|
54
|
+
for (const entry of entries.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0)) {
|
|
55
|
+
const relativePath = relativeDirectory
|
|
56
|
+
? `${relativeDirectory}/${entry.name}`
|
|
57
|
+
: entry.name;
|
|
58
|
+
const path = join(root, entry.name);
|
|
59
|
+
let stats;
|
|
60
|
+
try {
|
|
61
|
+
stats = await lstat(path);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
throw new ContextInstallError(path, "skill entry could not be inspected");
|
|
65
|
+
}
|
|
66
|
+
if (stats.isSymbolicLink()) {
|
|
67
|
+
throw new ContextInstallError(path, "skill content must not be a symbolic link");
|
|
68
|
+
}
|
|
69
|
+
if (stats.isDirectory()) {
|
|
70
|
+
files.push(...(await readSkillFiles(path, relativePath)));
|
|
71
|
+
}
|
|
72
|
+
else if (stats.isFile()) {
|
|
73
|
+
try {
|
|
74
|
+
files.push({ relativePath, content: await readFile(path) });
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
throw new ContextInstallError(path, "skill file could not be read");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
throw new ContextInstallError(path, "skill content must be a regular file or directory");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return files;
|
|
85
|
+
}
|
|
86
|
+
async function assertNoSymlinkAncestors(path) {
|
|
87
|
+
let current = resolve(path);
|
|
88
|
+
while (true) {
|
|
89
|
+
try {
|
|
90
|
+
const stats = await lstat(current);
|
|
91
|
+
if (stats.isSymbolicLink()) {
|
|
92
|
+
throw new ContextInstallError(current, "must not pass through a symbolic link");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (error instanceof ContextInstallError)
|
|
97
|
+
throw error;
|
|
98
|
+
if (!isNotFound(error)) {
|
|
99
|
+
throw new ContextInstallError(current, "could not be inspected safely");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const parent = dirname(current);
|
|
103
|
+
if (parent === current)
|
|
104
|
+
return;
|
|
105
|
+
current = parent;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function isNotFound(error) {
|
|
109
|
+
return (error !== null &&
|
|
110
|
+
typeof error === "object" &&
|
|
111
|
+
"code" in error &&
|
|
112
|
+
error.code === "ENOENT");
|
|
113
|
+
}
|
|
114
|
+
async function resolveMachineHomeDirectory(options) {
|
|
115
|
+
const configured = resolve(options.machineHomeDirectory ??
|
|
116
|
+
options.registryLocation?.homeDirectory ??
|
|
117
|
+
homedir());
|
|
118
|
+
try {
|
|
119
|
+
return await realpath(configured);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
throw new ContextInstallError(configured, "machine home directory could not be resolved safely");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function ensureMachineSkillsDirectory(path) {
|
|
126
|
+
await assertNoSymlinkAncestors(path);
|
|
127
|
+
try {
|
|
128
|
+
await mkdir(path, { recursive: true, mode: 0o700 });
|
|
129
|
+
const stats = await lstat(path);
|
|
130
|
+
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
131
|
+
throw new ContextInstallError(path, "must be a regular directory");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (error instanceof ContextInstallError)
|
|
136
|
+
throw error;
|
|
137
|
+
throw new ContextInstallError(path, "could not be created safely");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function targetStatus(target, source, expectedHash, previousSource, legacySource) {
|
|
141
|
+
let stats;
|
|
142
|
+
try {
|
|
143
|
+
stats = await lstat(target);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (isNotFound(error))
|
|
147
|
+
return "linked";
|
|
148
|
+
throw new ContextInstallError(target, "existing skill could not be inspected");
|
|
149
|
+
}
|
|
150
|
+
if (stats.isSymbolicLink()) {
|
|
151
|
+
let linkedPath;
|
|
152
|
+
try {
|
|
153
|
+
linkedPath = await realpath(target);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
if (previousSource === source)
|
|
157
|
+
return "repaired";
|
|
158
|
+
throw new ContextInstallError(target, "broken skill link is not owned by this app");
|
|
159
|
+
}
|
|
160
|
+
const resolvedSource = await realpath(source);
|
|
161
|
+
if (linkedPath === resolvedSource)
|
|
162
|
+
return "adopted";
|
|
163
|
+
if (legacySource !== undefined) {
|
|
164
|
+
const resolvedLegacySource = await realpath(legacySource);
|
|
165
|
+
if (linkedPath === resolvedLegacySource)
|
|
166
|
+
return "repaired";
|
|
167
|
+
}
|
|
168
|
+
let linkedHash;
|
|
169
|
+
try {
|
|
170
|
+
linkedHash = hashFiles(await readSkillFiles(linkedPath));
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
throw new ContextInstallError(target, "existing skill link could not be compared safely");
|
|
174
|
+
}
|
|
175
|
+
if (linkedHash === expectedHash)
|
|
176
|
+
return "adopted";
|
|
177
|
+
throw new ContextInstallError(target, "conflicts with an existing skill");
|
|
178
|
+
}
|
|
179
|
+
if (!stats.isDirectory()) {
|
|
180
|
+
throw new ContextInstallError(target, "conflicts with an existing file");
|
|
181
|
+
}
|
|
182
|
+
let existingHash;
|
|
183
|
+
try {
|
|
184
|
+
existingHash = hashFiles(await readSkillFiles(target));
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
throw new ContextInstallError(target, "existing skill could not be compared safely");
|
|
188
|
+
}
|
|
189
|
+
if (existingHash === expectedHash)
|
|
190
|
+
return "adopted";
|
|
191
|
+
throw new ContextInstallError(target, "conflicts with an existing skill");
|
|
192
|
+
}
|
|
193
|
+
async function linkSkill(target, source, status) {
|
|
194
|
+
if (status === "adopted")
|
|
195
|
+
return;
|
|
196
|
+
if (status === "repaired") {
|
|
197
|
+
try {
|
|
198
|
+
await unlink(target);
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
throw new ContextInstallError(target, "owned broken skill link could not be repaired");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
await symlink(source, target, "dir");
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
throw new ContextInstallError(target, "skill link could not be created");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function registryForSkills(registry, options) {
|
|
212
|
+
const previous = registry.apps[options.appId];
|
|
213
|
+
return {
|
|
214
|
+
...registry,
|
|
215
|
+
apps: {
|
|
216
|
+
...registry.apps,
|
|
217
|
+
[options.appId]: {
|
|
218
|
+
repositoryRoot: options.repositoryRoot,
|
|
219
|
+
skills: {
|
|
220
|
+
...(previous?.skills ?? {}),
|
|
221
|
+
...options.skills,
|
|
222
|
+
},
|
|
223
|
+
mcp: {
|
|
224
|
+
...(previous?.mcp ?? {}),
|
|
225
|
+
...options.mcp,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function errorPath(error, fallback) {
|
|
232
|
+
if (error !== null &&
|
|
233
|
+
typeof error === "object" &&
|
|
234
|
+
"path" in error &&
|
|
235
|
+
typeof error.path === "string") {
|
|
236
|
+
return error.path;
|
|
237
|
+
}
|
|
238
|
+
return fallback;
|
|
239
|
+
}
|
|
240
|
+
export async function checkContextInstallation(options) {
|
|
241
|
+
const appId = validateContextAppId(options.appId);
|
|
242
|
+
const paths = resolveContextPaths(options);
|
|
243
|
+
assertSafeContextPaths(paths);
|
|
244
|
+
const renderedFiles = await renderSkillFiles(paths);
|
|
245
|
+
const skillNames = [
|
|
246
|
+
...new Set(renderedFiles.map((file) => {
|
|
247
|
+
const separator = file.relativePath.indexOf("/");
|
|
248
|
+
return separator === -1
|
|
249
|
+
? file.relativePath
|
|
250
|
+
: file.relativePath.slice(0, separator);
|
|
251
|
+
})),
|
|
252
|
+
].sort();
|
|
253
|
+
const machineHomeDirectory = await resolveMachineHomeDirectory(options);
|
|
254
|
+
const machineSkillsRoot = join(machineHomeDirectory, MACHINE_SKILLS_DIRECTORY);
|
|
255
|
+
const registryOptions = options.registryLocation ?? {
|
|
256
|
+
homeDirectory: machineHomeDirectory,
|
|
257
|
+
};
|
|
258
|
+
const registry = await readContextRegistry(registryOptions);
|
|
259
|
+
const registryPath = resolveContextRegistryPaths(registryOptions).registry;
|
|
260
|
+
const app = registry.apps[appId];
|
|
261
|
+
let registryPresent = true;
|
|
262
|
+
try {
|
|
263
|
+
await lstat(registryPath);
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
if (isNotFound(error))
|
|
267
|
+
registryPresent = false;
|
|
268
|
+
else
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
let registryNeedsUpdate = app === undefined;
|
|
272
|
+
const issues = [];
|
|
273
|
+
const issuePaths = new Set();
|
|
274
|
+
const addIssue = (kind, path) => {
|
|
275
|
+
if (issuePaths.has(path))
|
|
276
|
+
return;
|
|
277
|
+
issuePaths.add(path);
|
|
278
|
+
issues.push({ kind, path });
|
|
279
|
+
};
|
|
280
|
+
let machineSkillsPresent = true;
|
|
281
|
+
try {
|
|
282
|
+
const stats = await lstat(machineSkillsRoot);
|
|
283
|
+
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
284
|
+
addIssue("conflict", machineSkillsRoot);
|
|
285
|
+
machineSkillsPresent = false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
if (isNotFound(error))
|
|
290
|
+
machineSkillsPresent = false;
|
|
291
|
+
else
|
|
292
|
+
addIssue("conflict", machineSkillsRoot);
|
|
293
|
+
}
|
|
294
|
+
for (const name of skillNames) {
|
|
295
|
+
const files = sourceSkillFiles(name, renderedFiles);
|
|
296
|
+
const sourcePath = join(paths.sources.skills, name);
|
|
297
|
+
const contentHash = hashFiles(files);
|
|
298
|
+
const target = join(machineSkillsRoot, name);
|
|
299
|
+
let targetOkay = false;
|
|
300
|
+
if (machineSkillsPresent) {
|
|
301
|
+
try {
|
|
302
|
+
const stats = await lstat(target);
|
|
303
|
+
if (stats.isSymbolicLink()) {
|
|
304
|
+
try {
|
|
305
|
+
targetOkay =
|
|
306
|
+
(await realpath(target)) === (await realpath(sourcePath));
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
addIssue("stale", target);
|
|
310
|
+
}
|
|
311
|
+
if (!targetOkay) {
|
|
312
|
+
try {
|
|
313
|
+
targetOkay =
|
|
314
|
+
hashFiles(await readSkillFiles(await realpath(target))) ===
|
|
315
|
+
contentHash;
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
addIssue("conflict", target);
|
|
319
|
+
}
|
|
320
|
+
if (targetOkay)
|
|
321
|
+
addIssue("stale", target);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
else if (stats.isDirectory()) {
|
|
325
|
+
try {
|
|
326
|
+
targetOkay =
|
|
327
|
+
hashFiles(await readSkillFiles(target)) === contentHash;
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
addIssue("conflict", target);
|
|
331
|
+
}
|
|
332
|
+
if (targetOkay)
|
|
333
|
+
addIssue("stale", target);
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
addIssue("conflict", target);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
if (isNotFound(error))
|
|
341
|
+
addIssue("missing", target);
|
|
342
|
+
else
|
|
343
|
+
addIssue("conflict", target);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
addIssue("missing", target);
|
|
348
|
+
}
|
|
349
|
+
const record = app?.skills[name];
|
|
350
|
+
if (record === undefined ||
|
|
351
|
+
record.sourcePath !== sourcePath ||
|
|
352
|
+
record.contentHash !== contentHash) {
|
|
353
|
+
if (record !== undefined)
|
|
354
|
+
addIssue("stale", target);
|
|
355
|
+
registryNeedsUpdate = true;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
let mcpPlan;
|
|
359
|
+
try {
|
|
360
|
+
mcpPlan = await planMcpInstallation(paths, machineHomeDirectory);
|
|
361
|
+
for (const installation of mcpPlan.installations) {
|
|
362
|
+
if (installation.status === "added") {
|
|
363
|
+
addIssue("missing", `${installation.path}#${installation.name}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
for (const [name, record] of Object.entries(mcpPlan.records)) {
|
|
367
|
+
const registered = app?.mcp[name];
|
|
368
|
+
if (registered === undefined ||
|
|
369
|
+
registered.sourcePath !== record.sourcePath ||
|
|
370
|
+
registered.normalizedDefinitionHash !== record.normalizedDefinitionHash) {
|
|
371
|
+
registryNeedsUpdate = true;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
addIssue("conflict", errorPath(error, machineHomeDirectory));
|
|
377
|
+
}
|
|
378
|
+
if (machineSkillsPresent) {
|
|
379
|
+
try {
|
|
380
|
+
const migration = await planLegacyMigration({
|
|
381
|
+
repositoryRoot: paths.repositoryRoot,
|
|
382
|
+
machineSkillsRoot,
|
|
383
|
+
canonicalSkillsRoot: paths.sources.skills,
|
|
384
|
+
skillNames,
|
|
385
|
+
});
|
|
386
|
+
if (migration !== undefined)
|
|
387
|
+
addIssue("stale", migration.manifestPath);
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
addIssue("conflict", errorPath(error, machineSkillsRoot));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (registryNeedsUpdate) {
|
|
394
|
+
addIssue(registryPresent ? "stale" : "missing", registryPath);
|
|
395
|
+
}
|
|
396
|
+
return { ok: issues.length === 0, issues };
|
|
397
|
+
}
|
|
398
|
+
export async function installContext(options) {
|
|
399
|
+
const appId = validateContextAppId(options.appId);
|
|
400
|
+
const paths = resolveContextPaths(options);
|
|
401
|
+
assertSafeContextPaths(paths);
|
|
402
|
+
const renderedFiles = await renderSkillFiles(paths);
|
|
403
|
+
const skillNames = [
|
|
404
|
+
...new Set(renderedFiles.map((file) => {
|
|
405
|
+
const separator = file.relativePath.indexOf("/");
|
|
406
|
+
return separator === -1
|
|
407
|
+
? file.relativePath
|
|
408
|
+
: file.relativePath.slice(0, separator);
|
|
409
|
+
})),
|
|
410
|
+
].sort();
|
|
411
|
+
const machineHomeDirectory = await resolveMachineHomeDirectory(options);
|
|
412
|
+
const machineSkillsRoot = join(machineHomeDirectory, MACHINE_SKILLS_DIRECTORY);
|
|
413
|
+
const mcpPlan = await planMcpInstallation(paths, machineHomeDirectory);
|
|
414
|
+
await ensureMachineSkillsDirectory(machineSkillsRoot);
|
|
415
|
+
const registryOptions = options.registryLocation ?? {
|
|
416
|
+
homeDirectory: machineHomeDirectory,
|
|
417
|
+
};
|
|
418
|
+
const registry = await readContextRegistry(registryOptions);
|
|
419
|
+
const previous = registry.apps[appId];
|
|
420
|
+
const legacyMigration = await planLegacyMigration({
|
|
421
|
+
repositoryRoot: paths.repositoryRoot,
|
|
422
|
+
machineSkillsRoot,
|
|
423
|
+
canonicalSkillsRoot: paths.sources.skills,
|
|
424
|
+
skillNames,
|
|
425
|
+
});
|
|
426
|
+
const legacySources = new Map(legacyMigration?.repoints.map((repoint) => [
|
|
427
|
+
repoint.name,
|
|
428
|
+
repoint.legacySource,
|
|
429
|
+
]));
|
|
430
|
+
const plans = [];
|
|
431
|
+
for (const name of skillNames) {
|
|
432
|
+
const files = sourceSkillFiles(name, renderedFiles);
|
|
433
|
+
const sourcePath = join(paths.sources.skills, name);
|
|
434
|
+
const contentHash = hashFiles(files);
|
|
435
|
+
const target = join(machineSkillsRoot, name);
|
|
436
|
+
const status = await targetStatus(target, sourcePath, contentHash, previous?.skills[name]?.sourcePath, legacySources.get(name));
|
|
437
|
+
plans.push({ name, sourcePath, contentHash, target, status });
|
|
438
|
+
}
|
|
439
|
+
const created = [];
|
|
440
|
+
let mcpApplied = false;
|
|
441
|
+
try {
|
|
442
|
+
for (const plan of plans) {
|
|
443
|
+
await linkSkill(plan.target, plan.sourcePath, plan.status);
|
|
444
|
+
if (plan.status !== "adopted")
|
|
445
|
+
created.push(plan.target);
|
|
446
|
+
}
|
|
447
|
+
await applyMcpInstallation(mcpPlan);
|
|
448
|
+
mcpApplied = mcpPlan.content !== undefined;
|
|
449
|
+
const nextRegistry = registryForSkills(registry, {
|
|
450
|
+
appId,
|
|
451
|
+
repositoryRoot: paths.repositoryRoot,
|
|
452
|
+
skills: Object.fromEntries(plans.map((plan) => [
|
|
453
|
+
plan.name,
|
|
454
|
+
{
|
|
455
|
+
sourcePath: plan.sourcePath,
|
|
456
|
+
contentHash: plan.contentHash,
|
|
457
|
+
},
|
|
458
|
+
])),
|
|
459
|
+
mcp: mcpPlan.records,
|
|
460
|
+
});
|
|
461
|
+
await writeContextRegistry(nextRegistry, registryOptions);
|
|
462
|
+
if (legacyMigration !== undefined) {
|
|
463
|
+
await removeLegacyManifest(legacyMigration);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
if (mcpApplied) {
|
|
468
|
+
await rollbackMcpInstallation(mcpPlan).catch(() => undefined);
|
|
469
|
+
}
|
|
470
|
+
await Promise.all(created.map(async (path) => {
|
|
471
|
+
await unlink(path).catch(() => undefined);
|
|
472
|
+
}));
|
|
473
|
+
if (legacyMigration !== undefined) {
|
|
474
|
+
await rollbackLegacyMigration(legacyMigration).catch(() => undefined);
|
|
475
|
+
}
|
|
476
|
+
throw error;
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
appId,
|
|
480
|
+
skills: plans.map(({ name, target: path, status }) => ({
|
|
481
|
+
name,
|
|
482
|
+
path,
|
|
483
|
+
status,
|
|
484
|
+
})),
|
|
485
|
+
mcp: mcpPlan.installations,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n lstat,\n mkdir,\n readFile,\n readdir,\n realpath,\n symlink,\n unlink,\n} from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport {\n type ContextRegistry,\n type ContextRegistryLocationOptions,\n type ContextRegistryMcpServer,\n readContextRegistry,\n resolveContextRegistryPaths,\n validateContextAppId,\n writeContextRegistry,\n} from \"./registry.js\";\nimport {\n resolveContextPaths,\n assertSafeContextPaths,\n type ContextPathOptions,\n} from \"./paths.js\";\nimport {\n applyMcpInstallation,\n planMcpInstallation,\n rollbackMcpInstallation,\n type ContextMcpInstall,\n} from \"./mcp.js\";\nimport {\n planLegacyMigration,\n removeLegacyManifest,\n rollbackLegacyMigration,\n type LegacyMigrationPlan,\n} from \"./migration.js\";\nimport { renderSkillFiles, type RenderedSkillFile } from \"./skills.js\";\n\nconst MACHINE_SKILLS_DIRECTORY = join(\".claude\", \"skills\");\n\ntype SkillFile = Readonly<{\n relativePath: string;\n content: Buffer;\n}>;\n\nexport type ContextInstallOptions = ContextPathOptions &\n Readonly<{\n appId: string;\n machineHomeDirectory?: string;\n registryLocation?: ContextRegistryLocationOptions;\n }>;\n\nexport type ContextSkillInstall = Readonly<{\n name: string;\n path: string;\n status: \"linked\" | \"adopted\" | \"repaired\";\n}>;\n\nexport type ContextInstallResult = Readonly<{\n appId: string;\n skills: readonly ContextSkillInstall[];\n mcp: readonly ContextMcpInstall[];\n}>;\n\nexport type ContextInstallCheckIssue = Readonly<{\n kind: \"missing\" | \"stale\" | \"conflict\";\n path: string;\n}>;\n\nexport type ContextInstallCheckResult = Readonly<{\n ok: boolean;\n issues: readonly ContextInstallCheckIssue[];\n}>;\n\nexport class ContextInstallError extends Error {\n readonly path: string;\n\n constructor(path: string, message: string) {\n super(`Unable to install context at ${path}: ${message}`);\n this.name = \"ContextInstallError\";\n this.path = path;\n }\n}\n\nfunction compareFiles(left: SkillFile, right: SkillFile) {\n return left.relativePath < right.relativePath\n ? -1\n : left.relativePath > right.relativePath\n ? 1\n : 0;\n}\n\nfunction hashFiles(files: readonly SkillFile[]) {\n const hash = createHash(\"sha256\");\n for (const file of [...files].sort(compareFiles)) {\n hash.update(file.relativePath);\n hash.update(\"\\0\");\n hash.update(file.content);\n hash.update(\"\\0\");\n }\n return hash.digest(\"hex\");\n}\n\nfunction sourceSkillFiles(\n name: string,\n files: readonly RenderedSkillFile[],\n): SkillFile[] {\n const prefix = `${name}/`;\n return files\n .filter((file) => file.relativePath.startsWith(prefix))\n .map((file) => ({\n relativePath: file.relativePath.slice(prefix.length),\n content: file.content,\n }));\n}\n\nasync function readSkillFiles(\n root: string,\n relativeDirectory = \"\",\n): Promise<SkillFile[]> {\n let entries;\n try {\n entries = await readdir(root, { withFileTypes: true });\n } catch {\n throw new ContextInstallError(root, \"skill directory could not be read\");\n }\n\n const files: SkillFile[] = [];\n for (const entry of entries.sort((left, right) =>\n left.name < right.name ? -1 : left.name > right.name ? 1 : 0,\n )) {\n const relativePath = relativeDirectory\n ? `${relativeDirectory}/${entry.name}`\n : entry.name;\n const path = join(root, entry.name);\n let stats;\n try {\n stats = await lstat(path);\n } catch {\n throw new ContextInstallError(path, \"skill entry could not be inspected\");\n }\n if (stats.isSymbolicLink()) {\n throw new ContextInstallError(\n path,\n \"skill content must not be a symbolic link\",\n );\n }\n if (stats.isDirectory()) {\n files.push(...(await readSkillFiles(path, relativePath)));\n } else if (stats.isFile()) {\n try {\n files.push({ relativePath, content: await readFile(path) });\n } catch {\n throw new ContextInstallError(path, \"skill file could not be read\");\n }\n } else {\n throw new ContextInstallError(\n path,\n \"skill content must be a regular file or directory\",\n );\n }\n }\n return files;\n}\n\nasync function assertNoSymlinkAncestors(path: string) {\n let current = resolve(path);\n while (true) {\n try {\n const stats = await lstat(current);\n if (stats.isSymbolicLink()) {\n throw new ContextInstallError(\n current,\n \"must not pass through a symbolic link\",\n );\n }\n } catch (error) {\n if (error instanceof ContextInstallError) throw error;\n if (!isNotFound(error)) {\n throw new ContextInstallError(current, \"could not be inspected safely\");\n }\n }\n const parent = dirname(current);\n if (parent === current) return;\n current = parent;\n }\n}\n\nfunction isNotFound(error: unknown) {\n return (\n error !== null &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n );\n}\n\nasync function resolveMachineHomeDirectory(options: ContextInstallOptions) {\n const configured = resolve(\n options.machineHomeDirectory ??\n options.registryLocation?.homeDirectory ??\n homedir(),\n );\n try {\n return await realpath(configured);\n } catch {\n throw new ContextInstallError(\n configured,\n \"machine home directory could not be resolved safely\",\n );\n }\n}\n\nasync function ensureMachineSkillsDirectory(path: string) {\n await assertNoSymlinkAncestors(path);\n try {\n await mkdir(path, { recursive: true, mode: 0o700 });\n const stats = await lstat(path);\n if (stats.isSymbolicLink() || !stats.isDirectory()) {\n throw new ContextInstallError(path, \"must be a regular directory\");\n }\n } catch (error) {\n if (error instanceof ContextInstallError) throw error;\n throw new ContextInstallError(path, \"could not be created safely\");\n }\n}\n\nasync function targetStatus(\n target: string,\n source: string,\n expectedHash: string,\n previousSource: string | undefined,\n legacySource: string | undefined,\n): Promise<\"linked\" | \"adopted\" | \"repaired\"> {\n let stats;\n try {\n stats = await lstat(target);\n } catch (error) {\n if (isNotFound(error)) return \"linked\";\n throw new ContextInstallError(\n target,\n \"existing skill could not be inspected\",\n );\n }\n\n if (stats.isSymbolicLink()) {\n let linkedPath: string;\n try {\n linkedPath = await realpath(target);\n } catch {\n if (previousSource === source) return \"repaired\";\n throw new ContextInstallError(\n target,\n \"broken skill link is not owned by this app\",\n );\n }\n const resolvedSource = await realpath(source);\n if (linkedPath === resolvedSource) return \"adopted\";\n if (legacySource !== undefined) {\n const resolvedLegacySource = await realpath(legacySource);\n if (linkedPath === resolvedLegacySource) return \"repaired\";\n }\n\n let linkedHash: string;\n try {\n linkedHash = hashFiles(await readSkillFiles(linkedPath));\n } catch {\n throw new ContextInstallError(\n target,\n \"existing skill link could not be compared safely\",\n );\n }\n if (linkedHash === expectedHash) return \"adopted\";\n throw new ContextInstallError(target, \"conflicts with an existing skill\");\n }\n\n if (!stats.isDirectory()) {\n throw new ContextInstallError(target, \"conflicts with an existing file\");\n }\n let existingHash: string;\n try {\n existingHash = hashFiles(await readSkillFiles(target));\n } catch {\n throw new ContextInstallError(\n target,\n \"existing skill could not be compared safely\",\n );\n }\n if (existingHash === expectedHash) return \"adopted\";\n throw new ContextInstallError(target, \"conflicts with an existing skill\");\n}\n\nasync function linkSkill(\n target: string,\n source: string,\n status: \"linked\" | \"adopted\" | \"repaired\",\n) {\n if (status === \"adopted\") return;\n if (status === \"repaired\") {\n try {\n await unlink(target);\n } catch {\n throw new ContextInstallError(\n target,\n \"owned broken skill link could not be repaired\",\n );\n }\n }\n try {\n await symlink(source, target, \"dir\");\n } catch {\n throw new ContextInstallError(target, \"skill link could not be created\");\n }\n}\n\nfunction registryForSkills(\n registry: ContextRegistry,\n options: {\n appId: string;\n repositoryRoot: string;\n skills: Readonly<\n Record<string, { sourcePath: string; contentHash: string }>\n >;\n mcp: Readonly<Record<string, ContextRegistryMcpServer>>;\n },\n): ContextRegistry {\n const previous = registry.apps[options.appId];\n return {\n ...registry,\n apps: {\n ...registry.apps,\n [options.appId]: {\n repositoryRoot: options.repositoryRoot,\n skills: {\n ...(previous?.skills ?? {}),\n ...options.skills,\n },\n mcp: {\n ...(previous?.mcp ?? {}),\n ...options.mcp,\n },\n },\n },\n };\n}\n\nfunction errorPath(error: unknown, fallback: string) {\n if (\n error !== null &&\n typeof error === \"object\" &&\n \"path\" in error &&\n typeof error.path === \"string\"\n ) {\n return error.path;\n }\n return fallback;\n}\n\nexport async function checkContextInstallation(\n options: ContextInstallOptions,\n): Promise<ContextInstallCheckResult> {\n const appId = validateContextAppId(options.appId);\n const paths = resolveContextPaths(options);\n assertSafeContextPaths(paths);\n const renderedFiles = await renderSkillFiles(paths);\n const skillNames = [\n ...new Set(\n renderedFiles.map((file) => {\n const separator = file.relativePath.indexOf(\"/\");\n return separator === -1\n ? file.relativePath\n : file.relativePath.slice(0, separator);\n }),\n ),\n ].sort();\n const machineHomeDirectory = await resolveMachineHomeDirectory(options);\n const machineSkillsRoot = join(\n machineHomeDirectory,\n MACHINE_SKILLS_DIRECTORY,\n );\n const registryOptions = options.registryLocation ?? {\n homeDirectory: machineHomeDirectory,\n };\n const registry = await readContextRegistry(registryOptions);\n const registryPath = resolveContextRegistryPaths(registryOptions).registry;\n const app = registry.apps[appId];\n let registryPresent = true;\n try {\n await lstat(registryPath);\n } catch (error) {\n if (isNotFound(error)) registryPresent = false;\n else throw error;\n }\n let registryNeedsUpdate = app === undefined;\n const issues: ContextInstallCheckIssue[] = [];\n const issuePaths = new Set<string>();\n const addIssue = (kind: ContextInstallCheckIssue[\"kind\"], path: string) => {\n if (issuePaths.has(path)) return;\n issuePaths.add(path);\n issues.push({ kind, path });\n };\n\n let machineSkillsPresent = true;\n try {\n const stats = await lstat(machineSkillsRoot);\n if (stats.isSymbolicLink() || !stats.isDirectory()) {\n addIssue(\"conflict\", machineSkillsRoot);\n machineSkillsPresent = false;\n }\n } catch (error) {\n if (isNotFound(error)) machineSkillsPresent = false;\n else addIssue(\"conflict\", machineSkillsRoot);\n }\n\n for (const name of skillNames) {\n const files = sourceSkillFiles(name, renderedFiles);\n const sourcePath = join(paths.sources.skills, name);\n const contentHash = hashFiles(files);\n const target = join(machineSkillsRoot, name);\n let targetOkay = false;\n if (machineSkillsPresent) {\n try {\n const stats = await lstat(target);\n if (stats.isSymbolicLink()) {\n try {\n targetOkay =\n (await realpath(target)) === (await realpath(sourcePath));\n } catch {\n addIssue(\"stale\", target);\n }\n if (!targetOkay) {\n try {\n targetOkay =\n hashFiles(await readSkillFiles(await realpath(target))) ===\n contentHash;\n } catch {\n addIssue(\"conflict\", target);\n }\n if (targetOkay) addIssue(\"stale\", target);\n }\n } else if (stats.isDirectory()) {\n try {\n targetOkay =\n hashFiles(await readSkillFiles(target)) === contentHash;\n } catch {\n addIssue(\"conflict\", target);\n }\n if (targetOkay) addIssue(\"stale\", target);\n } else {\n addIssue(\"conflict\", target);\n }\n } catch (error) {\n if (isNotFound(error)) addIssue(\"missing\", target);\n else addIssue(\"conflict\", target);\n }\n } else {\n addIssue(\"missing\", target);\n }\n\n const record = app?.skills[name];\n if (\n record === undefined ||\n record.sourcePath !== sourcePath ||\n record.contentHash !== contentHash\n ) {\n if (record !== undefined) addIssue(\"stale\", target);\n registryNeedsUpdate = true;\n }\n }\n\n let mcpPlan;\n try {\n mcpPlan = await planMcpInstallation(paths, machineHomeDirectory);\n for (const installation of mcpPlan.installations) {\n if (installation.status === \"added\") {\n addIssue(\"missing\", `${installation.path}#${installation.name}`);\n }\n }\n for (const [name, record] of Object.entries(mcpPlan.records)) {\n const registered = app?.mcp[name];\n if (\n registered === undefined ||\n registered.sourcePath !== record.sourcePath ||\n registered.normalizedDefinitionHash !== record.normalizedDefinitionHash\n ) {\n registryNeedsUpdate = true;\n }\n }\n } catch (error) {\n addIssue(\"conflict\", errorPath(error, machineHomeDirectory));\n }\n\n if (machineSkillsPresent) {\n try {\n const migration = await planLegacyMigration({\n repositoryRoot: paths.repositoryRoot,\n machineSkillsRoot,\n canonicalSkillsRoot: paths.sources.skills,\n skillNames,\n });\n if (migration !== undefined) addIssue(\"stale\", migration.manifestPath);\n } catch (error) {\n addIssue(\"conflict\", errorPath(error, machineSkillsRoot));\n }\n }\n\n if (registryNeedsUpdate) {\n addIssue(registryPresent ? \"stale\" : \"missing\", registryPath);\n }\n return { ok: issues.length === 0, issues };\n}\n\nexport async function installContext(\n options: ContextInstallOptions,\n): Promise<ContextInstallResult> {\n const appId = validateContextAppId(options.appId);\n const paths = resolveContextPaths(options);\n assertSafeContextPaths(paths);\n const renderedFiles = await renderSkillFiles(paths);\n const skillNames = [\n ...new Set(\n renderedFiles.map((file) => {\n const separator = file.relativePath.indexOf(\"/\");\n return separator === -1\n ? file.relativePath\n : file.relativePath.slice(0, separator);\n }),\n ),\n ].sort();\n const machineHomeDirectory = await resolveMachineHomeDirectory(options);\n const machineSkillsRoot = join(\n machineHomeDirectory,\n MACHINE_SKILLS_DIRECTORY,\n );\n const mcpPlan = await planMcpInstallation(paths, machineHomeDirectory);\n await ensureMachineSkillsDirectory(machineSkillsRoot);\n\n const registryOptions = options.registryLocation ?? {\n homeDirectory: machineHomeDirectory,\n };\n const registry = await readContextRegistry(registryOptions);\n const previous = registry.apps[appId];\n const legacyMigration: LegacyMigrationPlan | undefined =\n await planLegacyMigration({\n repositoryRoot: paths.repositoryRoot,\n machineSkillsRoot,\n canonicalSkillsRoot: paths.sources.skills,\n skillNames,\n });\n const legacySources = new Map(\n legacyMigration?.repoints.map((repoint) => [\n repoint.name,\n repoint.legacySource,\n ]),\n );\n const plans = [] as Array<{\n name: string;\n sourcePath: string;\n contentHash: string;\n target: string;\n status: \"linked\" | \"adopted\" | \"repaired\";\n }>;\n\n for (const name of skillNames) {\n const files = sourceSkillFiles(name, renderedFiles);\n const sourcePath = join(paths.sources.skills, name);\n const contentHash = hashFiles(files);\n const target = join(machineSkillsRoot, name);\n const status = await targetStatus(\n target,\n sourcePath,\n contentHash,\n previous?.skills[name]?.sourcePath,\n legacySources.get(name),\n );\n plans.push({ name, sourcePath, contentHash, target, status });\n }\n\n const created: string[] = [];\n let mcpApplied = false;\n try {\n for (const plan of plans) {\n await linkSkill(plan.target, plan.sourcePath, plan.status);\n if (plan.status !== \"adopted\") created.push(plan.target);\n }\n await applyMcpInstallation(mcpPlan);\n mcpApplied = mcpPlan.content !== undefined;\n const nextRegistry = registryForSkills(registry, {\n appId,\n repositoryRoot: paths.repositoryRoot,\n skills: Object.fromEntries(\n plans.map((plan) => [\n plan.name,\n {\n sourcePath: plan.sourcePath,\n contentHash: plan.contentHash,\n },\n ]),\n ),\n mcp: mcpPlan.records,\n });\n await writeContextRegistry(nextRegistry, registryOptions);\n if (legacyMigration !== undefined) {\n await removeLegacyManifest(legacyMigration);\n }\n } catch (error) {\n if (mcpApplied) {\n await rollbackMcpInstallation(mcpPlan).catch(() => undefined);\n }\n await Promise.all(\n created.map(async (path) => {\n await unlink(path).catch(() => undefined);\n }),\n );\n if (legacyMigration !== undefined) {\n await rollbackLegacyMigration(legacyMigration).catch(() => undefined);\n }\n throw error;\n }\n\n return {\n appId,\n skills: plans.map(({ name, target: path, status }) => ({\n name,\n path,\n status,\n })),\n mcp: mcpPlan.installations,\n };\n}\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function isNotFound(error: unknown): boolean;
|
|
2
|
+
export declare function assertWritableTarget(path: string, label: string): Promise<void>;
|
|
3
|
+
export declare function atomicWrite(path: string, content: string | Uint8Array, label: string, mode?: number): Promise<void>;
|
|
4
|
+
export declare function removeManagedFile(path: string, label: string): Promise<boolean>;
|
|
5
|
+
//# sourceMappingURL=filesystem.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../../src/internal/filesystem.ts"],"names":[],"mappings":"AAKA,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,WAOxC;AAeD,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,iBAcrE;AAED,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GAAG,UAAU,EAC5B,KAAK,EAAE,MAAM,EACb,IAAI,SAAQ,iBAeb;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,oBAoBlE","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { lstat, mkdir, rename, rm, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { ContextProjectionError } from \"../errors.js\";\n\nexport function isNotFound(error: unknown) {\n return (\n error !== null &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n );\n}\n\nasync function ensureDirectory(path: string, label: string) {\n try {\n await mkdir(path, { recursive: true });\n const stats = await lstat(path);\n if (stats.isSymbolicLink() || !stats.isDirectory()) {\n throw new ContextProjectionError(label, \"must be a regular directory\");\n }\n } catch (error) {\n if (error instanceof ContextProjectionError) throw error;\n throw new ContextProjectionError(label, \"could not be created safely\");\n }\n}\n\nexport async function assertWritableTarget(path: string, label: string) {\n let stats;\n try {\n stats = await lstat(path);\n } catch (error) {\n if (isNotFound(error)) return;\n throw new ContextProjectionError(label, \"could not be inspected safely\");\n }\n if (stats.isSymbolicLink()) {\n throw new ContextProjectionError(label, \"must not be a symbolic link\");\n }\n if (!stats.isFile()) {\n throw new ContextProjectionError(label, \"must be a regular file\");\n }\n}\n\nexport async function atomicWrite(\n path: string,\n content: string | Uint8Array,\n label: string,\n mode = 0o644,\n) {\n const parent = dirname(path);\n await ensureDirectory(parent, `${label} parent`);\n await assertWritableTarget(path, label);\n const temporaryPath = `${path}.${randomUUID()}.tmp`;\n\n try {\n await writeFile(temporaryPath, content, { mode, flag: \"wx\" });\n await rename(temporaryPath, path);\n } catch {\n throw new ContextProjectionError(label, \"could not be written safely\");\n } finally {\n await rm(temporaryPath, { force: true }).catch(() => undefined);\n }\n}\n\nexport async function removeManagedFile(path: string, label: string) {\n let stats;\n try {\n stats = await lstat(path);\n } catch (error) {\n if (isNotFound(error)) return false;\n throw new ContextProjectionError(label, \"could not be inspected safely\");\n }\n if (stats.isSymbolicLink()) {\n throw new ContextProjectionError(label, \"must not be a symbolic link\");\n }\n if (!stats.isFile()) {\n throw new ContextProjectionError(label, \"must be a regular file\");\n }\n try {\n await rm(path);\n } catch {\n throw new ContextProjectionError(label, \"could not be removed safely\");\n }\n return true;\n}\n"]}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { lstat, mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { ContextProjectionError } from "../errors.js";
|
|
5
|
+
export function isNotFound(error) {
|
|
6
|
+
return (error !== null &&
|
|
7
|
+
typeof error === "object" &&
|
|
8
|
+
"code" in error &&
|
|
9
|
+
error.code === "ENOENT");
|
|
10
|
+
}
|
|
11
|
+
async function ensureDirectory(path, label) {
|
|
12
|
+
try {
|
|
13
|
+
await mkdir(path, { recursive: true });
|
|
14
|
+
const stats = await lstat(path);
|
|
15
|
+
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
16
|
+
throw new ContextProjectionError(label, "must be a regular directory");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (error instanceof ContextProjectionError)
|
|
21
|
+
throw error;
|
|
22
|
+
throw new ContextProjectionError(label, "could not be created safely");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function assertWritableTarget(path, label) {
|
|
26
|
+
let stats;
|
|
27
|
+
try {
|
|
28
|
+
stats = await lstat(path);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (isNotFound(error))
|
|
32
|
+
return;
|
|
33
|
+
throw new ContextProjectionError(label, "could not be inspected safely");
|
|
34
|
+
}
|
|
35
|
+
if (stats.isSymbolicLink()) {
|
|
36
|
+
throw new ContextProjectionError(label, "must not be a symbolic link");
|
|
37
|
+
}
|
|
38
|
+
if (!stats.isFile()) {
|
|
39
|
+
throw new ContextProjectionError(label, "must be a regular file");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export async function atomicWrite(path, content, label, mode = 0o644) {
|
|
43
|
+
const parent = dirname(path);
|
|
44
|
+
await ensureDirectory(parent, `${label} parent`);
|
|
45
|
+
await assertWritableTarget(path, label);
|
|
46
|
+
const temporaryPath = `${path}.${randomUUID()}.tmp`;
|
|
47
|
+
try {
|
|
48
|
+
await writeFile(temporaryPath, content, { mode, flag: "wx" });
|
|
49
|
+
await rename(temporaryPath, path);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
throw new ContextProjectionError(label, "could not be written safely");
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
await rm(temporaryPath, { force: true }).catch(() => undefined);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export async function removeManagedFile(path, label) {
|
|
59
|
+
let stats;
|
|
60
|
+
try {
|
|
61
|
+
stats = await lstat(path);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (isNotFound(error))
|
|
65
|
+
return false;
|
|
66
|
+
throw new ContextProjectionError(label, "could not be inspected safely");
|
|
67
|
+
}
|
|
68
|
+
if (stats.isSymbolicLink()) {
|
|
69
|
+
throw new ContextProjectionError(label, "must not be a symbolic link");
|
|
70
|
+
}
|
|
71
|
+
if (!stats.isFile()) {
|
|
72
|
+
throw new ContextProjectionError(label, "must be a regular file");
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
await rm(path);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
throw new ContextProjectionError(label, "could not be removed safely");
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=filesystem.js.map
|