@amsterdamdatalabs/enact-extensions 0.1.61 → 0.1.62
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/dist/internal/file-ops.d.ts +11 -3
- package/dist/internal/file-ops.d.ts.map +1 -1
- package/dist/internal/file-ops.js +120 -47
- package/dist/internal/file-ops.js.map +1 -1
- package/dist/projection.d.ts +2 -20
- package/dist/projection.d.ts.map +1 -1
- package/dist/projection.js +3 -20
- package/dist/projection.js.map +1 -1
- package/package.json +1 -1
- package/scripts/lib/run-repo-doctor.mjs +39 -26
- package/scripts/lib/run-repo-install.mjs +53 -9
|
@@ -16,7 +16,16 @@ export interface FileAction {
|
|
|
16
16
|
action: "create" | "update" | "remove" | "remove-dir" | "skip";
|
|
17
17
|
reason: string;
|
|
18
18
|
}
|
|
19
|
+
interface FilePathStat {
|
|
20
|
+
isFile(): boolean;
|
|
21
|
+
isDirectory(): boolean;
|
|
22
|
+
isSymbolicLink(): boolean;
|
|
23
|
+
}
|
|
19
24
|
export interface FileOps {
|
|
25
|
+
/** Observe the filesystem after all operations planned so far. */
|
|
26
|
+
lstat(rel: string): FilePathStat | undefined;
|
|
27
|
+
/** Whether a path exists after all operations planned so far. */
|
|
28
|
+
exists(rel: string): boolean;
|
|
20
29
|
/** Write `content`; false when the file already has exactly those bytes. */
|
|
21
30
|
write(rel: string, content: string | Buffer, reason?: string): boolean;
|
|
22
31
|
/** Remove a file; false when it is not there. */
|
|
@@ -27,10 +36,9 @@ export interface FileOps {
|
|
|
27
36
|
removeDir(rel: string, reason?: string): boolean;
|
|
28
37
|
/** Remove a directory only when it is empty. */
|
|
29
38
|
rmdirIfEmpty(rel: string): void;
|
|
30
|
-
/**
|
|
31
|
-
symlink(rel: string, target: string, reason?: string): boolean;
|
|
32
|
-
/** Remove a managed symlink, dangling or not. */
|
|
39
|
+
/** Remove a legacy managed symlink, dangling or not, without following it. */
|
|
33
40
|
removeSymlink(rel: string, reason?: string): boolean;
|
|
34
41
|
}
|
|
35
42
|
export declare function makeFileOps(repo: string, dryRun: boolean, actions: FileAction[]): FileOps;
|
|
43
|
+
export {};
|
|
36
44
|
//# sourceMappingURL=file-ops.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-ops.d.ts","sourceRoot":"","sources":["../../src/internal/file-ops.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"file-ops.d.ts","sourceRoot":"","sources":["../../src/internal/file-ops.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,YAAY,GAAG,MAAM,CAAC;IAC/D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,YAAY;IACpB,MAAM,IAAI,OAAO,CAAC;IAClB,WAAW,IAAI,OAAO,CAAC;IACvB,cAAc,IAAI,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,OAAO;IACtB,kEAAkE;IAClE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAC7C,iEAAiE;IACjE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7B,4EAA4E;IAC5E,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACvE,iDAAiD;IACjD,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9C,4EAA4E;IAC5E,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3C,kDAAkD;IAClD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACjD,gDAAgD;IAChD,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,8EAA8E;IAC9E,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtD;AAMD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,OAAO,CAqJzF"}
|
|
@@ -1,31 +1,115 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { lstatSync, mkdirSync, readdirSync, readFileSync, rmdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
const abs = (repo, rel) => join(repo, ...rel.split("/"));
|
|
4
4
|
const DEFAULT_REASON = "managed by enact-repo install";
|
|
5
5
|
export function makeFileOps(repo, dryRun, actions) {
|
|
6
|
-
|
|
6
|
+
const removedRoots = new Set();
|
|
7
|
+
const virtualFiles = new Map();
|
|
8
|
+
const virtualDirs = new Set();
|
|
9
|
+
const underRemovedRoot = (rel) => [...removedRoots].some((root) => rel === root || rel.startsWith(`${root}/`));
|
|
10
|
+
const virtualLstat = (rel) => {
|
|
11
|
+
if (virtualFiles.has(rel)) {
|
|
12
|
+
return { isFile: () => true, isDirectory: () => false, isSymbolicLink: () => false };
|
|
13
|
+
}
|
|
14
|
+
if (virtualDirs.has(rel)) {
|
|
15
|
+
return { isFile: () => false, isDirectory: () => true, isSymbolicLink: () => false };
|
|
16
|
+
}
|
|
17
|
+
if (underRemovedRoot(rel))
|
|
18
|
+
return undefined;
|
|
19
|
+
return lstatSync(abs(repo, rel), { throwIfNoEntry: false });
|
|
20
|
+
};
|
|
21
|
+
const hasSymlinkAncestor = (rel) => {
|
|
22
|
+
const parts = rel.split("/").slice(0, -1);
|
|
23
|
+
for (let i = 1; i <= parts.length; i++) {
|
|
24
|
+
const st = virtualLstat(parts.slice(0, i).join("/"));
|
|
25
|
+
if (!st)
|
|
26
|
+
return false;
|
|
27
|
+
if (st.isSymbolicLink())
|
|
28
|
+
return true;
|
|
29
|
+
if (!st.isDirectory())
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
};
|
|
34
|
+
const markRemoved = (rel) => {
|
|
35
|
+
for (const path of virtualFiles.keys())
|
|
36
|
+
if (path === rel || path.startsWith(`${rel}/`))
|
|
37
|
+
virtualFiles.delete(path);
|
|
38
|
+
for (const path of virtualDirs)
|
|
39
|
+
if (path === rel || path.startsWith(`${rel}/`))
|
|
40
|
+
virtualDirs.delete(path);
|
|
41
|
+
removedRoots.add(rel);
|
|
42
|
+
};
|
|
43
|
+
const markWritten = (rel, content) => {
|
|
44
|
+
const parts = rel.split("/").slice(0, -1);
|
|
45
|
+
for (let i = 1; i <= parts.length; i++) {
|
|
46
|
+
const dirRel = parts.slice(0, i).join("/");
|
|
47
|
+
if (!virtualLstat(dirRel))
|
|
48
|
+
virtualDirs.add(dirRel);
|
|
49
|
+
}
|
|
50
|
+
virtualFiles.set(rel, content);
|
|
51
|
+
};
|
|
52
|
+
const virtualDirectoryIsEmpty = (rel) => {
|
|
53
|
+
const prefix = `${rel}/`;
|
|
54
|
+
if ([...virtualFiles.keys(), ...virtualDirs].some((path) => path.startsWith(prefix)))
|
|
55
|
+
return false;
|
|
56
|
+
if (underRemovedRoot(rel))
|
|
57
|
+
return true;
|
|
58
|
+
return readdirSync(abs(repo, rel)).every((name) => virtualLstat(`${rel}/${name}`) === undefined);
|
|
59
|
+
};
|
|
60
|
+
const fileAlreadyHasContent = (rel, existing, content) => {
|
|
61
|
+
if (!existing?.isFile() || existing.isSymbolicLink())
|
|
62
|
+
return false;
|
|
63
|
+
return (virtualFiles.get(rel) ?? readFileSync(abs(repo, rel))).equals(content);
|
|
64
|
+
};
|
|
65
|
+
const writeToDisk = (path, existing, content) => {
|
|
66
|
+
if (dryRun)
|
|
67
|
+
return;
|
|
68
|
+
if (existing?.isSymbolicLink())
|
|
69
|
+
rmSync(path, { force: true });
|
|
70
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
71
|
+
writeFileSync(path, content);
|
|
72
|
+
};
|
|
73
|
+
const ops = {
|
|
74
|
+
lstat(rel) {
|
|
75
|
+
return virtualLstat(rel);
|
|
76
|
+
},
|
|
77
|
+
exists(rel) {
|
|
78
|
+
return virtualLstat(rel) !== undefined;
|
|
79
|
+
},
|
|
7
80
|
write(rel, content, reason) {
|
|
8
81
|
const path = abs(repo, rel);
|
|
9
82
|
const buf = Buffer.isBuffer(content) ? content : Buffer.from(content);
|
|
10
|
-
|
|
83
|
+
if (hasSymlinkAncestor(rel)) {
|
|
84
|
+
throw new Error(`Refusing to write managed file through a symlinked ancestor: ${rel}`);
|
|
85
|
+
}
|
|
86
|
+
const existing = virtualLstat(rel);
|
|
87
|
+
const existed = existing !== undefined;
|
|
11
88
|
// Byte-identical is a no-op, so a re-install reports nothing and stays
|
|
12
|
-
// idempotent rather than churning mtimes and dry-run output.
|
|
13
|
-
|
|
89
|
+
// idempotent rather than churning mtimes and dry-run output. A symlink is
|
|
90
|
+
// never a managed file even when its target has identical bytes.
|
|
91
|
+
if (fileAlreadyHasContent(rel, existing, buf))
|
|
14
92
|
return false;
|
|
15
93
|
actions.push({ file: rel, action: existed ? "update" : "create", reason: reason ?? DEFAULT_REASON });
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
writeFileSync(path, buf);
|
|
19
|
-
}
|
|
94
|
+
writeToDisk(path, existing, buf);
|
|
95
|
+
markWritten(rel, buf);
|
|
20
96
|
return true;
|
|
21
97
|
},
|
|
22
98
|
remove(rel, reason) {
|
|
23
99
|
const path = abs(repo, rel);
|
|
24
|
-
if (
|
|
100
|
+
if (hasSymlinkAncestor(rel))
|
|
101
|
+
return false;
|
|
102
|
+
const existing = virtualLstat(rel);
|
|
103
|
+
if (!existing)
|
|
25
104
|
return false;
|
|
26
105
|
actions.push({ file: rel, action: "remove", reason: reason ?? DEFAULT_REASON });
|
|
27
|
-
if (!dryRun)
|
|
28
|
-
|
|
106
|
+
if (!dryRun) {
|
|
107
|
+
if (existing.isSymbolicLink())
|
|
108
|
+
unlinkSync(path);
|
|
109
|
+
else
|
|
110
|
+
rmSync(path, { force: true });
|
|
111
|
+
}
|
|
112
|
+
markRemoved(rel);
|
|
29
113
|
return true;
|
|
30
114
|
},
|
|
31
115
|
// Recorded rather than silently passed over: a retained file that appears
|
|
@@ -36,58 +120,47 @@ export function makeFileOps(repo, dryRun, actions) {
|
|
|
36
120
|
},
|
|
37
121
|
removeDir(rel, reason) {
|
|
38
122
|
const path = abs(repo, rel);
|
|
39
|
-
if (
|
|
123
|
+
if (hasSymlinkAncestor(rel))
|
|
124
|
+
return false;
|
|
125
|
+
const existing = virtualLstat(rel);
|
|
126
|
+
if (!existing)
|
|
40
127
|
return false;
|
|
41
128
|
actions.push({ file: rel, action: "remove-dir", reason: reason ?? "directory" });
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
129
|
+
if (!dryRun) {
|
|
130
|
+
if (existing.isSymbolicLink())
|
|
131
|
+
unlinkSync(path);
|
|
132
|
+
else
|
|
133
|
+
rmSync(path, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
134
|
+
}
|
|
135
|
+
markRemoved(rel);
|
|
48
136
|
return true;
|
|
49
137
|
},
|
|
50
138
|
rmdirIfEmpty(rel) {
|
|
51
139
|
const path = abs(repo, rel);
|
|
52
|
-
if (
|
|
140
|
+
if (hasSymlinkAncestor(rel))
|
|
141
|
+
return;
|
|
142
|
+
const existing = virtualLstat(rel);
|
|
143
|
+
if (existing?.isDirectory() && !existing.isSymbolicLink() && virtualDirectoryIsEmpty(rel)) {
|
|
53
144
|
actions.push({ file: rel, action: "remove-dir", reason: "empty directory" });
|
|
54
|
-
if (!dryRun)
|
|
145
|
+
if (!dryRun && lstatSync(path, { throwIfNoEntry: false })?.isDirectory())
|
|
55
146
|
rmdirSync(path);
|
|
147
|
+
markRemoved(rel);
|
|
56
148
|
}
|
|
57
149
|
},
|
|
58
|
-
// Symlinks need lstat, not existsSync: existsSync FOLLOWS a symlink, so it
|
|
59
|
-
// reports false for a dangling one -- exactly the state a link can be in
|
|
60
|
-
// mid-uninstall, once its target is already gone.
|
|
61
|
-
symlink(rel, target, reason) {
|
|
62
|
-
const path = abs(repo, rel);
|
|
63
|
-
const st = lstatSync(path, { throwIfNoEntry: false });
|
|
64
|
-
if (st?.isSymbolicLink() && readlinkSync(path) === target)
|
|
65
|
-
return false;
|
|
66
|
-
actions.push({
|
|
67
|
-
file: rel,
|
|
68
|
-
action: st ? "update" : "create",
|
|
69
|
-
reason: reason ?? (st ? "replacing stale entry with managed symlink" : "managed symlink"),
|
|
70
|
-
});
|
|
71
|
-
if (!dryRun) {
|
|
72
|
-
// Whatever is there (a stale real directory from a pre-symlink install,
|
|
73
|
-
// a wrong-target link, ...) sits inside a path this installer
|
|
74
|
-
// exclusively generates -- safe to replace outright.
|
|
75
|
-
if (st)
|
|
76
|
-
rmSync(path, { recursive: true, force: true });
|
|
77
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
78
|
-
symlinkSync(target, path);
|
|
79
|
-
}
|
|
80
|
-
return true;
|
|
81
|
-
},
|
|
82
150
|
removeSymlink(rel, reason) {
|
|
83
151
|
const path = abs(repo, rel);
|
|
84
|
-
if (
|
|
152
|
+
if (hasSymlinkAncestor(rel))
|
|
153
|
+
return false;
|
|
154
|
+
const existing = virtualLstat(rel);
|
|
155
|
+
if (!existing?.isSymbolicLink())
|
|
85
156
|
return false;
|
|
86
157
|
actions.push({ file: rel, action: "remove", reason: reason ?? "managed symlink" });
|
|
87
158
|
if (!dryRun)
|
|
88
|
-
|
|
159
|
+
unlinkSync(path);
|
|
160
|
+
markRemoved(rel);
|
|
89
161
|
return true;
|
|
90
162
|
},
|
|
91
163
|
};
|
|
164
|
+
return ops;
|
|
92
165
|
}
|
|
93
166
|
//# sourceMappingURL=file-ops.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-ops.js","sourceRoot":"","sources":["../../src/internal/file-ops.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"file-ops.js","sourceRoot":"","sources":["../../src/internal/file-ops.ts"],"names":[],"mappings":"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"}
|
package/dist/projection.d.ts
CHANGED
|
@@ -4,20 +4,6 @@ import type { ProjectionPlatform } from "./internal/types.js";
|
|
|
4
4
|
* every skill whose SKILL.md sets `disable-model-invocation: true`.
|
|
5
5
|
*/
|
|
6
6
|
export declare function materializeCodexSkillMetadata(skillsSource: string, targetSkills: string): void;
|
|
7
|
-
/**
|
|
8
|
-
* Project the bundle into a self-contained Claude Code plugin root at
|
|
9
|
-
* `targetRoot` (`.claude-plugin/plugin.json`, skills/, host-flagged
|
|
10
|
-
* hooks/hooks.json, .mcp.json, README.md) WITHOUT registering any marketplace
|
|
11
|
-
* or touching a Claude home. The derived manifest is computed in memory from
|
|
12
|
-
* the canonical `.agents/plugin.json`; nothing is written back to the source.
|
|
13
|
-
*
|
|
14
|
-
* `options.symlinkSkills` (default false, preserving prior behavior for any
|
|
15
|
-
* other caller): when true, the `skills` component is NOT copied into
|
|
16
|
-
* `targetRoot` at all. This is for callers (the repo-local claude installer)
|
|
17
|
-
* that place a relative symlink to a shared skills tree at that path
|
|
18
|
-
* themselves — copying here would just be overwritten or leave stale
|
|
19
|
-
* duplicate bytes behind.
|
|
20
|
-
*/
|
|
21
7
|
/**
|
|
22
8
|
* Project the bundle into a self-contained plugin root for `platform` at
|
|
23
9
|
* `targetRoot` (manifest, skills/, hooks, mcp, README) WITHOUT registering any
|
|
@@ -31,16 +17,12 @@ export declare function materializeCodexSkillMetadata(skillsSource: string, targ
|
|
|
31
17
|
* name safe. Hand-writing loose per-host files instead was what forced a flat,
|
|
32
18
|
* un-namespaced shared skills pool in the first place.
|
|
33
19
|
*/
|
|
34
|
-
export declare function projectPluginRoot(pluginRoot: string, targetRoot: string, platform: ProjectionPlatform
|
|
35
|
-
symlinkSkills?: boolean;
|
|
36
|
-
}): {
|
|
20
|
+
export declare function projectPluginRoot(pluginRoot: string, targetRoot: string, platform: ProjectionPlatform): {
|
|
37
21
|
name: string;
|
|
38
22
|
version: string;
|
|
39
23
|
};
|
|
40
24
|
/** Claude-specific alias, kept because it names the common case at call sites. */
|
|
41
|
-
export declare function projectClaudePluginRoot(pluginRoot: string, targetRoot: string
|
|
42
|
-
symlinkSkills?: boolean;
|
|
43
|
-
}): {
|
|
25
|
+
export declare function projectClaudePluginRoot(pluginRoot: string, targetRoot: string): {
|
|
44
26
|
name: string;
|
|
45
27
|
version: string;
|
|
46
28
|
};
|
package/dist/projection.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../src/projection.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAkB,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAqE9E;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAY9F;AAOD
|
|
1
|
+
{"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../src/projection.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAkB,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAqE9E;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAY9F;AAOD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,kBAAkB,GAC3B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA+CnC;AAED,kFAAkF;AAClF,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAEjH"}
|
package/dist/projection.js
CHANGED
|
@@ -97,20 +97,6 @@ function stringField(manifest, key) {
|
|
|
97
97
|
const value = manifest[key];
|
|
98
98
|
return typeof value === "string" && value.trim() ? value : undefined;
|
|
99
99
|
}
|
|
100
|
-
/**
|
|
101
|
-
* Project the bundle into a self-contained Claude Code plugin root at
|
|
102
|
-
* `targetRoot` (`.claude-plugin/plugin.json`, skills/, host-flagged
|
|
103
|
-
* hooks/hooks.json, .mcp.json, README.md) WITHOUT registering any marketplace
|
|
104
|
-
* or touching a Claude home. The derived manifest is computed in memory from
|
|
105
|
-
* the canonical `.agents/plugin.json`; nothing is written back to the source.
|
|
106
|
-
*
|
|
107
|
-
* `options.symlinkSkills` (default false, preserving prior behavior for any
|
|
108
|
-
* other caller): when true, the `skills` component is NOT copied into
|
|
109
|
-
* `targetRoot` at all. This is for callers (the repo-local claude installer)
|
|
110
|
-
* that place a relative symlink to a shared skills tree at that path
|
|
111
|
-
* themselves — copying here would just be overwritten or leave stale
|
|
112
|
-
* duplicate bytes behind.
|
|
113
|
-
*/
|
|
114
100
|
/**
|
|
115
101
|
* Project the bundle into a self-contained plugin root for `platform` at
|
|
116
102
|
* `targetRoot` (manifest, skills/, hooks, mcp, README) WITHOUT registering any
|
|
@@ -124,7 +110,7 @@ function stringField(manifest, key) {
|
|
|
124
110
|
* name safe. Hand-writing loose per-host files instead was what forced a flat,
|
|
125
111
|
* un-namespaced shared skills pool in the first place.
|
|
126
112
|
*/
|
|
127
|
-
export function projectPluginRoot(pluginRoot, targetRoot, platform
|
|
113
|
+
export function projectPluginRoot(pluginRoot, targetRoot, platform) {
|
|
128
114
|
const enact = readManifestFile(pluginRoot, "enact");
|
|
129
115
|
const manifest = manifestForPlatform(platform, enact);
|
|
130
116
|
const validation = validateManifest(platform, manifest);
|
|
@@ -138,7 +124,6 @@ export function projectPluginRoot(pluginRoot, targetRoot, platform, options = {}
|
|
|
138
124
|
rmSync(targetRoot, { recursive: true, force: true });
|
|
139
125
|
mkdirSync(targetRoot, { recursive: true });
|
|
140
126
|
const agentsEntry = cleanRel(manifest.agents) ?? cleanRel(DEFAULT_PATHS.agents);
|
|
141
|
-
const skillsEntry = cleanRel(manifest.skills) ?? cleanRel(DEFAULT_PATHS.skills);
|
|
142
127
|
const hooksFileRel = cleanRel(manifest.hooks) ?? cleanRel(DEFAULT_PATHS.hooks) ?? "hooks/hooks.json";
|
|
143
128
|
// Hosts disagree on the MCP config filename: Cursor reads `mcp.json`, the
|
|
144
129
|
// rest read `.mcp.json`. Copying the canonical name verbatim left Cursor
|
|
@@ -147,8 +132,6 @@ export function projectPluginRoot(pluginRoot, targetRoot, platform, options = {}
|
|
|
147
132
|
const mcpEntry = cleanRel(manifest.mcpServers) ?? cleanRel(DEFAULT_PATHS.mcpServers);
|
|
148
133
|
const mcpTargetName = DEFAULT_MCP_FILENAME[platform];
|
|
149
134
|
for (const entry of projectionEntries(manifest, platform)) {
|
|
150
|
-
if (options.symlinkSkills && entry === skillsEntry)
|
|
151
|
-
continue;
|
|
152
135
|
const sourcePath = resolve(pluginRoot, entry);
|
|
153
136
|
if (!existsSync(sourcePath))
|
|
154
137
|
continue;
|
|
@@ -174,7 +157,7 @@ export function projectPluginRoot(pluginRoot, targetRoot, platform, options = {}
|
|
|
174
157
|
return { name, version: stringField(manifest, "version") ?? "0.0.0" };
|
|
175
158
|
}
|
|
176
159
|
/** Claude-specific alias, kept because it names the common case at call sites. */
|
|
177
|
-
export function projectClaudePluginRoot(pluginRoot, targetRoot
|
|
178
|
-
return projectPluginRoot(pluginRoot, targetRoot, "claude"
|
|
160
|
+
export function projectClaudePluginRoot(pluginRoot, targetRoot) {
|
|
161
|
+
return projectPluginRoot(pluginRoot, targetRoot, "claude");
|
|
179
162
|
}
|
|
180
163
|
//# sourceMappingURL=projection.js.map
|
package/dist/projection.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projection.js","sourceRoot":"","sources":["../src/projection.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"projection.js","sourceRoot":"","sources":["../src/projection.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
|
|
48
48
|
import { spawnSync } from "node:child_process";
|
|
49
49
|
import { createHash } from "node:crypto";
|
|
50
|
-
import { existsSync, lstatSync, readdirSync, readFileSync
|
|
50
|
+
import { existsSync, lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
51
51
|
import { homedir } from "node:os";
|
|
52
52
|
import { basename, join } from "node:path";
|
|
53
53
|
import * as TOML from "@iarna/toml";
|
|
@@ -301,43 +301,57 @@ function runHookDoctor(repo, enactHook) {
|
|
|
301
301
|
// lock integrity — every canonical managed file present + hash match, plus
|
|
302
302
|
// no foreign content inside an owned root.
|
|
303
303
|
// ---------------------------------------------------------------------------
|
|
304
|
+
function firstSymlinkComponent(repo, rel) {
|
|
305
|
+
const parts = rel.split("/");
|
|
306
|
+
for (let i = 1; i <= parts.length; i++) {
|
|
307
|
+
const component = parts.slice(0, i).join("/");
|
|
308
|
+
const st = lstatSync(abs(repo, component), { throwIfNoEntry: false });
|
|
309
|
+
if (!st) return null;
|
|
310
|
+
if (st.isSymbolicLink()) return component;
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function managedSymlinkIssue(repo, rel) {
|
|
316
|
+
const component = firstSymlinkComponent(repo, rel);
|
|
317
|
+
if (!component) return null;
|
|
318
|
+
return component === rel
|
|
319
|
+
? `${rel} (managed path is a symlink)`
|
|
320
|
+
: `${rel} (managed path traverses symlink ${component})`;
|
|
321
|
+
}
|
|
322
|
+
|
|
304
323
|
function checkLockIntegrity(repo, name, lock) {
|
|
305
324
|
const missingFiles = [];
|
|
306
325
|
const driftedFiles = [];
|
|
326
|
+
const symlinkIssues = [];
|
|
327
|
+
const recordSymlinkIssue = (rel) => {
|
|
328
|
+
const issue = managedSymlinkIssue(repo, rel);
|
|
329
|
+
if (issue) symlinkIssues.push(issue);
|
|
330
|
+
return issue !== null;
|
|
331
|
+
};
|
|
332
|
+
recordSymlinkIssue(lockRel(name));
|
|
307
333
|
for (const [rel, { sha256: want }] of Object.entries(lock.files)) {
|
|
334
|
+
if (recordSymlinkIssue(rel)) continue;
|
|
308
335
|
if (!existsSync(abs(repo, rel))) missingFiles.push(rel);
|
|
309
336
|
else if (fileSha(repo, rel) !== want) driftedFiles.push(rel);
|
|
310
337
|
}
|
|
311
338
|
const tomlIssues = [];
|
|
312
339
|
for (const [rel, record] of Object.entries(lock.toml)) {
|
|
340
|
+
if (recordSymlinkIssue(rel)) continue;
|
|
313
341
|
const path = abs(repo, rel);
|
|
314
342
|
const found = existsSync(path) ? findBlock(readFileSync(path, "utf8"), name) : null;
|
|
315
343
|
if (!found || sha256(found.block) !== record.sha256) tomlIssues.push(rel);
|
|
316
344
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if (!st) {
|
|
328
|
-
symlinkIssues.push(`${rel} (missing)`);
|
|
329
|
-
continue;
|
|
330
|
-
}
|
|
331
|
-
if (!st.isSymbolicLink()) {
|
|
332
|
-
symlinkIssues.push(`${rel} (not a symlink)`);
|
|
333
|
-
continue;
|
|
334
|
-
}
|
|
335
|
-
const target = readlinkSync(path);
|
|
336
|
-
if (target !== record.target) {
|
|
337
|
-
symlinkIssues.push(`${rel} (target is ${target}, expected ${record.target})`);
|
|
338
|
-
continue;
|
|
339
|
-
}
|
|
340
|
-
if (!existsSync(path)) symlinkIssues.push(`${rel} (dangling -> ${target})`);
|
|
345
|
+
for (const rel of Object.keys(lock.gitignore ?? {})) recordSymlinkIssue(rel);
|
|
346
|
+
if (lock.config) recordSymlinkIssue(CONFIG_PATH);
|
|
347
|
+
for (const record of Object.values(lock.displaced?.files ?? {})) {
|
|
348
|
+
if (typeof record?.storedAt === "string") recordSymlinkIssue(record.storedAt);
|
|
349
|
+
}
|
|
350
|
+
const legacySymlinks = Object.keys(lock.symlinks ?? {}).sort();
|
|
351
|
+
if (legacySymlinks.length > 0) {
|
|
352
|
+
symlinkIssues.push(
|
|
353
|
+
`legacy lock records managed symlinks; re-run install to replace them with real files: ${legacySymlinks.join(", ")}`,
|
|
354
|
+
);
|
|
341
355
|
}
|
|
342
356
|
const rootIssues = [];
|
|
343
357
|
for (const root of lock.ownedRoots ?? []) {
|
|
@@ -407,7 +421,6 @@ function checkNativeHostPresence(repo, lock, host) {
|
|
|
407
421
|
...Object.keys(lock.files ?? {}),
|
|
408
422
|
...Object.keys(lock.merged ?? {}),
|
|
409
423
|
...Object.keys(lock.toml ?? {}),
|
|
410
|
-
...Object.keys(lock.symlinks ?? {}),
|
|
411
424
|
].filter((rel) => isNativeHostPath(host, rel));
|
|
412
425
|
const nativePaths = [...new Set(rels)].sort();
|
|
413
426
|
return {
|
|
@@ -585,7 +585,6 @@ export function buildPlan(pluginRoot, bundle, hosts, repo) {
|
|
|
585
585
|
json: new Map(),
|
|
586
586
|
toml: new Map(),
|
|
587
587
|
gitignore: new Map(),
|
|
588
|
-
symlinks: new Map(),
|
|
589
588
|
skills: new Set(),
|
|
590
589
|
};
|
|
591
590
|
plan.gitignore.set(GITIGNORE_PATH, GITIGNORE_LINES);
|
|
@@ -623,6 +622,37 @@ function assignJsonPath(doc, path, value) {
|
|
|
623
622
|
at[path.at(-1)] = jsonValue(value);
|
|
624
623
|
}
|
|
625
624
|
|
|
625
|
+
function renderRootJson(value, indent = 0, prefixWidth = 0) {
|
|
626
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
627
|
+
if (Array.isArray(value)) {
|
|
628
|
+
if (value.length === 0) return "[]";
|
|
629
|
+
const compact = `[${value.map((entry) => JSON.stringify(entry)).join(", ")}]`;
|
|
630
|
+
if (value.every((entry) => entry === null || typeof entry !== "object") && prefixWidth + compact.length <= 120) {
|
|
631
|
+
return compact;
|
|
632
|
+
}
|
|
633
|
+
const padding = " ".repeat(indent);
|
|
634
|
+
const childPadding = " ".repeat(indent + 2);
|
|
635
|
+
return `[\n${value
|
|
636
|
+
.map((entry, index) => {
|
|
637
|
+
const suffix = index === value.length - 1 ? "" : ",";
|
|
638
|
+
return `${childPadding}${renderRootJson(entry, indent + 2)}${suffix}`;
|
|
639
|
+
})
|
|
640
|
+
.join("\n")}\n${padding}]`;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const entries = Object.entries(value);
|
|
644
|
+
if (entries.length === 0) return "{}";
|
|
645
|
+
const padding = " ".repeat(indent);
|
|
646
|
+
const childPadding = " ".repeat(indent + 2);
|
|
647
|
+
return `{\n${entries
|
|
648
|
+
.map(([key, entry], index) => {
|
|
649
|
+
const prefix = `${childPadding}${JSON.stringify(key)}: `;
|
|
650
|
+
const suffix = index === entries.length - 1 ? "" : ",";
|
|
651
|
+
return `${prefix}${renderRootJson(entry, indent + 2, prefix.length)}${suffix}`;
|
|
652
|
+
})
|
|
653
|
+
.join("\n")}\n${padding}}`;
|
|
654
|
+
}
|
|
655
|
+
|
|
626
656
|
/** Build the exact native files from the bundle, without reading a repo file. */
|
|
627
657
|
function materializeAuthoritativeFiles(plan, bundle) {
|
|
628
658
|
const files = new Map(plan.files);
|
|
@@ -640,7 +670,9 @@ function materializeAuthoritativeFiles(plan, bundle) {
|
|
|
640
670
|
if (!Array.isArray(at[key])) at[key] = [];
|
|
641
671
|
at[key].push(jsonValue(entry.value));
|
|
642
672
|
}
|
|
643
|
-
|
|
673
|
+
const rendered =
|
|
674
|
+
rel === ".mcp.json" || rel === "opencode.json" ? renderRootJson(doc) : JSON.stringify(doc, null, 2);
|
|
675
|
+
files.set(rel, Buffer.from(`${rendered}\n`));
|
|
644
676
|
}
|
|
645
677
|
for (const [rel, toml] of plan.toml) files.set(rel, Buffer.from(renderTomlBlock(bundle.name, toml.tables)));
|
|
646
678
|
for (const [rel, lines] of plan.gitignore) files.set(rel, Buffer.from(`${lines.join("\n")}\n`));
|
|
@@ -659,7 +691,7 @@ function isBoundaryOrAncestor(rel, boundaries) {
|
|
|
659
691
|
|
|
660
692
|
function clearOwnedRoot(repo, root, boundaries, ops) {
|
|
661
693
|
const rootPath = abs(repo, root);
|
|
662
|
-
const rootState =
|
|
694
|
+
const rootState = ops.lstat(root);
|
|
663
695
|
if (!rootState) return;
|
|
664
696
|
if (!rootState.isDirectory() || rootState.isSymbolicLink()) {
|
|
665
697
|
ops.removeSymlink(root, "replacing non-directory owned root");
|
|
@@ -671,7 +703,7 @@ function clearOwnedRoot(repo, root, boundaries, ops) {
|
|
|
671
703
|
}
|
|
672
704
|
const visit = (rel) => {
|
|
673
705
|
const path = abs(repo, rel);
|
|
674
|
-
const state =
|
|
706
|
+
const state = ops.lstat(rel);
|
|
675
707
|
if (!state) return;
|
|
676
708
|
// A boundary is entirely outside this deployment. Do not descend into it:
|
|
677
709
|
// logs/caches are owned by their runtime, not by this installer.
|
|
@@ -719,6 +751,15 @@ function runAuthoritativeRepoInstall(pluginRoot, options, context) {
|
|
|
719
751
|
);
|
|
720
752
|
}
|
|
721
753
|
|
|
754
|
+
// Current plans cannot create links. Remove every exact link recorded by a
|
|
755
|
+
// legacy lock before rebuilding roots, first cutting any linked ancestor so
|
|
756
|
+
// a descendant record can never escape the repository. FileOps carries this
|
|
757
|
+
// state virtually during --dry-run, keeping its plan identical to a real run.
|
|
758
|
+
for (const rel of Object.keys(prev.symlinks ?? {})) {
|
|
759
|
+
replaceSymlinkAncestors(rel, ops);
|
|
760
|
+
ops.removeSymlink(rel, "legacy managed symlink hard cut");
|
|
761
|
+
}
|
|
762
|
+
|
|
722
763
|
// A reconciliation owns the complete declared component surface, not just
|
|
723
764
|
// the hosts selected for this invocation. Otherwise a previous host's
|
|
724
765
|
// root would become an accidental preservation zone when the host list is
|
|
@@ -739,7 +780,7 @@ function runAuthoritativeRepoInstall(pluginRoot, options, context) {
|
|
|
739
780
|
|
|
740
781
|
const files = {};
|
|
741
782
|
for (const [rel, content] of desired) {
|
|
742
|
-
replaceSymlinkAncestors(
|
|
783
|
+
replaceSymlinkAncestors(rel, ops);
|
|
743
784
|
ops.write(rel, content, "authoritative bundle projection");
|
|
744
785
|
files[rel] = { sha256: sha256(content) };
|
|
745
786
|
}
|
|
@@ -767,7 +808,6 @@ function runAuthoritativeRepoInstall(pluginRoot, options, context) {
|
|
|
767
808
|
version: bundle.version,
|
|
768
809
|
hosts,
|
|
769
810
|
files: sortObject(files),
|
|
770
|
-
symlinks: {},
|
|
771
811
|
toml: {},
|
|
772
812
|
gitignore: {},
|
|
773
813
|
config,
|
|
@@ -848,12 +888,16 @@ export const fileSha = (repo, rel) => sha256(readFileSync(abs(repo, rel)));
|
|
|
848
888
|
*
|
|
849
889
|
* Scoped to ancestors strictly inside the repo, and only those that are links.
|
|
850
890
|
*/
|
|
851
|
-
function replaceSymlinkAncestors(
|
|
891
|
+
function replaceSymlinkAncestors(rel, ops) {
|
|
852
892
|
const parts = rel.split("/").slice(0, -1);
|
|
853
893
|
for (let i = 1; i <= parts.length; i++) {
|
|
854
894
|
const dirRel = parts.slice(0, i).join("/");
|
|
855
|
-
const st =
|
|
856
|
-
if (st?.isSymbolicLink())
|
|
895
|
+
const st = ops.lstat(dirRel);
|
|
896
|
+
if (st?.isSymbolicLink()) {
|
|
897
|
+
ops.removeSymlink(dirRel, "replacing a symlinked directory with real content");
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
if (!st || !st.isDirectory()) return;
|
|
857
901
|
}
|
|
858
902
|
}
|
|
859
903
|
|