@agent_forge/forge 1.14.1 → 1.15.1
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/package.json +7 -4
- package/run.js +25 -48
- package/install.js +0 -182
package/package.json
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent_forge/forge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.1",
|
|
4
4
|
"description": "AI 开发质量门禁引擎 — 结构化门禁管道,在 AI 生成的代码进入仓库前进行质量锻造",
|
|
5
5
|
"bin": {
|
|
6
6
|
"forge": "run.js"
|
|
7
7
|
},
|
|
8
|
-
"
|
|
9
|
-
"
|
|
8
|
+
"optionalDependencies": {
|
|
9
|
+
"@agent_forge/forge-darwin-arm64": "1.15.1",
|
|
10
|
+
"@agent_forge/forge-darwin-x64": "1.15.1",
|
|
11
|
+
"@agent_forge/forge-linux-arm64": "1.15.1",
|
|
12
|
+
"@agent_forge/forge-linux-x64": "1.15.1",
|
|
13
|
+
"@agent_forge/forge-win32-x64": "1.15.1"
|
|
10
14
|
},
|
|
11
15
|
"files": [
|
|
12
|
-
"install.js",
|
|
13
16
|
"run.js",
|
|
14
17
|
"README.md"
|
|
15
18
|
],
|
package/run.js
CHANGED
|
@@ -1,55 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { spawn
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
console.error("[forge] Recovered binary from .old backup");
|
|
24
|
-
} catch (e) {
|
|
25
|
-
// Restored binary is broken — put it back as .old so we don't lose it
|
|
26
|
-
try { fs.renameSync(binaryPath, oldPath); } catch (_) {}
|
|
27
|
-
console.error("[forge] WARNING: .old binary is also broken");
|
|
28
|
-
}
|
|
29
|
-
} else {
|
|
30
|
-
// Both exist — verify current binary works, then clean up .old
|
|
31
|
-
try {
|
|
32
|
-
execSync(`"${binaryPath}" --version`, { timeout: 5000, stdio: "pipe", env: { ...process.env, FORGE_SKIP_UPDATE_CHECK: "1" } });
|
|
33
|
-
fs.unlinkSync(oldPath);
|
|
34
|
-
} catch (e) {
|
|
35
|
-
// Current binary broken — replace with .old (rename first, then probe)
|
|
36
|
-
try { fs.unlinkSync(binaryPath); } catch (_) {}
|
|
37
|
-
try {
|
|
38
|
-
fs.renameSync(oldPath, binaryPath);
|
|
39
|
-
execSync(`"${binaryPath}" --version`, { timeout: 5000, stdio: "pipe", env: { ...process.env, FORGE_SKIP_UPDATE_CHECK: "1" } });
|
|
40
|
-
console.error("[forge] Recovered binary from .old backup");
|
|
41
|
-
} catch (e2) {
|
|
42
|
-
console.error("[forge] WARNING: both binary and .old are broken");
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
3
|
+
const { spawn } = require("child_process");
|
|
4
|
+
|
|
5
|
+
// 通过 optionalDependencies 平台子包定位当前平台的二进制。
|
|
6
|
+
// npm install 时按 os/cpu 只装匹配当前平台的一个子包(@agent_forge/forge-<platform>-<arch>),
|
|
7
|
+
// 二进制随子包落进 node_modules,无需 install script——npm 12 起 install scripts 默认
|
|
8
|
+
// 禁用,平台分包是官方推荐的二进制分发方式(esbuild/rollup/turbo 同模式)。
|
|
9
|
+
const exe = process.platform === "win32" ? "forge.exe" : "forge";
|
|
10
|
+
const platformPkg = `@agent_forge/forge-${process.platform}-${process.arch}`;
|
|
11
|
+
|
|
12
|
+
// 支持的平台白名单(goreleaser 构建矩阵:linux/darwin × amd64/arm64 + windows/amd64)。
|
|
13
|
+
// 不在此列的平台(如 windows-arm64)无对应子包,require.resolve 必失败——明确 stderr 提示,
|
|
14
|
+
// 不混入"子包未装"的静默 approve,避免用户误以为 Forge 正常工作却零拦截。
|
|
15
|
+
const SUPPORTED = new Set([
|
|
16
|
+
"darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-x64",
|
|
17
|
+
]);
|
|
18
|
+
const platKey = `${process.platform}-${process.arch}`;
|
|
19
|
+
if (!SUPPORTED.has(platKey)) {
|
|
20
|
+
console.error(`[forge] unsupported platform: ${platKey} (no prebuilt binary); failing open — hooks will not fire.`);
|
|
21
|
+
console.log('{"decision":"approve"}');
|
|
22
|
+
process.exit(0);
|
|
46
23
|
}
|
|
47
24
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
//
|
|
25
|
+
let binaryPath;
|
|
26
|
+
try {
|
|
27
|
+
binaryPath = require.resolve(`${platformPkg}/bin/${exe}`);
|
|
28
|
+
} catch (_) {
|
|
29
|
+
// 支持的平台但子包未装(npm 12 + --omit=optional / 安装中断)——静默 approve 避免阻塞 hooks。
|
|
53
30
|
console.log('{"decision":"approve"}');
|
|
54
31
|
process.exit(0);
|
|
55
32
|
}
|
package/install.js
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
const { execSync } = require("child_process");
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
const path = require("path");
|
|
4
|
-
const https = require("https");
|
|
5
|
-
const { createWriteStream } = require("fs");
|
|
6
|
-
const { pipeline } = require("stream/promises");
|
|
7
|
-
|
|
8
|
-
const VERSION = require("./package.json").version;
|
|
9
|
-
|
|
10
|
-
function getPlatform() {
|
|
11
|
-
const platform = process.platform;
|
|
12
|
-
const arch = process.arch;
|
|
13
|
-
const osMap = { darwin: "darwin", linux: "linux", win32: "windows" };
|
|
14
|
-
const archMap = { x64: "x86_64", arm64: "aarch64" };
|
|
15
|
-
|
|
16
|
-
const goos = osMap[platform];
|
|
17
|
-
const goarch = archMap[arch];
|
|
18
|
-
|
|
19
|
-
if (!goos || !goarch) {
|
|
20
|
-
throw new Error(`Unsupported platform: ${platform}/${arch}`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return { goos, goarch };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function getBinaryName() {
|
|
27
|
-
return process.platform === "win32" ? "forge.exe" : "forge";
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function download(url, dest) {
|
|
31
|
-
let lastError;
|
|
32
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
33
|
-
if (attempt > 0) {
|
|
34
|
-
const delay = Math.min(1000 * Math.pow(2, attempt), 8000);
|
|
35
|
-
console.log(`Retrying (${attempt + 1}/3) after ${delay / 1000}s...`);
|
|
36
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
let currentUrl = url;
|
|
41
|
-
while (true) {
|
|
42
|
-
const res = await new Promise((resolve, reject) => {
|
|
43
|
-
https
|
|
44
|
-
.get(currentUrl, { timeout: 30000 }, resolve)
|
|
45
|
-
.on("error", reject);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
49
|
-
res.destroy();
|
|
50
|
-
currentUrl = res.headers.location;
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
if (res.statusCode !== 200) {
|
|
54
|
-
res.destroy();
|
|
55
|
-
throw new Error(`Download failed: HTTP ${res.statusCode}`);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
await pipeline(res, createWriteStream(dest));
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
} catch (err) {
|
|
62
|
-
lastError = err;
|
|
63
|
-
// Clean up partial download
|
|
64
|
-
try { fs.unlinkSync(dest); } catch (_) {}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
throw lastError || new Error("Download failed after 3 attempts");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Check if an existing binary matches the target version.
|
|
72
|
-
* Runs `forge --version` and parses the version string.
|
|
73
|
-
*/
|
|
74
|
-
function existingBinaryMatches(binaryPath) {
|
|
75
|
-
try {
|
|
76
|
-
const output = execSync(`"${binaryPath}" --version`, {
|
|
77
|
-
timeout: 5000,
|
|
78
|
-
stdio: "pipe",
|
|
79
|
-
env: { ...process.env, FORGE_SKIP_UPDATE_CHECK: "1" },
|
|
80
|
-
}).toString().trim();
|
|
81
|
-
// Output format: "forge version 0.14.4 (commit: ...)" or "forge version dev"
|
|
82
|
-
const match = output.match(/forge version (\S+)/);
|
|
83
|
-
return match && match[1] === VERSION;
|
|
84
|
-
} catch (_) {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async function main() {
|
|
90
|
-
const binDir = path.join(__dirname, "bin");
|
|
91
|
-
fs.mkdirSync(binDir, { recursive: true });
|
|
92
|
-
|
|
93
|
-
const binaryName = getBinaryName();
|
|
94
|
-
const binaryPath = path.join(binDir, binaryName);
|
|
95
|
-
|
|
96
|
-
// Skip download if existing binary already matches the target version.
|
|
97
|
-
// This avoids "file busy" errors on Windows when upgrading while
|
|
98
|
-
// forge hooks are actively running in another Claude Code session.
|
|
99
|
-
if (existingBinaryMatches(binaryPath)) {
|
|
100
|
-
console.log(`forge v${VERSION} already installed.`);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const { goos, goarch } = getPlatform();
|
|
105
|
-
const archiveName = `forge_${VERSION}_${goos}_${goarch}.tar.gz`;
|
|
106
|
-
|
|
107
|
-
// Support custom binary host for regions with poor GitHub connectivity.
|
|
108
|
-
// Usage: FORGE_BINARY_HOST=https://mirror.example.com npm install -g @agent_forge/forge
|
|
109
|
-
const baseUrl = process.env.FORGE_BINARY_HOST
|
|
110
|
-
|| "https://github.com/MjxUpUp/Forge/releases/download";
|
|
111
|
-
const url = `${baseUrl}/v${VERSION}/${archiveName}`;
|
|
112
|
-
|
|
113
|
-
const archivePath = path.join(binDir, archiveName);
|
|
114
|
-
console.log(`Downloading forge v${VERSION} for ${goos}/${goarch}...`);
|
|
115
|
-
|
|
116
|
-
await download(url, archivePath);
|
|
117
|
-
console.log(`Downloaded to ${archivePath}`);
|
|
118
|
-
|
|
119
|
-
// Extract using relative path (cwd=binDir) to avoid Windows tar
|
|
120
|
-
// interpreting "X:/path" as a remote host connection.
|
|
121
|
-
//
|
|
122
|
-
// On Windows, if forge.exe is currently running (e.g. another Claude Code
|
|
123
|
-
// session has hooks active), tar cannot overwrite it. Handle this by:
|
|
124
|
-
// 1. Renaming the old binary to .old (preserves running process)
|
|
125
|
-
// 2. Extracting the new binary
|
|
126
|
-
// 3. Cleaning up archive
|
|
127
|
-
// The run.js wrapper will recover from .old on next launch if needed.
|
|
128
|
-
let renamedOld = false;
|
|
129
|
-
if (process.platform === "win32" && fs.existsSync(binaryPath)) {
|
|
130
|
-
const oldPath = binaryPath + ".old";
|
|
131
|
-
try { fs.unlinkSync(oldPath); } catch (_) {}
|
|
132
|
-
try {
|
|
133
|
-
fs.renameSync(binaryPath, oldPath);
|
|
134
|
-
renamedOld = true;
|
|
135
|
-
} catch (_) {
|
|
136
|
-
// Cannot rename either — will try extract anyway
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
try {
|
|
141
|
-
execSync(`tar xzf "${archiveName}"`, { cwd: binDir, stdio: "pipe", timeout: 30000 });
|
|
142
|
-
} catch (err) {
|
|
143
|
-
// Extract failed — likely file busy on Windows
|
|
144
|
-
if (renamedOld) {
|
|
145
|
-
// Restore old binary so the package isn't left broken
|
|
146
|
-
const oldPath = binaryPath + ".old";
|
|
147
|
-
try {
|
|
148
|
-
if (!fs.existsSync(binaryPath)) {
|
|
149
|
-
fs.renameSync(oldPath, binaryPath);
|
|
150
|
-
console.error(`[forge] Extract failed, restored previous binary. Will upgrade on next launch.`);
|
|
151
|
-
}
|
|
152
|
-
} catch (_) {}
|
|
153
|
-
}
|
|
154
|
-
throw new Error(
|
|
155
|
-
`Failed to extract binary (file may be in use). ` +
|
|
156
|
-
`Close other Claude Code sessions and run: npm install -g @agent_forge/forge`
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Make executable (Unix)
|
|
161
|
-
if (process.platform !== "win32") {
|
|
162
|
-
fs.chmodSync(binaryPath, 0o755);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Cleanup archive and .old backup
|
|
166
|
-
fs.unlinkSync(archivePath);
|
|
167
|
-
if (renamedOld) {
|
|
168
|
-
try { fs.unlinkSync(binaryPath + ".old"); } catch (_) {}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
console.log(`forge v${VERSION} installed successfully.`);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
main().catch((err) => {
|
|
175
|
-
console.error("Installation failed:", err.message);
|
|
176
|
-
console.error("");
|
|
177
|
-
console.error("If GitHub is unreachable, set a mirror:");
|
|
178
|
-
console.error(" FORGE_BINARY_HOST=https://your-mirror.com npm install -g @agent_forge/forge");
|
|
179
|
-
console.error("");
|
|
180
|
-
console.error("Or download manually: https://github.com/MjxUpUp/Forge/releases");
|
|
181
|
-
process.exit(1);
|
|
182
|
-
});
|