uniner-cli 1.0.3 → 1.0.6
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/LICENSE +4 -4
- package/README.md +181 -171
- package/bin/uniner-cli-uninstall.js +32 -129
- package/bin/uniner-cli.js +42 -42
- package/lib/SKILL.md.template +373 -371
- package/lib/paths.js +78 -78
- package/package.json +50 -47
- package/scripts/cleanup.js +123 -0
- package/scripts/postinstall-helpers.js +140 -0
- package/scripts/postinstall.js +385 -428
- package/scripts/preuninstall.js +25 -0
package/lib/paths.js
CHANGED
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
// Path utilities shared by bin/, scripts/postinstall.js, scripts/preuninstall.js.
|
|
2
|
-
// Single source of truth so we do not drift between launcher and installer.
|
|
3
|
-
|
|
4
|
-
const os = require('os');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
|
|
8
|
-
const pkg = require('../package.json');
|
|
9
|
-
|
|
10
|
-
// ----------------------------------------------------------------------
|
|
11
|
-
// Layout we install into:
|
|
12
|
-
//
|
|
13
|
-
// Windows: %USERPROFILE%\.uniner-cli\<version>\
|
|
14
|
-
// bin\uniner-cli\uniner-cli.exe ← real PyInstaller exe
|
|
15
|
-
// skills\
|
|
16
|
-
// docs\
|
|
17
|
-
// workdir\
|
|
18
|
-
// Linux: ~/.uniner-cli/<version>/ (not yet shipped)
|
|
19
|
-
//
|
|
20
|
-
// We keep <version> in the path so two npm-installed versions can coexist
|
|
21
|
-
// without colliding (`npm install uniner-cli@1.0.0` then `npm install uniner-cli@1.0.1`
|
|
22
|
-
// won't clobber each other), and so postinstall is idempotent: if the
|
|
23
|
-
// version-specific dir already has a working binary, we skip the download.
|
|
24
|
-
// ----------------------------------------------------------------------
|
|
25
|
-
|
|
26
|
-
function homeDir() {
|
|
27
|
-
return os.homedir();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function rootDir() {
|
|
31
|
-
return path.join(homeDir(), '.uniner-cli');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function versionDir(version) {
|
|
35
|
-
return path.join(rootDir(), version || pkg.version);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function realBinaryPath(version) {
|
|
39
|
-
const ver = version || pkg.version;
|
|
40
|
-
if (process.platform === 'win32') {
|
|
41
|
-
return path.join(versionDir(ver), 'bin', 'uniner-cli', 'uniner-cli.exe');
|
|
42
|
-
}
|
|
43
|
-
// Linux/Mac path will be filled in later when those builds exist.
|
|
44
|
-
return path.join(versionDir(ver), 'bin', 'uniner-cli', 'uniner-cli');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function skillDir() {
|
|
48
|
-
// Same place Claude Code looks for user-global skills.
|
|
49
|
-
return path.join(homeDir(), '.claude', 'skills', 'uniner-cli');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function skillFile() {
|
|
53
|
-
return path.join(skillDir(), 'SKILL.md');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function templatePath() {
|
|
57
|
-
return path.join(__dirname, 'SKILL.md.template');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function isInstalled(version) {
|
|
61
|
-
try {
|
|
62
|
-
return fs.statSync(realBinaryPath(version)).isFile();
|
|
63
|
-
} catch (_) {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
module.exports = {
|
|
69
|
-
pkg,
|
|
70
|
-
homeDir,
|
|
71
|
-
rootDir,
|
|
72
|
-
versionDir,
|
|
73
|
-
realBinaryPath,
|
|
74
|
-
skillDir,
|
|
75
|
-
skillFile,
|
|
76
|
-
templatePath,
|
|
77
|
-
isInstalled,
|
|
78
|
-
};
|
|
1
|
+
// Path utilities shared by bin/, scripts/postinstall.js, scripts/preuninstall.js.
|
|
2
|
+
// Single source of truth so we do not drift between launcher and installer.
|
|
3
|
+
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
|
|
8
|
+
const pkg = require('../package.json');
|
|
9
|
+
|
|
10
|
+
// ----------------------------------------------------------------------
|
|
11
|
+
// Layout we install into:
|
|
12
|
+
//
|
|
13
|
+
// Windows: %USERPROFILE%\.uniner-cli\<version>\
|
|
14
|
+
// bin\uniner-cli\uniner-cli.exe ← real PyInstaller exe
|
|
15
|
+
// skills\
|
|
16
|
+
// docs\
|
|
17
|
+
// workdir\
|
|
18
|
+
// Linux: ~/.uniner-cli/<version>/ (not yet shipped)
|
|
19
|
+
//
|
|
20
|
+
// We keep <version> in the path so two npm-installed versions can coexist
|
|
21
|
+
// without colliding (`npm install uniner-cli@1.0.0` then `npm install uniner-cli@1.0.1`
|
|
22
|
+
// won't clobber each other), and so postinstall is idempotent: if the
|
|
23
|
+
// version-specific dir already has a working binary, we skip the download.
|
|
24
|
+
// ----------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
function homeDir() {
|
|
27
|
+
return os.homedir();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function rootDir() {
|
|
31
|
+
return path.join(homeDir(), '.uniner-cli');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function versionDir(version) {
|
|
35
|
+
return path.join(rootDir(), version || pkg.version);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function realBinaryPath(version) {
|
|
39
|
+
const ver = version || pkg.version;
|
|
40
|
+
if (process.platform === 'win32') {
|
|
41
|
+
return path.join(versionDir(ver), 'bin', 'uniner-cli', 'uniner-cli.exe');
|
|
42
|
+
}
|
|
43
|
+
// Linux/Mac path will be filled in later when those builds exist.
|
|
44
|
+
return path.join(versionDir(ver), 'bin', 'uniner-cli', 'uniner-cli');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function skillDir() {
|
|
48
|
+
// Same place Claude Code looks for user-global skills.
|
|
49
|
+
return path.join(homeDir(), '.claude', 'skills', 'uniner-cli');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function skillFile() {
|
|
53
|
+
return path.join(skillDir(), 'SKILL.md');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function templatePath() {
|
|
57
|
+
return path.join(__dirname, 'SKILL.md.template');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isInstalled(version) {
|
|
61
|
+
try {
|
|
62
|
+
return fs.statSync(realBinaryPath(version)).isFile();
|
|
63
|
+
} catch (_) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
pkg,
|
|
70
|
+
homeDir,
|
|
71
|
+
rootDir,
|
|
72
|
+
versionDir,
|
|
73
|
+
realBinaryPath,
|
|
74
|
+
skillDir,
|
|
75
|
+
skillFile,
|
|
76
|
+
templatePath,
|
|
77
|
+
isInstalled,
|
|
78
|
+
};
|
package/package.json
CHANGED
|
@@ -1,47 +1,50 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "uniner-cli",
|
|
3
|
+
"version": "1.0.6",
|
|
4
|
+
"description": "uniner-cli — RPA automation CLI: browser automation (Playwright/CDP), Excel, OCR, desktop/SAP, crypto/HTTP/email tools. Auto-installs a Claude Code skill on install.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"rpa",
|
|
7
|
+
"automation",
|
|
8
|
+
"uniner",
|
|
9
|
+
"playwright",
|
|
10
|
+
"excel",
|
|
11
|
+
"ocr",
|
|
12
|
+
"claude-code",
|
|
13
|
+
"ai-agent"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://www.uniner.com/",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://www.uniner.com/",
|
|
18
|
+
"email": "support@uniner.com"
|
|
19
|
+
},
|
|
20
|
+
"license": "Proprietary",
|
|
21
|
+
"author": "Uniner Tech",
|
|
22
|
+
"main": "lib/paths.js",
|
|
23
|
+
"bin": {
|
|
24
|
+
"uniner-cli": "bin/uniner-cli.js",
|
|
25
|
+
"uniner-cli-uninstall": "bin/uniner-cli-uninstall.js"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"postinstall": "node scripts/postinstall.js",
|
|
29
|
+
"preuninstall": "node scripts/preuninstall.js"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": "\u003e=14"
|
|
33
|
+
},
|
|
34
|
+
"os": [
|
|
35
|
+
"win32",
|
|
36
|
+
"darwin",
|
|
37
|
+
"linux"
|
|
38
|
+
],
|
|
39
|
+
"cpu": [
|
|
40
|
+
"x64",
|
|
41
|
+
"arm64"
|
|
42
|
+
],
|
|
43
|
+
"files": [
|
|
44
|
+
"bin/",
|
|
45
|
+
"lib/",
|
|
46
|
+
"scripts/",
|
|
47
|
+
"README.md",
|
|
48
|
+
"LICENSE"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Shared cleanup logic for uninstall.
|
|
2
|
+
// Extracted so both the npm lifecycle hook (preuninstall.js) and the
|
|
3
|
+
// manual command (bin/uniner-cli-uninstall.js) can share it.
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
rootDir,
|
|
12
|
+
skillDir,
|
|
13
|
+
skillFile,
|
|
14
|
+
} = require('../lib/paths');
|
|
15
|
+
|
|
16
|
+
function rmRf(p) {
|
|
17
|
+
try {
|
|
18
|
+
if (fs.existsSync(p)) {
|
|
19
|
+
fs.rmSync(p, { recursive: true, force: true });
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
} catch (_) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function rmFile(p) {
|
|
29
|
+
try {
|
|
30
|
+
if (fs.existsSync(p)) {
|
|
31
|
+
fs.unlinkSync(p);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
} catch (_) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Run the full uninstall cleanup.
|
|
42
|
+
*
|
|
43
|
+
* @param {object} [opts]
|
|
44
|
+
* @param {boolean} [opts.verbose=false] Print progress lines to stdout.
|
|
45
|
+
* The preuninstall hook should pass false
|
|
46
|
+
* because npm swallows lifecycle output
|
|
47
|
+
* unless --verbose is used; the manual
|
|
48
|
+
* command passes true so the user sees
|
|
49
|
+
* what happened.
|
|
50
|
+
* @returns {{ didSomething: boolean, scenariosKept: number, scenariosDir: string|null }}
|
|
51
|
+
*/
|
|
52
|
+
function cleanup(opts) {
|
|
53
|
+
const verbose = (opts && opts.verbose) || false;
|
|
54
|
+
|
|
55
|
+
function log(msg) {
|
|
56
|
+
if (verbose) {
|
|
57
|
+
console.log(`[uniner-cli] ${msg}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const root = rootDir();
|
|
62
|
+
let didSomething = false;
|
|
63
|
+
|
|
64
|
+
// 1. Remove the entire ~/.uniner-cli/ tree (all versions).
|
|
65
|
+
// The version-specific layout exists so the installer can support
|
|
66
|
+
// multi-version coexistence, but on uninstall the user wants a full
|
|
67
|
+
// removal — orphaned old version dirs would just waste disk space.
|
|
68
|
+
if (rmRf(root)) {
|
|
69
|
+
log(`Removed ${root}`);
|
|
70
|
+
didSomething = true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 2. Remove ONLY the SKILL.md file. NEVER rm -rf the whole skill dir,
|
|
74
|
+
// because the user's accumulated scenarios/<name>/ subdirectories
|
|
75
|
+
// live there and represent real work they've sedimented over time.
|
|
76
|
+
const dir = skillDir();
|
|
77
|
+
if (rmFile(skillFile())) {
|
|
78
|
+
log(`Removed ${skillFile()}`);
|
|
79
|
+
didSomething = true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 3. Count scenarios/ subdirs that contain a meta.json.
|
|
83
|
+
let scenariosKept = 0;
|
|
84
|
+
const scenariosPath = path.join(dir, 'scenarios');
|
|
85
|
+
if (fs.existsSync(scenariosPath)) {
|
|
86
|
+
try {
|
|
87
|
+
scenariosKept = fs.readdirSync(scenariosPath).filter(n => {
|
|
88
|
+
const meta = path.join(scenariosPath, n, 'meta.json');
|
|
89
|
+
return fs.existsSync(meta);
|
|
90
|
+
}).length;
|
|
91
|
+
} catch (_) {
|
|
92
|
+
// best-effort
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 4. If skill dir is now genuinely empty, remove it. Otherwise keep it.
|
|
97
|
+
try {
|
|
98
|
+
if (fs.existsSync(dir)) {
|
|
99
|
+
const remaining = fs.readdirSync(dir);
|
|
100
|
+
if (remaining.length === 0) {
|
|
101
|
+
fs.rmdirSync(dir);
|
|
102
|
+
log(`Removed empty ${dir}`);
|
|
103
|
+
didSomething = true;
|
|
104
|
+
} else if (verbose) {
|
|
105
|
+
log(`Keeping ${dir} (${scenariosKept} scenario(s) preserved)`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch (_) {
|
|
109
|
+
// best-effort
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!didSomething) {
|
|
113
|
+
log('Nothing to clean — already removed.');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
didSomething,
|
|
118
|
+
scenariosKept,
|
|
119
|
+
scenariosDir: scenariosKept > 0 ? scenariosPath : null,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = { cleanup };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* postinstall-helpers.js — 平台检测和分发逻辑
|
|
3
|
+
*
|
|
4
|
+
* 从 postinstall.js 中提取的可测试辅助函数。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { spawnSync } = require('child_process');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 获取当前平台信息
|
|
13
|
+
* @returns {{ platform: string, arch: string } | null} 平台信息,不支持的平台返回 null
|
|
14
|
+
*/
|
|
15
|
+
function getPlatformInfo() {
|
|
16
|
+
const platformMap = {
|
|
17
|
+
'win32': 'windows',
|
|
18
|
+
'darwin': 'darwin',
|
|
19
|
+
'linux': 'linux'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const platform = platformMap[process.platform];
|
|
23
|
+
if (!platform) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
platform,
|
|
29
|
+
arch: process.arch
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 根据当前平台从元数据中选择对应的下载信息
|
|
35
|
+
* @param {object} meta - latest.json 的完整内容
|
|
36
|
+
* @returns {object} 包含 portable_url, portable_sha256, portable_size 的对象
|
|
37
|
+
* @throws {Error} 如果平台不受支持或元数据缺失
|
|
38
|
+
*/
|
|
39
|
+
function selectMetadata(meta) {
|
|
40
|
+
const info = getPlatformInfo();
|
|
41
|
+
if (!info) {
|
|
42
|
+
throw new Error(`不支持的平台: ${process.platform} ${process.arch}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { platform, arch } = info;
|
|
46
|
+
const platformMeta = meta[platform];
|
|
47
|
+
|
|
48
|
+
if (!platformMeta) {
|
|
49
|
+
throw new Error(`latest.json 缺少 ${platform} 平台的元数据`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const archMeta = platformMeta[arch];
|
|
53
|
+
if (!archMeta || !archMeta.portable_url || !archMeta.portable_sha256) {
|
|
54
|
+
throw new Error(`latest.json 缺少 ${platform}.${arch} 的 portable_url / portable_sha256`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return archMeta;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 获取当前平台的解压命令
|
|
62
|
+
* @param {string} archivePath - 压缩文件路径
|
|
63
|
+
* @param {string} destDir - 解压目标目录
|
|
64
|
+
* @returns {{ command: string, args: string[] }} 解压命令和参数
|
|
65
|
+
*/
|
|
66
|
+
function getExtractCommand(archivePath, destDir) {
|
|
67
|
+
const info = getPlatformInfo();
|
|
68
|
+
if (!info) {
|
|
69
|
+
throw new Error(`不支持的平台: ${process.platform}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (info.platform === 'windows') {
|
|
73
|
+
// Windows: 使用 PowerShell Expand-Archive
|
|
74
|
+
return {
|
|
75
|
+
command: 'powershell.exe',
|
|
76
|
+
args: [
|
|
77
|
+
'-NoProfile',
|
|
78
|
+
'-NonInteractive',
|
|
79
|
+
'-ExecutionPolicy', 'Bypass',
|
|
80
|
+
'-Command',
|
|
81
|
+
`Expand-Archive -LiteralPath '${archivePath}' -DestinationPath '${destDir}' -Force`
|
|
82
|
+
]
|
|
83
|
+
};
|
|
84
|
+
} else if (info.platform === 'darwin' || info.platform === 'linux') {
|
|
85
|
+
// macOS / Linux: 使用系统 tar
|
|
86
|
+
return {
|
|
87
|
+
command: 'tar',
|
|
88
|
+
args: ['-xzf', archivePath, '-C', destDir]
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
throw new Error(`未实现的平台解压逻辑: ${info.platform}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 执行解压
|
|
97
|
+
* @param {string} archivePath - 压缩文件路径
|
|
98
|
+
* @param {string} destDir - 解压目标目录
|
|
99
|
+
*/
|
|
100
|
+
function extractArchive(archivePath, destDir) {
|
|
101
|
+
const fs = require('fs');
|
|
102
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
103
|
+
|
|
104
|
+
const { command, args } = getExtractCommand(archivePath, destDir);
|
|
105
|
+
const result = spawnSync(command, args, { stdio: 'inherit' });
|
|
106
|
+
|
|
107
|
+
if (result.error) {
|
|
108
|
+
throw new Error(`无法执行解压命令: ${result.error.message}`);
|
|
109
|
+
}
|
|
110
|
+
if (result.status !== 0) {
|
|
111
|
+
throw new Error(`解压命令退出码: ${result.status}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 获取当前平台的二进制文件名
|
|
117
|
+
* @returns {string} 二进制文件名(带或不带扩展名)
|
|
118
|
+
*/
|
|
119
|
+
function getBinaryName() {
|
|
120
|
+
const info = getPlatformInfo();
|
|
121
|
+
if (!info) {
|
|
122
|
+
throw new Error(`不支持的平台: ${process.platform}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (info.platform === 'windows') {
|
|
126
|
+
return 'uniner-cli.exe';
|
|
127
|
+
} else if (info.platform === 'darwin' || info.platform === 'linux') {
|
|
128
|
+
return 'uniner-cli';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
throw new Error(`未实现的平台二进制名: ${info.platform}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = {
|
|
135
|
+
getPlatformInfo,
|
|
136
|
+
selectMetadata,
|
|
137
|
+
getExtractCommand,
|
|
138
|
+
extractArchive,
|
|
139
|
+
getBinaryName
|
|
140
|
+
};
|