-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcheck-installers.mjs
More file actions
156 lines (138 loc) · 6.25 KB
/
Copy pathcheck-installers.mjs
File metadata and controls
156 lines (138 loc) · 6.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!/usr/bin/env node
import {
cpSync,
existsSync,
mkdirSync,
mkdtempSync,
readFileSync,
readdirSync,
rmSync,
statSync,
writeFileSync,
} from "node:fs";
import { createHash } from "node:crypto";
import { tmpdir } from "node:os";
import { dirname, join, relative, resolve } from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const root = dirname(dirname(fileURLToPath(import.meta.url)));
const source = join(root, "skills", "codebase-visualize");
const powershell = "powershell.exe";
const bash = "C:/Program Files/Git/bin/bash.exe";
function assert(condition, message) {
if (!condition) throw new Error(message);
}
function run(command, args, options = {}) {
return spawnSync(command, args, {
cwd: options.cwd || root,
encoding: "utf8",
shell: false,
env: options.env || process.env,
});
}
function runOk(command, args, options = {}) {
const result = run(command, args, options);
if (result.error || result.status !== 0) {
throw new Error(`${command} failed:\n${result.error?.message || result.stderr || result.stdout}`);
}
return result;
}
function files(dir) {
const output = [];
function walk(current) {
for (const entry of readdirSync(current, { withFileTypes: true })) {
const path = join(current, entry.name);
if (entry.isDirectory()) walk(path);
else if (entry.isFile()) output.push(relative(dir, path).split("\\").join("/"));
}
}
walk(dir);
return output.sort();
}
function hash(path) {
return createHash("sha256").update(readFileSync(path)).digest("hex");
}
function assertExactCopy(installed) {
const expected = files(source);
const actual = files(installed);
assert(JSON.stringify(actual) === JSON.stringify(expected), `installed file list drifted: ${installed}`);
for (const path of expected) {
assert(hash(join(installed, path)) === hash(join(source, path)), `installed file content drifted: ${path}`);
}
}
function bashPath(path) {
return resolve(path).replace(/^([A-Za-z]):/, (_, drive) => `/${drive.toLowerCase()}`).split("\\").join("/");
}
function destination(base, tool) {
return join(base, tool === "claude" ? ".claude" : ".agents", "skills", "codebase-visualize");
}
function invokeInstaller(shell, tool, scope, base, force = false, scriptRoot = root) {
if (shell === "powershell") {
const args = [
"-NoProfile",
"-ExecutionPolicy", "Bypass",
"-File", join(scriptRoot, "scripts", "install.ps1"),
"-Tool", tool,
"-Scope", scope,
scope === "user" ? "-HomeRoot" : "-ProjectRoot", base,
];
if (force) args.push("-Force");
return run(powershell, args);
}
const args = [
bashPath(join(scriptRoot, "scripts", "install.sh")),
"--tool", tool,
"--scope", scope,
scope === "user" ? "--home" : "--project-root", bashPath(base),
];
if (force) args.push("--force");
return run(bash, args);
}
const scratch = mkdtempSync(join(tmpdir(), "codebase-visualize-installers-"));
try {
for (const shell of ["powershell", "bash"]) {
for (const tool of ["claude", "codex"]) {
for (const scope of ["user", "project"]) {
const base = join(scratch, `${shell}-${tool}-${scope}`);
mkdirSync(base, { recursive: true });
const installed = destination(base, tool);
const fresh = invokeInstaller(shell, tool, scope, base);
assert(fresh.status === 0, `${shell} ${tool}/${scope} fresh install failed: ${fresh.stderr}`);
assert(fresh.stdout.includes("Installation complete. No repository was visualized."), `${shell} installer output lost no-visualize marker`);
assert(fresh.stdout.includes("Invoke explicitly"), `${shell} installer output lost invocation guidance`);
assert(fresh.stdout.includes("visualize.mjs"), `${shell} installer output lost visualize command marker`);
assertExactCopy(installed);
assert(!existsSync(join(base, "docs", "ai", "visualize")), `${shell} installer visualized the install base`);
const refused = invokeInstaller(shell, tool, scope, base);
assert(refused.status !== 0, `${shell} ${tool}/${scope} did not refuse existing target`);
assert((refused.stderr + refused.stdout).includes(shell === "powershell" ? "-Force" : "--force"), `${shell} refusal output lost force guidance`);
writeFileSync(join(installed, "stale.txt"), "must be pruned\n");
const forced = invokeInstaller(shell, tool, scope, base, true);
assert(forced.status === 0, `${shell} ${tool}/${scope} force install failed: ${forced.stderr}`);
assert(!existsSync(join(installed, "stale.txt")), `${shell} ${tool}/${scope} force install retained stale files`);
assertExactCopy(installed);
const target = join(base, "runtime-target");
mkdirSync(join(target, "src"), { recursive: true });
writeFileSync(join(target, "src", "index.js"), "export const installed = true;\n");
runOk(process.execPath, [join(installed, "scripts", "visualize.mjs"), "--target", target]);
assert(existsSync(join(target, "docs", "ai", "visualize", "codebase-graph.json")), "installed runtime did not write graph");
assert(existsSync(join(target, "docs", "ai", "visualize", "codebase-map.html")), "installed runtime did not write map");
}
}
}
for (const shell of ["powershell", "bash"]) {
const broken = join(scratch, `broken-${shell}`);
mkdirSync(join(broken, "scripts"), { recursive: true });
mkdirSync(join(broken, "skills", "codebase-visualize"), { recursive: true });
cpSync(join(root, "scripts", shell === "powershell" ? "install.ps1" : "install.sh"), join(broken, "scripts", shell === "powershell" ? "install.ps1" : "install.sh"));
writeFileSync(join(broken, "skills", "codebase-visualize", "SKILL.md"), "incomplete\n");
const base = join(scratch, `broken-dest-${shell}`);
mkdirSync(base);
const result = invokeInstaller(shell, "codex", "user", base, false, broken);
assert(result.status !== 0, `${shell} incomplete-source install unexpectedly succeeded`);
assert(!existsSync(destination(base, "codex")), `${shell} incomplete-source install left a partial target`);
}
} finally {
rmSync(scratch, { recursive: true, force: true });
}
console.log("check:installers OK - PowerShell and bash tool/scope matrices verified.");