forgedock 1.0.11 → 1.0.13
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/bin/forgedock.mjs +130 -16
- package/package.json +1 -1
package/bin/forgedock.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
readFileSync,
|
|
10
10
|
writeFileSync,
|
|
11
11
|
renameSync,
|
|
12
|
+
copyFileSync,
|
|
12
13
|
} from "fs";
|
|
13
14
|
import { execSync } from "child_process";
|
|
14
15
|
import readline from "readline";
|
|
@@ -36,6 +37,42 @@ const YELLOW = "\x1b[33m";
|
|
|
36
37
|
const CYAN = "\x1b[36m";
|
|
37
38
|
const RED = "\x1b[31m";
|
|
38
39
|
const RESET = "\x1b[0m";
|
|
40
|
+
const DIM = "\x1b[2m";
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Splash — the ForgeDock wordmark shown at startup
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
const LOGO_LINES = [
|
|
47
|
+
" ╔═╗╔═╗╦═╗╔═╗╔═╗╔╦╗╔═╗╔═╗╦╔═",
|
|
48
|
+
" ╠╣ ║ ║╠╦╝║ ╦║╣ ║║║ ║║ ╠╩╗",
|
|
49
|
+
" ╚ ╚═╝╩╚═╚═╝╚═╝═╩╝╚═╝╚═╝╩ ╩",
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
function getVersion() {
|
|
53
|
+
try {
|
|
54
|
+
const pkg = JSON.parse(
|
|
55
|
+
readFileSync(join(FORGE_HOME, "package.json"), "utf-8"),
|
|
56
|
+
);
|
|
57
|
+
return pkg.version || "";
|
|
58
|
+
} catch {
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function splash() {
|
|
64
|
+
const version = getVersion();
|
|
65
|
+
console.log("");
|
|
66
|
+
for (const line of LOGO_LINES) {
|
|
67
|
+
console.log(`${BOLD}${CYAN}${line}${RESET}`);
|
|
68
|
+
}
|
|
69
|
+
console.log("");
|
|
70
|
+
console.log(` ${DIM}GitHub as a knowledge graph for AI agents${RESET}`);
|
|
71
|
+
if (version) {
|
|
72
|
+
console.log(` ${DIM}v${version}${RESET}`);
|
|
73
|
+
}
|
|
74
|
+
console.log("");
|
|
75
|
+
}
|
|
39
76
|
|
|
40
77
|
/**
|
|
41
78
|
* Prompt the user for a yes/no confirmation.
|
|
@@ -55,11 +92,34 @@ async function confirmOverwrite(question) {
|
|
|
55
92
|
});
|
|
56
93
|
rl.question(question, (answer) => {
|
|
57
94
|
rl.close();
|
|
58
|
-
resolve(
|
|
95
|
+
resolve(
|
|
96
|
+
answer.trim().toLowerCase() === "y" ||
|
|
97
|
+
answer.trim().toLowerCase() === "yes",
|
|
98
|
+
);
|
|
59
99
|
});
|
|
60
100
|
});
|
|
61
101
|
}
|
|
62
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Returns true if `dir` is inside a git work tree. Used to avoid generating
|
|
105
|
+
* forge.yaml in non-project directories — e.g. when npx defaults cwd to a
|
|
106
|
+
* system folder (C:\Windows) because the launching shell sat on an
|
|
107
|
+
* unsupported UNC/WSL path. The command is a fixed string with no
|
|
108
|
+
* interpolated input, so there is no shell-injection surface (ref #151).
|
|
109
|
+
*/
|
|
110
|
+
function isGitWorkTree(dir) {
|
|
111
|
+
try {
|
|
112
|
+
const out = execSync("git rev-parse --is-inside-work-tree", {
|
|
113
|
+
cwd: dir,
|
|
114
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
115
|
+
encoding: "utf-8",
|
|
116
|
+
});
|
|
117
|
+
return out.trim() === "true";
|
|
118
|
+
} catch {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
63
123
|
async function findMarkdownFiles(dir) {
|
|
64
124
|
const results = [];
|
|
65
125
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
@@ -110,10 +170,17 @@ async function install() {
|
|
|
110
170
|
updated++;
|
|
111
171
|
}
|
|
112
172
|
} else {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
)
|
|
116
|
-
|
|
173
|
+
// Existing regular file — a ForgeDock-shipped command copied by a prior
|
|
174
|
+
// install (common on Windows, where symlinks aren't permitted). It is
|
|
175
|
+
// ForgeDock-managed (we only iterate shipped command names), so refresh
|
|
176
|
+
// it: skip if already current, otherwise overwrite with a fresh copy.
|
|
177
|
+
if (readFileSync(file, "utf-8") === readFileSync(target, "utf-8")) {
|
|
178
|
+
skipped++;
|
|
179
|
+
} else {
|
|
180
|
+
copyFileSync(file, target);
|
|
181
|
+
console.log(` ${YELLOW}Updated${RESET}: ${rel}`);
|
|
182
|
+
updated++;
|
|
183
|
+
}
|
|
117
184
|
}
|
|
118
185
|
} catch (err) {
|
|
119
186
|
if (err.code !== "ENOENT") {
|
|
@@ -122,9 +189,20 @@ async function install() {
|
|
|
122
189
|
);
|
|
123
190
|
throw err;
|
|
124
191
|
}
|
|
125
|
-
// Doesn't exist — create symlink
|
|
126
|
-
|
|
127
|
-
|
|
192
|
+
// Doesn't exist — create it. Prefer a symlink; fall back to a copy on
|
|
193
|
+
// systems where symlink creation is not permitted (e.g. Windows without
|
|
194
|
+
// Developer Mode / admin), which would otherwise throw EPERM. See #587.
|
|
195
|
+
try {
|
|
196
|
+
await symlink(file, target);
|
|
197
|
+
console.log(` ${GREEN}Installed${RESET}: ${rel}`);
|
|
198
|
+
} catch (linkErr) {
|
|
199
|
+
if (linkErr.code === "EPERM" || linkErr.code === "EACCES") {
|
|
200
|
+
copyFileSync(file, target);
|
|
201
|
+
console.log(` ${GREEN}Installed${RESET} (copy): ${rel}`);
|
|
202
|
+
} else {
|
|
203
|
+
throw linkErr;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
128
206
|
installed++;
|
|
129
207
|
}
|
|
130
208
|
}
|
|
@@ -159,10 +237,24 @@ async function install() {
|
|
|
159
237
|
);
|
|
160
238
|
console.log("");
|
|
161
239
|
|
|
162
|
-
// Auto-generate forge.yaml if missing — no second command needed
|
|
163
|
-
|
|
240
|
+
// Auto-generate forge.yaml if missing — no second command needed.
|
|
241
|
+
// Only do this inside a real git project; otherwise cwd may be a system or
|
|
242
|
+
// read-only directory (e.g. C:\Windows when npx is launched from a WSL path),
|
|
243
|
+
// and writing forge.yaml there would crash. See #585.
|
|
244
|
+
const cwd = process.cwd();
|
|
245
|
+
const forgeYamlPath = join(cwd, "forge.yaml");
|
|
164
246
|
if (!existsSync(forgeYamlPath)) {
|
|
165
|
-
|
|
247
|
+
if (isGitWorkTree(cwd)) {
|
|
248
|
+
await init(true);
|
|
249
|
+
} else {
|
|
250
|
+
console.log(
|
|
251
|
+
` ${YELLOW}Skipping forge.yaml generation${RESET} — current directory is not a git project.`,
|
|
252
|
+
);
|
|
253
|
+
console.log(
|
|
254
|
+
` Run ${CYAN}npx forgedock init${RESET} inside your project directory to generate it.`,
|
|
255
|
+
);
|
|
256
|
+
console.log("");
|
|
257
|
+
}
|
|
166
258
|
}
|
|
167
259
|
}
|
|
168
260
|
|
|
@@ -180,14 +272,23 @@ async function uninstall() {
|
|
|
180
272
|
|
|
181
273
|
try {
|
|
182
274
|
const stats = await lstat(target);
|
|
275
|
+
const { unlink } = await import("fs/promises");
|
|
183
276
|
if (stats.isSymbolicLink()) {
|
|
184
277
|
const current = await readlink(target);
|
|
185
278
|
if (current === file) {
|
|
186
|
-
const { unlink } = await import("fs/promises");
|
|
187
279
|
await unlink(target);
|
|
188
280
|
console.log(` ${RED}Removed${RESET}: ${rel}`);
|
|
189
281
|
removed++;
|
|
190
282
|
}
|
|
283
|
+
} else if (
|
|
284
|
+
readFileSync(file, "utf-8") === readFileSync(target, "utf-8")
|
|
285
|
+
) {
|
|
286
|
+
// Regular file installed by ForgeDock in copy mode (content matches the
|
|
287
|
+
// shipped command). Safe to remove. Content-differing files are left
|
|
288
|
+
// in place in case the user edited them.
|
|
289
|
+
await unlink(target);
|
|
290
|
+
console.log(` ${RED}Removed${RESET}: ${rel}`);
|
|
291
|
+
removed++;
|
|
191
292
|
}
|
|
192
293
|
} catch (err) {
|
|
193
294
|
if (err.code !== "ENOENT") {
|
|
@@ -511,15 +612,15 @@ project:
|
|
|
511
612
|
name: "${projectName}"
|
|
512
613
|
owner: "${owner}"
|
|
513
614
|
repo: "${repo}"
|
|
514
|
-
description: "${description.replace(/\\/g,
|
|
615
|
+
description: "${description.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"
|
|
515
616
|
|
|
516
617
|
# =============================================================================
|
|
517
618
|
# PATHS (REQUIRED)
|
|
518
619
|
# =============================================================================
|
|
519
620
|
|
|
520
621
|
paths:
|
|
521
|
-
root: "${cwd.replace(/\\/g,
|
|
522
|
-
worktree_base: "${worktreeBase.replace(/\\/g,
|
|
622
|
+
root: "${cwd.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"
|
|
623
|
+
worktree_base: "${worktreeBase.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"
|
|
523
624
|
|
|
524
625
|
# =============================================================================
|
|
525
626
|
# BRANCHES (REQUIRED)
|
|
@@ -583,7 +684,18 @@ branches:
|
|
|
583
684
|
# - '"status": "ok"'
|
|
584
685
|
`;
|
|
585
686
|
|
|
586
|
-
|
|
687
|
+
try {
|
|
688
|
+
writeFileSync(outputPath, content, "utf-8");
|
|
689
|
+
} catch (err) {
|
|
690
|
+
console.error("");
|
|
691
|
+
console.error(
|
|
692
|
+
` ${RED}Error${RESET}: Could not write forge.yaml to ${outputPath} — ${err.code ?? err.message}`,
|
|
693
|
+
);
|
|
694
|
+
console.error(
|
|
695
|
+
` Run ${CYAN}npx forgedock init${RESET} from a writable project directory.`,
|
|
696
|
+
);
|
|
697
|
+
process.exit(1);
|
|
698
|
+
}
|
|
587
699
|
|
|
588
700
|
console.log(` ${GREEN}Created${RESET}: forge.yaml`);
|
|
589
701
|
console.log("");
|
|
@@ -641,6 +753,8 @@ function help() {
|
|
|
641
753
|
console.log("");
|
|
642
754
|
}
|
|
643
755
|
|
|
756
|
+
splash();
|
|
757
|
+
|
|
644
758
|
switch (command) {
|
|
645
759
|
case "install":
|
|
646
760
|
await install();
|