@windaka-erp/erp-cli 0.3.2 → 0.4.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/README.md +73 -47
- package/bin/erp-cli-darwin-amd64 +0 -0
- package/bin/erp-cli-darwin-arm64 +0 -0
- package/bin/erp-cli-linux-amd64 +0 -0
- package/bin/erp-cli-linux-arm64 +0 -0
- package/bin/erp-cli-windows-amd64.exe +0 -0
- package/bin/erp-cli-windows-arm64.exe +0 -0
- package/package.json +3 -9
- package/scripts/run.js +32 -197
- package/scripts/install.js +0 -146
package/README.md
CHANGED
|
@@ -1,93 +1,119 @@
|
|
|
1
1
|
# @windaka-erp/erp-cli
|
|
2
2
|
|
|
3
|
-
物达通(WindaKa)物业管理 ERP
|
|
3
|
+
> 物达通(WindaKa)物业管理 ERP 的命令行工具,为人类和 AI Agent 设计。
|
|
4
|
+
> 一句话:**用户说自然语言,AI Agent 通过本工具操作 ERP + 检索知识库**。
|
|
4
5
|
|
|
5
|
-
##
|
|
6
|
+
## 它能做什么
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
- **知识库问答**:对接 RAGFlow,问"消防巡检怎么处理""报修流程""物业费标准"等问题,先检索知识库拿到原文,再结合上下文回答。
|
|
9
|
+
- **ERP 操作**:查/建工单、分派、接单、确认、回访等工单全流程;查看项目、通讯录、费用(逐步完善中)。
|
|
10
|
+
- **AI Agent 友好**:统一 JSON 输出、`--dry-run` 预览、危险操作确认、意图路由文档,AI 装上 Skill 即用。
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
## 安装
|
|
10
13
|
|
|
11
14
|
```bash
|
|
12
|
-
# 安装 CLI
|
|
13
15
|
npm install -g @windaka-erp/erp-cli
|
|
14
|
-
|
|
15
|
-
# 安装 Skills(必需,让 AI Agent 知道如何使用 CLI)
|
|
16
|
-
npx skills add windaka-erp/erp-cli -y -g
|
|
17
16
|
```
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
> 二进制已打包进 npm 包(含 Windows/macOS/Linux 的 x64/arm64 六个平台),安装即用,无需联网下载。
|
|
19
|
+
|
|
20
|
+
国内用户用淘宝镜像加速:
|
|
20
21
|
|
|
21
22
|
```bash
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
go build -o erp-cli .
|
|
23
|
+
npm install -g @windaka-erp/erp-cli --registry=https://registry.npmmirror.com
|
|
24
|
+
```
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
验证:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
erp-cli --version
|
|
28
30
|
```
|
|
29
31
|
|
|
30
32
|
## 快速开始
|
|
31
33
|
|
|
32
|
-
###
|
|
34
|
+
### 1. 配置 ERP 地址
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
erp-cli config init
|
|
38
|
+
# 默认连生产环境,也可指定:
|
|
39
|
+
erp-cli config init --base-url https://your-erp-server.com/prod-api
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. 登录(Device Flow,扫码/账号授权)
|
|
33
43
|
|
|
34
44
|
```bash
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
erp-cli auth login
|
|
46
|
+
# 输出登录链接 → 浏览器打开授权 → 自动保存 token
|
|
47
|
+
```
|
|
37
48
|
|
|
38
|
-
|
|
39
|
-
erp-cli --as me auth login
|
|
49
|
+
### 3. 开始用
|
|
40
50
|
|
|
41
|
-
|
|
42
|
-
erp-cli
|
|
51
|
+
```bash
|
|
52
|
+
erp-cli workorder +list # 我的待办工单
|
|
53
|
+
erp-cli workorder +info --id 123 --dept-id 456 # 工单详情
|
|
54
|
+
erp-cli knowledge +search --question "报修流程" # 检索知识库
|
|
43
55
|
```
|
|
44
56
|
|
|
45
|
-
|
|
57
|
+
## 配置知识库(RAGFlow)
|
|
46
58
|
|
|
47
|
-
|
|
59
|
+
知识库检索走独立的 RAGFlow 服务,首次用要配地址、API Key、知识库 ID:
|
|
48
60
|
|
|
49
61
|
```bash
|
|
50
|
-
|
|
51
|
-
|
|
62
|
+
erp-cli config ragflow
|
|
63
|
+
# 交互式引导:输入 RAGFlow 地址、API Key,自动列出知识库让你选
|
|
64
|
+
```
|
|
52
65
|
|
|
53
|
-
|
|
54
|
-
npx skills add windaka-erp/erp-cli -y -g
|
|
66
|
+
验证:
|
|
55
67
|
|
|
56
|
-
|
|
57
|
-
erp-cli
|
|
68
|
+
```bash
|
|
69
|
+
erp-cli knowledge +check # 检查 RAGFlow 连通性
|
|
70
|
+
erp-cli knowledge +list-datasets # 列出知识库
|
|
58
71
|
```
|
|
59
72
|
|
|
60
|
-
|
|
73
|
+
> 知识库检索不需要 ERP 登录,走 RAGFlow 自己的 API Key。
|
|
61
74
|
|
|
62
|
-
##
|
|
75
|
+
## 命令一览
|
|
63
76
|
|
|
64
77
|
```
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
config init/show/set/remove # 配置管理(ERP 地址、RAGFlow)
|
|
79
|
+
config ragflow # 交互式配置 RAGFlow 知识库
|
|
80
|
+
auth login/login-start/login-wait # 登录(两步登录适合 AI Agent)
|
|
81
|
+
auth status/logout # 登录状态
|
|
82
|
+
knowledge +search # 检索知识库(核心高频)
|
|
83
|
+
knowledge +list-datasets # 列出知识库
|
|
84
|
+
knowledge +check # 检查 RAGFlow 连通性
|
|
85
|
+
workorder +list # 我的待办工单
|
|
86
|
+
workorder +handle-list # 我经办的工单(含历史)
|
|
87
|
+
workorder +info # 工单详情(含处理记录)
|
|
88
|
+
workorder +search-user # 搜索员工(分派/转单用)
|
|
89
|
+
workorder +apportion # 分派工单
|
|
90
|
+
api GET/POST <path> # 原始 API 调用(兜底)
|
|
71
91
|
```
|
|
72
92
|
|
|
73
|
-
|
|
93
|
+
所有命令加 `--help` 看详细参数,加 `--dry-run` 预览请求不实际执行。
|
|
74
94
|
|
|
75
|
-
|
|
95
|
+
## 全局参数
|
|
96
|
+
|
|
97
|
+
- `--profile <name>` — 切换环境(prod/staging),默认 default
|
|
76
98
|
- `--dry-run` — 预览请求内容,不实际执行
|
|
99
|
+
- `--format json|table|csv` — 输出格式,默认 json
|
|
100
|
+
|
|
101
|
+
> 单用户模式:一个环境(profile)一个登录 token,命令不带任何身份参数。
|
|
77
102
|
|
|
78
|
-
## Agent
|
|
103
|
+
## AI Agent 使用
|
|
79
104
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
105
|
+
本工具为 AI Agent 设计。配合 **Skills**(AI 使用文档,含意图路由、命令参数、行为规则)使用,AI 即可:
|
|
106
|
+
- 听懂用户口语("我的待办""消防巡检怎么处理")→ 自动选对命令
|
|
107
|
+
- 写操作前确认、危险操作 dry-run
|
|
108
|
+
- 知识库优先:非 ERP 操作的问题默认先检索知识库
|
|
83
109
|
|
|
84
|
-
|
|
110
|
+
Skills 由作者单独发布,安装方式见对应平台说明。
|
|
85
111
|
|
|
86
|
-
##
|
|
112
|
+
## 环境要求
|
|
87
113
|
|
|
88
114
|
- Node.js >= 14.0.0
|
|
89
|
-
-
|
|
115
|
+
- 平台:Windows / macOS / Linux(均支持 x64 和 arm64)
|
|
90
116
|
|
|
91
117
|
## License
|
|
92
118
|
|
|
93
|
-
MIT
|
|
119
|
+
MIT
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windaka-erp/erp-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "ERP
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "物达通(WindaKa)物业管理 ERP 命令行工具,为人类和 AI Agent 设计",
|
|
5
5
|
"bin": {
|
|
6
6
|
"erp-cli": "scripts/run.js"
|
|
7
7
|
},
|
|
8
|
-
"scripts": {
|
|
9
|
-
"postinstall": "node scripts/install.js"
|
|
10
|
-
},
|
|
11
8
|
"files": [
|
|
12
9
|
"scripts/",
|
|
13
10
|
"bin/",
|
|
@@ -36,8 +33,5 @@
|
|
|
36
33
|
"cpu": [
|
|
37
34
|
"x64",
|
|
38
35
|
"arm64"
|
|
39
|
-
]
|
|
40
|
-
"dependencies": {
|
|
41
|
-
"@windaka-erp/erp-cli": "^0.2.0"
|
|
42
|
-
}
|
|
36
|
+
]
|
|
43
37
|
}
|
package/scripts/run.js
CHANGED
|
@@ -1,218 +1,53 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
// run.js — erp-cli 启动器。二进制已打包进 npm 包的 bin/ 目录,
|
|
3
|
+
// 按当前平台选择对应二进制执行。无需联网下载,不依赖 GitHub。
|
|
4
|
+
const { execFileSync } = require("child_process");
|
|
3
5
|
const path = require("path");
|
|
4
6
|
const fs = require("fs");
|
|
5
|
-
const https = require("https");
|
|
6
|
-
const http = require("http");
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const GITEE_REPO = "xing-wenkai/erp-cli";
|
|
8
|
+
// Node 平台/架构 → 二进制文件名片段
|
|
9
|
+
const OS_MAP = { win32: "windows", darwin: "darwin", linux: "linux" };
|
|
10
|
+
const ARCH_MAP = { x64: "amd64", arm64: "arm64" };
|
|
12
11
|
|
|
13
|
-
function
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (fs.existsSync(p)) return p;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
console.error(`Error: ${BINARY_NAME} binary not found.`);
|
|
28
|
-
console.error(`Please run: npm install`);
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// ── 静默自动更新 ──
|
|
33
|
-
|
|
34
|
-
function getPlatformInfo() {
|
|
35
|
-
const platform = process.platform;
|
|
36
|
-
const arch = process.arch;
|
|
37
|
-
|
|
38
|
-
const map = {
|
|
39
|
-
win32_x64: { target: "windows-amd64", ext: ".exe" },
|
|
40
|
-
win32_arm64: { target: "windows-arm64", ext: ".exe" },
|
|
41
|
-
darwin_x64: { target: "darwin-amd64", ext: "" },
|
|
42
|
-
darwin_arm64: { target: "darwin-arm64", ext: "" },
|
|
43
|
-
linux_x64: { target: "linux-amd64", ext: "" },
|
|
44
|
-
linux_arm64: { target: "linux-arm64", ext: "" },
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
return map[`${platform}_${arch}`] || null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function getInstalledVersion(binaryPath) {
|
|
51
|
-
try {
|
|
52
|
-
const output = execSync(`"${binaryPath}" --version`, {
|
|
53
|
-
encoding: "utf8",
|
|
54
|
-
timeout: 5000,
|
|
55
|
-
});
|
|
56
|
-
const match = output.match(/version\s+v?(\S+)/i);
|
|
57
|
-
return match ? match[1].replace(/^v/, "") : null;
|
|
58
|
-
} catch {
|
|
59
|
-
return null;
|
|
12
|
+
function getBinaryName() {
|
|
13
|
+
const os = OS_MAP[process.platform];
|
|
14
|
+
const arch = ARCH_MAP[process.arch];
|
|
15
|
+
if (!os || !arch) {
|
|
16
|
+
console.error(
|
|
17
|
+
`Error: unsupported platform ${process.platform}-${process.arch}.`
|
|
18
|
+
);
|
|
19
|
+
console.error(
|
|
20
|
+
`Supported: win32-x64, win32-arm64, darwin-x64, darwin-arm64, linux-x64, linux-arm64.`
|
|
21
|
+
);
|
|
22
|
+
process.exit(1);
|
|
60
23
|
}
|
|
24
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
25
|
+
return `erp-cli-${os}-${arch}${ext}`;
|
|
61
26
|
}
|
|
62
27
|
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
const client = url.startsWith("https") ? https : http;
|
|
66
|
-
client
|
|
67
|
-
.get(url, { timeout: 5000 }, (res) => {
|
|
68
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
69
|
-
fetchJSON(res.headers.location).then(resolve, reject);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (res.statusCode !== 200) {
|
|
73
|
-
reject(new Error(`HTTP ${res.statusCode}`));
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
let data = "";
|
|
77
|
-
res.on("data", (chunk) => (data += chunk));
|
|
78
|
-
res.on("end", () => {
|
|
79
|
-
try {
|
|
80
|
-
resolve(JSON.parse(data));
|
|
81
|
-
} catch (e) {
|
|
82
|
-
reject(e);
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
})
|
|
86
|
-
.on("error", reject)
|
|
87
|
-
.on("timeout", function () {
|
|
88
|
-
this.destroy();
|
|
89
|
-
reject(new Error("timeout"));
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function downloadFile(url, dest) {
|
|
95
|
-
return new Promise((resolve, reject) => {
|
|
96
|
-
const client = url.startsWith("https") ? https : http;
|
|
97
|
-
client
|
|
98
|
-
.get(url, (res) => {
|
|
99
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
100
|
-
downloadFile(res.headers.location, dest).then(resolve, reject);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
if (res.statusCode !== 200) {
|
|
104
|
-
reject(new Error(`HTTP ${res.statusCode}`));
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const stream = fs.createWriteStream(dest);
|
|
108
|
-
res.pipe(stream);
|
|
109
|
-
stream.on("finish", () => {
|
|
110
|
-
stream.close();
|
|
111
|
-
if (process.platform !== "win32") {
|
|
112
|
-
fs.chmodSync(dest, 0o755);
|
|
113
|
-
}
|
|
114
|
-
resolve();
|
|
115
|
-
});
|
|
116
|
-
stream.on("error", reject);
|
|
117
|
-
})
|
|
118
|
-
.on("error", reject);
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async function silentAutoUpdate() {
|
|
123
|
-
try {
|
|
124
|
-
const binary = getBinaryPath();
|
|
125
|
-
const installedVer = getInstalledVersion(binary);
|
|
126
|
-
|
|
127
|
-
// 如果已安装版本和 package.json 一致,跳过
|
|
128
|
-
if (installedVer === VERSION.replace(/^v/, "")) return;
|
|
129
|
-
|
|
130
|
-
// 已安装版本比 package.json 新?跳过
|
|
131
|
-
if (installedVer && installedVer > VERSION.replace(/^v/, "")) return;
|
|
28
|
+
function main() {
|
|
29
|
+
const binary = path.join(__dirname, "..", "bin", getBinaryName());
|
|
132
30
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
".last-update-check"
|
|
31
|
+
if (!fs.existsSync(binary)) {
|
|
32
|
+
console.error(`Error: binary not found at ${binary}.`);
|
|
33
|
+
console.error(
|
|
34
|
+
`Try reinstalling: npm install -g @windaka-erp/erp-cli`
|
|
138
35
|
);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const ONE_DAY = 24 * 60 * 60 * 1000;
|
|
142
|
-
if (Date.now() - lastCheck < ONE_DAY) return;
|
|
143
|
-
} catch {
|
|
144
|
-
// 文件不存在或读取失败,继续检查
|
|
145
|
-
}
|
|
146
|
-
if (installedVer && installedVer > VERSION.replace(/^v/, "")) return;
|
|
147
|
-
|
|
148
|
-
// 需要更新:查询 Release 获取最新版本号(GitHub 优先,Gitee 回退)
|
|
149
|
-
let release;
|
|
150
|
-
try {
|
|
151
|
-
release = await fetchJSON(
|
|
152
|
-
`https://api.github.com/repos/${GITHUB_REPO}/releases/latest`
|
|
153
|
-
);
|
|
154
|
-
} catch {
|
|
155
|
-
// GitHub 不通,回退 Gitee API
|
|
156
|
-
release = await fetchJSON(
|
|
157
|
-
`https://gitee.com/api/v5/repos/${GITEE_REPO}/releases/latest`
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const latestTag = release.tag_name; // e.g. "v0.3.0"
|
|
162
|
-
const latestVer = latestTag.replace(/^v/, "");
|
|
163
|
-
|
|
164
|
-
// 最新版和 package.json 版本不一致?等 npm update 后再说
|
|
165
|
-
if (latestVer !== VERSION.replace(/^v/, "")) return;
|
|
166
|
-
|
|
167
|
-
// 下载新版本
|
|
168
|
-
const info = getPlatformInfo();
|
|
169
|
-
if (!info) return;
|
|
170
|
-
|
|
171
|
-
const filename = `${BINARY_NAME}-${info.target}${info.ext}`;
|
|
172
|
-
const asset = release.assets.find((a) => a.name === filename);
|
|
173
|
-
if (!asset) return;
|
|
174
|
-
|
|
175
|
-
const binDir = path.join(__dirname, "..", "bin");
|
|
176
|
-
const binaryPath = path.join(binDir, `${BINARY_NAME}${info.ext}`);
|
|
177
|
-
|
|
178
|
-
// 下载到临时文件,完成后原子替换
|
|
179
|
-
const tmpPath = binaryPath + ".tmp";
|
|
180
|
-
await downloadFile(asset.browser_download_url, tmpPath);
|
|
181
|
-
|
|
182
|
-
// 替换旧二进制
|
|
183
|
-
fs.renameSync(tmpPath, binaryPath);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
184
38
|
|
|
185
|
-
|
|
39
|
+
// 非 Windows 确保可执行权限(npm 打包可能丢失)
|
|
40
|
+
if (process.platform !== "win32") {
|
|
186
41
|
try {
|
|
187
|
-
|
|
188
|
-
if (!fs.existsSync(markerDir)) fs.mkdirSync(markerDir, { recursive: true });
|
|
189
|
-
fs.writeFileSync(markerDir + "/.last-update-check", String(Date.now()));
|
|
42
|
+
fs.chmodSync(binary, 0o755);
|
|
190
43
|
} catch {
|
|
191
|
-
//
|
|
44
|
+
// 忽略权限错误
|
|
192
45
|
}
|
|
193
|
-
} catch {
|
|
194
|
-
// 静默失败,不影响正常使用
|
|
195
|
-
// 即使失败也写入检查时间,避免频繁重试
|
|
196
|
-
try {
|
|
197
|
-
const markerDir = path.join(require("os").homedir(), ".erp-cli");
|
|
198
|
-
if (!fs.existsSync(markerDir)) fs.mkdirSync(markerDir, { recursive: true });
|
|
199
|
-
fs.writeFileSync(markerDir + "/.last-update-check", String(Date.now()));
|
|
200
|
-
} catch {}
|
|
201
46
|
}
|
|
202
|
-
}
|
|
203
47
|
|
|
204
|
-
function main() {
|
|
205
|
-
// 后台静默更新,不阻塞主流程
|
|
206
|
-
silentAutoUpdate().catch(() => {});
|
|
207
|
-
|
|
208
|
-
const binary = getBinaryPath();
|
|
209
48
|
const args = process.argv.slice(2);
|
|
210
|
-
|
|
211
49
|
try {
|
|
212
|
-
execFileSync(binary, args, {
|
|
213
|
-
stdio: "inherit",
|
|
214
|
-
env: { ...process.env },
|
|
215
|
-
});
|
|
50
|
+
execFileSync(binary, args, { stdio: "inherit" });
|
|
216
51
|
} catch (err) {
|
|
217
52
|
process.exitCode = err.status || 1;
|
|
218
53
|
}
|
package/scripts/install.js
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
const { execSync } = require("child_process");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
|
|
5
|
-
// ── 配置 ──
|
|
6
|
-
const BINARY_NAME = "erp-cli";
|
|
7
|
-
const VERSION = require("../package.json").version;
|
|
8
|
-
const GITHUB_REPO = "windaka-erp/erp-cli";
|
|
9
|
-
const GITEE_REPO = "xing-wenkai/erp-cli";
|
|
10
|
-
|
|
11
|
-
// ── 平台映射 ──
|
|
12
|
-
function getPlatformInfo() {
|
|
13
|
-
const platform = process.platform;
|
|
14
|
-
const arch = process.arch;
|
|
15
|
-
|
|
16
|
-
const map = {
|
|
17
|
-
win32_x64: { target: "windows-amd64", ext: ".exe" },
|
|
18
|
-
win32_arm64: { target: "windows-arm64", ext: ".exe" },
|
|
19
|
-
darwin_x64: { target: "darwin-amd64", ext: "" },
|
|
20
|
-
darwin_arm64: { target: "darwin-arm64", ext: "" },
|
|
21
|
-
linux_x64: { target: "linux-amd64", ext: "" },
|
|
22
|
-
linux_arm64: { target: "linux-arm64", ext: "" },
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const key = `${platform}_${arch}`;
|
|
26
|
-
const info = map[key];
|
|
27
|
-
if (!info) {
|
|
28
|
-
console.error(`Unsupported platform: ${platform}-${arch}`);
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
return info;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// ── 下载地址(GitHub 优先,Gitee 回退)──
|
|
35
|
-
function getDownloadURLs(info) {
|
|
36
|
-
const filename = `${BINARY_NAME}-${info.target}${info.ext}`;
|
|
37
|
-
return [
|
|
38
|
-
`https://github.com/${GITHUB_REPO}/releases/download/v${VERSION}/${filename}`,
|
|
39
|
-
`https://gitee.com/${GITEE_REPO}/releases/download/v${VERSION}/${filename}`,
|
|
40
|
-
];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ── 获取已安装二进制的版本号 ──
|
|
44
|
-
function getInstalledVersion(binaryPath) {
|
|
45
|
-
try {
|
|
46
|
-
const output = execSync(`"${binaryPath}" --version`, {
|
|
47
|
-
encoding: "utf8",
|
|
48
|
-
timeout: 5000,
|
|
49
|
-
});
|
|
50
|
-
// 输出格式: "erp-cli version v0.2.0" 或 "erp-cli version 0.2.0"
|
|
51
|
-
const match = output.match(/version\s+v?(\S+)/i);
|
|
52
|
-
return match ? match[1] : null;
|
|
53
|
-
} catch {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// ── 比较版本号 ──
|
|
59
|
-
function isVersionOutdated(installed, expected) {
|
|
60
|
-
if (!installed) return true;
|
|
61
|
-
// 去掉 v 前缀统一比较
|
|
62
|
-
const a = installed.replace(/^v/, "");
|
|
63
|
-
const b = expected.replace(/^v/, "");
|
|
64
|
-
return a !== b;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// ── 下载二进制 ──
|
|
68
|
-
function downloadBinary(url, binaryPath) {
|
|
69
|
-
if (process.platform === "win32") {
|
|
70
|
-
execSync(
|
|
71
|
-
`powershell -Command "Invoke-WebRequest -Uri '${url}' -OutFile '${binaryPath}'"`,
|
|
72
|
-
{ stdio: "inherit" }
|
|
73
|
-
);
|
|
74
|
-
} else {
|
|
75
|
-
execSync(`curl -L -o "${binaryPath}" "${url}"`, {
|
|
76
|
-
stdio: "inherit",
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 设置可执行权限(非 Windows)
|
|
81
|
-
if (process.platform !== "win32") {
|
|
82
|
-
fs.chmodSync(binaryPath, 0o755);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// ── 主流程 ──
|
|
87
|
-
function install() {
|
|
88
|
-
const pkgDir = path.join(__dirname, "..");
|
|
89
|
-
const binDir = path.join(pkgDir, "bin");
|
|
90
|
-
if (!fs.existsSync(binDir)) {
|
|
91
|
-
fs.mkdirSync(binDir, { recursive: true });
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const info = getPlatformInfo();
|
|
95
|
-
const binaryPath = path.join(binDir, `${BINARY_NAME}${info.ext}`);
|
|
96
|
-
|
|
97
|
-
// 1. 检查 bin/ 目录是否已有对应平台的二进制
|
|
98
|
-
if (fs.existsSync(binaryPath)) {
|
|
99
|
-
const installedVer = getInstalledVersion(binaryPath);
|
|
100
|
-
if (!isVersionOutdated(installedVer, VERSION)) {
|
|
101
|
-
console.log(`Found bundled binary: ${binaryPath} (v${installedVer})`);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
// 版本不一致,需要更新
|
|
105
|
-
console.log(
|
|
106
|
-
`Binary version mismatch: installed v${installedVer}, expected v${VERSION}. Updating...`
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// 2. 检查项目根目录是否有本地编译产物(开发模式)
|
|
111
|
-
const localBinary = path.join(pkgDir, `${BINARY_NAME}${info.ext}`);
|
|
112
|
-
if (fs.existsSync(localBinary)) {
|
|
113
|
-
console.log(`Found local binary, copying...`);
|
|
114
|
-
fs.copyFileSync(localBinary, binaryPath);
|
|
115
|
-
console.log(`Installed from local build: ${binaryPath}`);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// 3. 从 GitHub Releases 下载,失败回退 Gitee
|
|
120
|
-
const urls = getDownloadURLs(info);
|
|
121
|
-
console.log(`Downloading ${BINARY_NAME} v${VERSION} for ${info.target}...`);
|
|
122
|
-
|
|
123
|
-
let downloaded = false;
|
|
124
|
-
for (const url of urls) {
|
|
125
|
-
console.log(` Trying: ${url}`);
|
|
126
|
-
try {
|
|
127
|
-
downloadBinary(url, binaryPath);
|
|
128
|
-
downloaded = true;
|
|
129
|
-
console.log(`Successfully installed ${BINARY_NAME} v${VERSION}`);
|
|
130
|
-
break;
|
|
131
|
-
} catch (err) {
|
|
132
|
-
console.warn(` Failed: ${err.message}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (!downloaded) {
|
|
137
|
-
console.error(`All download sources failed.`);
|
|
138
|
-
console.error(`You can build from source:`);
|
|
139
|
-
console.error(` git clone https://github.com/${GITHUB_REPO}.git`);
|
|
140
|
-
console.error(` cd erp-cli && go build -o ${BINARY_NAME} .`);
|
|
141
|
-
console.error(` Then copy the binary to: ${binaryPath}`);
|
|
142
|
-
process.exit(1);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
install();
|