@windaka-erp/erp-cli 0.2.0 → 0.3.0
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 +5 -2
- package/scripts/install.js +76 -30
- package/scripts/run.js +182 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windaka-erp/erp-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "ERP CLI - command-line tool for Windaka ERP system",
|
|
5
5
|
"bin": {
|
|
6
6
|
"erp-cli": "scripts/run.js"
|
|
@@ -36,5 +36,8 @@
|
|
|
36
36
|
"cpu": [
|
|
37
37
|
"x64",
|
|
38
38
|
"arm64"
|
|
39
|
-
]
|
|
39
|
+
],
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@windaka-erp/erp-cli": "^0.2.0"
|
|
42
|
+
}
|
|
40
43
|
}
|
package/scripts/install.js
CHANGED
|
@@ -6,6 +6,7 @@ const fs = require("fs");
|
|
|
6
6
|
const BINARY_NAME = "erp-cli";
|
|
7
7
|
const VERSION = require("../package.json").version;
|
|
8
8
|
const GITHUB_REPO = "windaka-erp/erp-cli";
|
|
9
|
+
const GITEE_REPO = "xing-wenkai/erp-cli";
|
|
9
10
|
|
|
10
11
|
// ── 平台映射 ──
|
|
11
12
|
function getPlatformInfo() {
|
|
@@ -30,10 +31,56 @@ function getPlatformInfo() {
|
|
|
30
31
|
return info;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
// ──
|
|
34
|
-
function
|
|
34
|
+
// ── 下载地址(GitHub 优先,Gitee 回退)──
|
|
35
|
+
function getDownloadURLs(info) {
|
|
35
36
|
const filename = `${BINARY_NAME}-${info.target}${info.ext}`;
|
|
36
|
-
return
|
|
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
|
+
}
|
|
37
84
|
}
|
|
38
85
|
|
|
39
86
|
// ── 主流程 ──
|
|
@@ -47,10 +94,17 @@ function install() {
|
|
|
47
94
|
const info = getPlatformInfo();
|
|
48
95
|
const binaryPath = path.join(binDir, `${BINARY_NAME}${info.ext}`);
|
|
49
96
|
|
|
50
|
-
// 1. 检查 bin/
|
|
97
|
+
// 1. 检查 bin/ 目录是否已有对应平台的二进制
|
|
51
98
|
if (fs.existsSync(binaryPath)) {
|
|
52
|
-
|
|
53
|
-
|
|
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
|
+
);
|
|
54
108
|
}
|
|
55
109
|
|
|
56
110
|
// 2. 检查项目根目录是否有本地编译产物(开发模式)
|
|
@@ -62,33 +116,25 @@ function install() {
|
|
|
62
116
|
return;
|
|
63
117
|
}
|
|
64
118
|
|
|
65
|
-
// 3. 从 GitHub Releases
|
|
66
|
-
const
|
|
119
|
+
// 3. 从 GitHub Releases 下载,失败回退 Gitee
|
|
120
|
+
const urls = getDownloadURLs(info);
|
|
67
121
|
console.log(`Downloading ${BINARY_NAME} v${VERSION} for ${info.target}...`);
|
|
68
|
-
console.log(` URL: ${url}`);
|
|
69
122
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
stdio: "inherit",
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// 设置可执行权限(非 Windows)
|
|
85
|
-
if (process.platform !== "win32") {
|
|
86
|
-
fs.chmodSync(binaryPath, 0o755);
|
|
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}`);
|
|
87
133
|
}
|
|
134
|
+
}
|
|
88
135
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
console.error(`Download failed: ${err.message}`);
|
|
136
|
+
if (!downloaded) {
|
|
137
|
+
console.error(`All download sources failed.`);
|
|
92
138
|
console.error(`You can build from source:`);
|
|
93
139
|
console.error(` git clone https://github.com/${GITHUB_REPO}.git`);
|
|
94
140
|
console.error(` cd erp-cli && go build -o ${BINARY_NAME} .`);
|
|
@@ -97,4 +143,4 @@ function install() {
|
|
|
97
143
|
}
|
|
98
144
|
}
|
|
99
145
|
|
|
100
|
-
install();
|
|
146
|
+
install();
|
package/scripts/run.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const { execFileSync } = require("child_process");
|
|
2
|
+
const { execFileSync, execSync } = require("child_process");
|
|
3
3
|
const path = require("path");
|
|
4
4
|
const fs = require("fs");
|
|
5
|
+
const https = require("https");
|
|
6
|
+
const http = require("http");
|
|
5
7
|
|
|
6
8
|
const BINARY_NAME = "erp-cli";
|
|
9
|
+
const VERSION = require("../package.json").version;
|
|
10
|
+
const GITHUB_REPO = "windaka-erp/erp-cli";
|
|
11
|
+
const GITEE_REPO = "xing-wenkai/erp-cli";
|
|
7
12
|
|
|
8
13
|
function getBinaryPath() {
|
|
9
14
|
const binDir = path.join(__dirname, "..", "bin");
|
|
10
15
|
|
|
11
|
-
// 按优先级查找二进制
|
|
12
16
|
const candidates = [
|
|
13
17
|
path.join(binDir, `${BINARY_NAME}.exe`), // bin/erp-cli.exe (Windows)
|
|
14
18
|
path.join(binDir, BINARY_NAME), // bin/erp-cli (macOS/Linux)
|
|
@@ -25,7 +29,182 @@ function getBinaryPath() {
|
|
|
25
29
|
process.exit(1);
|
|
26
30
|
}
|
|
27
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;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function fetchJSON(url) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
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;
|
|
132
|
+
|
|
133
|
+
// 检查上次更新时间,每天最多检查一次
|
|
134
|
+
const updateMarker = path.join(
|
|
135
|
+
require("os").homedir(),
|
|
136
|
+
".erp-cli",
|
|
137
|
+
".last-update-check"
|
|
138
|
+
);
|
|
139
|
+
try {
|
|
140
|
+
const lastCheck = parseInt(fs.readFileSync(updateMarker, "utf8"), 10);
|
|
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);
|
|
184
|
+
|
|
185
|
+
// 写入本次检查时间
|
|
186
|
+
try {
|
|
187
|
+
const markerDir = path.join(require("os").homedir(), ".erp-cli");
|
|
188
|
+
if (!fs.existsSync(markerDir)) fs.mkdirSync(markerDir, { recursive: true });
|
|
189
|
+
fs.writeFileSync(markerDir + "/.last-update-check", String(Date.now()));
|
|
190
|
+
} catch {
|
|
191
|
+
// 写入失败不影响
|
|
192
|
+
}
|
|
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
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
28
204
|
function main() {
|
|
205
|
+
// 后台静默更新,不阻塞主流程
|
|
206
|
+
silentAutoUpdate().catch(() => {});
|
|
207
|
+
|
|
29
208
|
const binary = getBinaryPath();
|
|
30
209
|
const args = process.argv.slice(2);
|
|
31
210
|
|
|
@@ -35,10 +214,8 @@ function main() {
|
|
|
35
214
|
env: { ...process.env },
|
|
36
215
|
});
|
|
37
216
|
} catch (err) {
|
|
38
|
-
// execFileSync throws on non-zero exit codes; the binary already
|
|
39
|
-
// wrote its output to stdout/stderr, so just exit with same code.
|
|
40
217
|
process.exitCode = err.status || 1;
|
|
41
218
|
}
|
|
42
219
|
}
|
|
43
220
|
|
|
44
|
-
main();
|
|
221
|
+
main();
|