Skip to content

Commit fde3226

Browse files
committed
Merge pull request 'fix(npm): improve missing binary diagnostics' (#18) from wangyue111/gitlink-cli:fix/npm-missing-binary-diagnostics into master
2 parents c4ec40a + cdca68f commit fde3226

10 files changed

Lines changed: 275 additions & 176 deletions

File tree

.github/workflows/release.yml

Lines changed: 51 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -27,171 +27,68 @@ jobs:
2727
run: |
2828
mkdir -p dist
2929
VERSION=${GITHUB_REF#refs/tags/v}
30-
31-
for pair in "darwin amd64" "darwin arm64" "linux amd64" "linux arm64"; do
32-
GOOS=$(echo $pair | cut -d' ' -f1)
33-
GOARCH=$(echo $pair | cut -d' ' -f2)
30+
MODULE="github.com/gitlink-org/gitlink-cli"
31+
LDFLAGS="-s -w -X ${MODULE}/cmd.Version=${VERSION}"
32+
33+
for pair in \
34+
"darwin amd64" \
35+
"darwin arm64" \
36+
"linux amd64" \
37+
"linux arm64" \
38+
"windows amd64" \
39+
"windows arm64"; do
40+
GOOS=$(echo "$pair" | cut -d' ' -f1)
41+
GOARCH=$(echo "$pair" | cut -d' ' -f2)
42+
OUT="gitlink-cli"
43+
if [ "$GOOS" = "windows" ]; then
44+
OUT="gitlink-cli.exe"
45+
fi
46+
3447
echo "Building ${GOOS}-${GOARCH}..."
35-
GOOS=$GOOS GOARCH=$GOARCH go build -ldflags "-s -w -X 'github.com/gitlink-org/gitlink-cli/cmd.Version=${VERSION}'" -o dist/gitlink-cli .
36-
cd dist
37-
tar -czf "gitlink-cli_${VERSION}_${GOOS}_${GOARCH}.tar.gz" gitlink-cli
38-
rm gitlink-cli
39-
cd ..
48+
BUILD_DIR="dist/gitlink-cli_${VERSION}_${GOOS}_${GOARCH}"
49+
mkdir -p "$BUILD_DIR"
50+
CGO_ENABLED=0 GOOS=$GOOS GOARCH=$GOARCH go build -ldflags "$LDFLAGS" -o "$BUILD_DIR/$OUT" .
51+
52+
if [ "$GOOS" = "windows" ]; then
53+
(cd "$BUILD_DIR" && zip -q "../gitlink-cli_${VERSION}_${GOOS}_${GOARCH}.zip" "$OUT")
54+
else
55+
tar -czf "dist/gitlink-cli_${VERSION}_${GOOS}_${GOARCH}.tar.gz" -C "$BUILD_DIR" "$OUT"
56+
fi
57+
rm -rf "$BUILD_DIR"
4058
done
4159
60+
ls -lh dist
61+
4262
- name: Create Release
4363
uses: softprops/action-gh-release@v2
4464
with:
45-
files: dist/*.tar.gz
65+
files: |
66+
dist/*.tar.gz
67+
dist/*.zip
4668
generate_release_notes: true
4769

4870
- name: Build npm package
4971
run: |
5072
VERSION=${GITHUB_REF#refs/tags/v}
51-
mkdir -p npm-pkg/bin npm-pkg/scripts npm-pkg/skills
52-
53-
# Copy skills
54-
cp -r skills/* npm-pkg/skills/
55-
56-
# Copy bin wrappers
57-
cp bin/cli.js npm-pkg/bin/ 2>/dev/null || cat > npm-pkg/bin/cli.js << 'NODEEOF'
58-
#!/usr/bin/env node
59-
const {execFileSync} = require("child_process");
60-
const path = require("path");
61-
const bin = path.join(__dirname, "..", "bin", "gitlink-cli");
62-
try { process.exit(execFileSync(bin, process.argv.slice(2), {stdio:"inherit"}).status); }
63-
catch(e) { process.exit(e.status || 1); }
64-
NODEEOF
65-
66-
cat > npm-pkg/bin/install-skills.js << 'NODEEOF'
67-
#!/usr/bin/env node
68-
const {execSync} = require("child_process");
69-
const path = require("path");
70-
const skillsDir = path.join(__dirname, "..", "skills");
71-
try {
72-
execSync(`npx skills add "${skillsDir}" -y -g`, {stdio:"inherit"});
73-
} catch(e) {
74-
console.error("Failed to install skills:", e.message);
75-
process.exit(1);
76-
}
77-
NODEEOF
78-
79-
# Copy install.js
80-
cp scripts/install.js npm-pkg/scripts/ 2>/dev/null || cat > npm-pkg/scripts/install.js << 'NODEEOF'
81-
#!/usr/bin/env node
82-
"use strict";
83-
const os = require("os");
84-
const path = require("path");
85-
const fs = require("fs");
86-
const https = require("https");
87-
const http = require("http");
88-
const {execSync} = require("child_process");
89-
90-
const PACKAGE = require("../package.json");
91-
const VERSION = PACKAGE.version;
92-
const BINARY_NAME = "gitlink-cli";
93-
const RELEASE_BASE = "https://github.com";
94-
const REPO_OWNER = "ccfos";
95-
const REPO_NAME = "gitlink-cli";
96-
97-
function getPlatformInfo() {
98-
const platform = os.platform();
99-
const arch = os.arch();
100-
const pmap = {darwin:"darwin",linux:"linux",win32:"windows"};
101-
const amap = {x64:"amd64",arm64:"arm64"};
102-
const p=pmap[platform], a=amap[arch];
103-
if(!p||!a) throw new Error(`Unsupported: ${platform}-${arch}`);
104-
return {platform:p,arch:a};
105-
}
106-
107-
function fetch(url) {
108-
return new Promise((resolve,reject) => {
109-
const mod=url.startsWith("https")?https:http;
110-
let count=0;
111-
function req(u) {
112-
if(++count>5) return reject(new Error("Too many redirects"));
113-
mod.get(u,(res) => {
114-
if([301,302,307,308].includes(res.statusCode)&&res.headers.location){
115-
let loc=res.headers.location;
116-
if(loc.startsWith("/")){const p=new URL(u);loc=p.protocol+"//"+p.host+loc}
117-
return req(loc);
118-
}
119-
if(res.statusCode!==200) return reject(new Error(`HTTP ${res.statusCode}`));
120-
const c=[];res.on("data",d=>c.push(d));res.on("end",()=>resolve(Buffer.concat(c)));
121-
}).on("error",reject);
122-
}
123-
req(url);
124-
});
125-
}
126-
127-
async function main() {
128-
try {
129-
const {platform,arch} = getPlatformInfo();
130-
const binDir = path.join(__dirname,"..","bin");
131-
if(!fs.existsSync(binDir)) fs.mkdirSync(binDir,{recursive:true});
132-
const binaryPath = path.join(binDir,BINARY_NAME);
133-
134-
if(fs.existsSync(binaryPath)) {
135-
try {
136-
const out = execSync(`"${binaryPath}" version`,{encoding:"utf-8",timeout:5000});
137-
if(out.includes(VERSION)) { console.log(`${BINARY_NAME} v${VERSION} already installed.`); return; }
138-
} catch(e) {}
139-
fs.unlinkSync(binaryPath);
140-
}
141-
142-
const assetName = `gitlink-cli_${VERSION}_${platform}_${arch}.tar.gz`;
143-
const url = `${RELEASE_BASE}/${REPO_OWNER}/${REPO_NAME}/releases/download/v${VERSION}/${assetName}`;
144-
console.log(`Downloading ${url}...`);
145-
const data = await fetch(url);
146-
const tmp = path.join(binDir,"dl.tar.gz");
147-
fs.writeFileSync(tmp,data);
148-
execSync(`tar -xzf "${tmp}" -C "${binDir}"`,{stdio:"pipe"});
149-
fs.unlinkSync(tmp);
150-
fs.chmodSync(binaryPath,0o755);
151-
console.log(`${BINARY_NAME} v${VERSION} installed.`);
152-
} catch(err) {
153-
console.warn(`⚠ Binary download failed: ${err.message}`);
154-
console.warn(`Skills are installed. Install binary manually:`);
155-
console.warn(`npm run postinstall`);
156-
}
157-
}
158-
main();
159-
NODEEOF
160-
161-
# Create README
162-
cp README.md npm-pkg/
163-
164-
# Create package.json
165-
cat > npm-pkg/package.json << PKGEOF
166-
{
167-
"name": "@gitlink-ai/cli",
168-
"version": "${VERSION}",
169-
"description": "GitLink CLI — 面向 AI Agent 的 GitLink 命令行工具",
170-
"main": "bin/cli.js",
171-
"bin": {
172-
"gitlink-cli": "./bin/cli.js",
173-
"gitlink-cli-install-skills": "./bin/install-skills.js"
174-
},
175-
"scripts": {
176-
"postinstall": "node scripts/install.js"
177-
},
178-
"files": [
179-
"bin/",
180-
"scripts/",
181-
"skills/",
182-
"README.md",
183-
"package.json"
184-
],
185-
"repository": {
186-
"type": "git",
187-
"url": "https://github.com/ccfos/gitlink-cli.git"
188-
},
189-
"keywords": ["gitlink", "cli", "ai-agent", "skills"],
190-
"author": "",
191-
"license": "MulanPSL-2.0"
192-
}
193-
PKGEOF
194-
73+
export VERSION
74+
rm -rf npm-pkg
75+
mkdir -p npm-pkg
76+
cp -R npm/. npm-pkg/
77+
cp README.md npm-pkg/README.md
78+
rm -rf npm-pkg/skills
79+
cp -R skills npm-pkg/skills
80+
81+
node <<'NODE'
82+
const fs = require('fs');
83+
const pkgPath = 'npm-pkg/package.json';
84+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
85+
pkg.version = process.env.VERSION;
86+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
87+
NODE
88+
89+
chmod +x npm-pkg/bin/cli.js
90+
chmod +x npm-pkg/bin/install-skills.js
91+
19592
cd npm-pkg
19693
npm publish --access public
19794
env:

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,16 @@ gitlink-cli auth status # Shows "✓ Logged in via GITLINK_TOKEN environment v
407407

408408
Priority: `GITLINK_TOKEN` env var > keyring/file stored token. When the env var is not set, the original interactive login flow works as before.
409409

410+
### Q: What if npm installs successfully but `gitlink-cli` reports a missing binary?
411+
412+
Reinstall first:
413+
414+
```bash
415+
npm install -g @gitlink-ai/cli
416+
```
417+
418+
If the error persists, check whether the release page contains the asset for your platform, for example `gitlink-cli_<version>_windows_amd64.zip` on Windows x64. You can also download the binary manually from the release page or build from source with `go install .`.
419+
410420
### Q: Where are credentials stored on Windows?
411421

412422
gitlink-cli uses Windows Credential Manager for secure token storage. If Credential Manager is unavailable, it automatically falls back to file storage (`~/.config/gitlink-cli/credentials`).

README.zh-CN.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,16 @@ gitlink-cli auth status # 显示 "✓ Logged in via GITLINK_TOKEN environment
386386

387387
Token 优先级:`GITLINK_TOKEN` 环境变量 > keyring/文件存储的 token。不设置环境变量时完全兼容原有交互式登录。
388388

389+
### Q: npm 安装成功但 `gitlink-cli` 提示缺少二进制怎么办?
390+
391+
先尝试重新安装:
392+
393+
```bash
394+
npm install -g @gitlink-ai/cli
395+
```
396+
397+
如果仍然失败,请检查 Release 页面是否包含当前平台的资产,例如 Windows x64 对应 `gitlink-cli_<version>_windows_amd64.zip`。也可以从 Release 页面手动下载二进制,或使用 `go install .` 从源码构建。
398+
389399
### Q: Windows 上凭证存储在哪里?
390400

391401
gitlink-cli 使用 Windows Credential Manager 安全存储 Token。如果 Credential Manager 不可用,会自动降级到文件存储(`~/.config/gitlink-cli/credentials`)。

npm/bin/cli.js

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,78 @@
22

33
"use strict";
44

5+
const fs = require("fs");
56
const path = require("path");
67
const { execFileSync } = require("child_process");
78

8-
const ext = process.platform === "win32" ? ".exe" : "";
9-
const binaryPath = path.join(__dirname, "gitlink-cli" + ext);
9+
const BINARY_NAME = "gitlink-cli";
1010

11-
try {
12-
execFileSync(binaryPath, process.argv.slice(2), { stdio: "inherit" });
13-
} catch (err) {
14-
if (err.status !== undefined) {
15-
process.exit(err.status);
11+
function getBinaryName(platform = process.platform) {
12+
return platform === "win32" ? `${BINARY_NAME}.exe` : BINARY_NAME;
13+
}
14+
15+
function getBinaryPath(platform = process.platform, baseDir = __dirname) {
16+
return path.join(baseDir, getBinaryName(platform));
17+
}
18+
19+
function formatMissingBinaryError(
20+
binaryPath,
21+
platform = process.platform,
22+
arch = process.arch
23+
) {
24+
return [
25+
`Error: ${BINARY_NAME} binary not found at ${binaryPath}`,
26+
`Platform: ${platform}/${arch}`,
27+
"",
28+
"The npm package was installed, but the native binary is missing.",
29+
"This usually means the release asset for your platform is unavailable or postinstall failed.",
30+
"",
31+
"Try reinstalling:",
32+
" npm install -g @gitlink-ai/cli",
33+
"",
34+
"If the problem persists, check the GitLink CLI release assets:",
35+
" https://www.gitlink.org.cn/Gitlink/gitlink-cli/releases",
36+
].join("\n");
37+
}
38+
39+
function run(args = process.argv.slice(2), options = {}) {
40+
const platform = options.platform || process.platform;
41+
const arch = options.arch || process.arch;
42+
const binaryPath = options.binaryPath || getBinaryPath(platform);
43+
const execFile = options.execFileSync || execFileSync;
44+
const stderr = options.stderr || process.stderr;
45+
const exit = options.exit || process.exit;
46+
47+
function failMissingBinary() {
48+
stderr.write(`${formatMissingBinaryError(binaryPath, platform, arch)}\n`);
49+
return exit(1);
50+
}
51+
52+
if (!fs.existsSync(binaryPath)) {
53+
return failMissingBinary();
54+
}
55+
56+
try {
57+
execFile(binaryPath, args, { stdio: "inherit" });
58+
} catch (err) {
59+
if (err.code === "ENOENT") {
60+
return failMissingBinary();
61+
}
62+
if (err.status !== undefined) {
63+
return exit(err.status);
64+
}
65+
stderr.write(`Failed to run ${BINARY_NAME}: ${err.message}\n`);
66+
return exit(1);
1667
}
17-
console.error(`Failed to run gitlink-cli: ${err.message}`);
18-
console.error(
19-
"Binary may not be installed. Try reinstalling: npm install -g @gitlink-ai/cli"
20-
);
21-
process.exit(1);
2268
}
69+
70+
if (require.main === module) {
71+
run();
72+
}
73+
74+
module.exports = {
75+
getBinaryName,
76+
getBinaryPath,
77+
formatMissingBinaryError,
78+
run,
79+
};

npm/bin/install-skills.js

100644100755
File mode changed.

npm/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"gitlink-cli-install-skills": "bin/install-skills.js"
88
},
99
"scripts": {
10-
"postinstall": "node scripts/install.js"
10+
"postinstall": "node scripts/install.js",
11+
"test": "node test/install.test.js && node test/cli.test.js"
1112
},
1213
"keywords": [
1314
"gitlink",

0 commit comments

Comments
 (0)