rainbo-cli 0.1.5 → 0.1.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/README.md +13 -5
- package/checksums.txt +6 -6
- package/package.json +8 -7
- package/packaging/npm/install.js +167 -0
- package/{scripts → packaging/npm}/run.js +21 -15
- package/scripts/install.js +0 -143
package/README.md
CHANGED
|
@@ -17,9 +17,9 @@ rainbo-cli --version
|
|
|
17
17
|
## Development
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
Rscript
|
|
20
|
+
Rscript bin/rainbo-cli.R --version
|
|
21
21
|
npm run build:release
|
|
22
|
-
node
|
|
22
|
+
node packaging/npm/run.js help
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Release
|
|
@@ -29,13 +29,21 @@ node scripts/run.js help
|
|
|
29
29
|
3. Run:
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
|
|
32
|
+
npm run release:tag
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
Pushing the `vX.Y.Z` tag triggers GitHub Actions to create GitHub Release
|
|
36
36
|
archives and publish the npm wrapper package.
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
Publishing uses npm Trusted Publishing (OIDC) from GitHub Actions. No long-lived
|
|
39
|
+
`NPM_TOKEN` secret is required.
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
## Project layout
|
|
41
42
|
|
|
43
|
+
```text
|
|
44
|
+
bin/ Rscript entrypoint used by source and release archives
|
|
45
|
+
R/rainbo_cli/ CLI command, version, output, and update logic
|
|
46
|
+
packaging/npm/ npm postinstall downloader and bin launcher
|
|
47
|
+
tools/release/ release archive builder and tag helper
|
|
48
|
+
.github/workflows/ GitHub Release + npm Trusted Publishing workflow
|
|
49
|
+
```
|
package/checksums.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
1eefc9876d83642be4a2326447fc1cd1cdc2147b5c968efe3a7d954b5c990346 rainbo-cli-0.1.6-darwin-amd64.tar.gz
|
|
2
|
+
50d9bdceae09a3b91d5faac0c3766e07f60b66cf9d25aae31c21ef9dfb8674db rainbo-cli-0.1.6-darwin-arm64.tar.gz
|
|
3
|
+
2bd8eaaa8db300fe7366a84b551e68a5bb52567b34eb33831d237d0548e866b5 rainbo-cli-0.1.6-linux-amd64.tar.gz
|
|
4
|
+
fd6aa48c8af7bef44e1cde5118921f495666bd54c37e2f70de61c71e2cc42634 rainbo-cli-0.1.6-linux-arm64.tar.gz
|
|
5
|
+
4141becb64dfbc4229d6969a08c2fb83566af8eb96c012717156cbf8c2f6d8e3 rainbo-cli-0.1.6-windows-amd64.zip
|
|
6
|
+
4141becb64dfbc4229d6969a08c2fb83566af8eb96c012717156cbf8c2f6d8e3 rainbo-cli-0.1.6-windows-arm64.zip
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rainbo-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Rainbo command line tools for data migration workflows",
|
|
5
5
|
"bin": {
|
|
6
|
-
"rainbo-cli": "
|
|
6
|
+
"rainbo-cli": "packaging/npm/run.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build:release": "bash
|
|
10
|
-
"postinstall": "node
|
|
11
|
-
"
|
|
9
|
+
"build:release": "bash tools/release/build-release.sh",
|
|
10
|
+
"postinstall": "node packaging/npm/install.js",
|
|
11
|
+
"release:tag": "bash tools/release/tag-release.sh",
|
|
12
|
+
"test": "node packaging/npm/run.js --version && node packaging/npm/run.js help && node packaging/npm/run.js update --check"
|
|
12
13
|
},
|
|
13
14
|
"os": [
|
|
14
15
|
"darwin",
|
|
@@ -28,8 +29,8 @@
|
|
|
28
29
|
},
|
|
29
30
|
"license": "MIT",
|
|
30
31
|
"files": [
|
|
31
|
-
"
|
|
32
|
-
"
|
|
32
|
+
"packaging/npm/install.js",
|
|
33
|
+
"packaging/npm/run.js",
|
|
33
34
|
"checksums.txt",
|
|
34
35
|
"README.md",
|
|
35
36
|
"LICENSE"
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const crypto = require("crypto");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const { execFileSync } = require("child_process");
|
|
8
|
+
|
|
9
|
+
const repoRoot = path.join(__dirname, "..", "..");
|
|
10
|
+
const VERSION = require(path.join(repoRoot, "package.json")).version.replace(/-.*$/, "");
|
|
11
|
+
const REPO = "work2a/rainbo-cli";
|
|
12
|
+
const NAME = "rainbo-cli";
|
|
13
|
+
const DEFAULT_MIRROR_HOST = "https://registry.npmmirror.com";
|
|
14
|
+
const ALLOWED_HOSTS = new Set(["github.com", "objects.githubusercontent.com", "registry.npmmirror.com"]);
|
|
15
|
+
|
|
16
|
+
const PLATFORM_MAP = { darwin: "darwin", linux: "linux", win32: "windows" };
|
|
17
|
+
const ARCH_MAP = { x64: "amd64", arm64: "arm64" };
|
|
18
|
+
|
|
19
|
+
const platform = PLATFORM_MAP[process.platform];
|
|
20
|
+
const arch = ARCH_MAP[process.arch];
|
|
21
|
+
const isWindows = process.platform === "win32";
|
|
22
|
+
const ext = isWindows ? ".zip" : ".tar.gz";
|
|
23
|
+
const archiveName = `${NAME}-${VERSION}-${platform}-${arch}${ext}`;
|
|
24
|
+
const githubUrl = `https://github.com/${REPO}/releases/download/v${VERSION}/${archiveName}`;
|
|
25
|
+
const installDir = path.join(repoRoot, "vendor", NAME);
|
|
26
|
+
|
|
27
|
+
function joinUrl(base, suffix) {
|
|
28
|
+
return base.replace(/\/+$/, "") + suffix;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isDefaultNpmjsRegistry(url) {
|
|
32
|
+
try {
|
|
33
|
+
return new URL(url).hostname === "registry.npmjs.org";
|
|
34
|
+
} catch (_) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isValidDownloadBase(raw) {
|
|
40
|
+
try {
|
|
41
|
+
const parsed = new URL(raw);
|
|
42
|
+
return parsed.protocol === "https:" && !!parsed.hostname;
|
|
43
|
+
} catch (_) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function resolveMirrorUrls(env, archive, version) {
|
|
49
|
+
const binaryPath = `/-/binary/${NAME}/v${version}/${archive}`;
|
|
50
|
+
const urls = [];
|
|
51
|
+
const registry = (env.npm_config_registry || "").trim();
|
|
52
|
+
if (registry && !isDefaultNpmjsRegistry(registry) && isValidDownloadBase(registry)) {
|
|
53
|
+
const base = new URL(registry);
|
|
54
|
+
urls.push(joinUrl(base.origin + base.pathname, binaryPath));
|
|
55
|
+
}
|
|
56
|
+
const defaultUrl = joinUrl(DEFAULT_MIRROR_HOST, binaryPath);
|
|
57
|
+
if (!urls.includes(defaultUrl)) urls.push(defaultUrl);
|
|
58
|
+
return urls;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function assertAllowedHost(url) {
|
|
62
|
+
const host = new URL(url).hostname;
|
|
63
|
+
if (!ALLOWED_HOSTS.has(host)) throw new Error(`Download host not allowed: ${host}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function download(url, destPath) {
|
|
67
|
+
assertAllowedHost(url);
|
|
68
|
+
execFileSync("curl", [
|
|
69
|
+
"--fail", "--location", "--silent", "--show-error",
|
|
70
|
+
"--connect-timeout", "10", "--max-time", "120", "--max-redirs", "3",
|
|
71
|
+
"--output", destPath, url,
|
|
72
|
+
], { stdio: ["ignore", "ignore", "pipe"] });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function getExpectedChecksum(archive, checksumsDir) {
|
|
76
|
+
const checksumsPath = path.join(checksumsDir || repoRoot, "checksums.txt");
|
|
77
|
+
if (!fs.existsSync(checksumsPath)) {
|
|
78
|
+
console.error("[WARN] checksums.txt not found, skipping checksum verification");
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const content = fs.readFileSync(checksumsPath, "utf8");
|
|
82
|
+
for (const line of content.split("\n")) {
|
|
83
|
+
const trimmed = line.trim();
|
|
84
|
+
if (!trimmed) continue;
|
|
85
|
+
const idx = trimmed.indexOf(" ");
|
|
86
|
+
if (idx === -1) continue;
|
|
87
|
+
if (trimmed.slice(idx + 2) === archive) return trimmed.slice(0, idx);
|
|
88
|
+
}
|
|
89
|
+
throw new Error(`Checksum entry not found for ${archive}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function verifyChecksum(archivePath, expectedHash) {
|
|
93
|
+
if (expectedHash === null) return;
|
|
94
|
+
const hash = crypto.createHash("sha256");
|
|
95
|
+
const fd = fs.openSync(archivePath, "r");
|
|
96
|
+
try {
|
|
97
|
+
const buf = Buffer.alloc(64 * 1024);
|
|
98
|
+
let bytesRead;
|
|
99
|
+
while ((bytesRead = fs.readSync(fd, buf, 0, buf.length, null)) > 0) {
|
|
100
|
+
hash.update(buf.subarray(0, bytesRead));
|
|
101
|
+
}
|
|
102
|
+
} finally {
|
|
103
|
+
fs.closeSync(fd);
|
|
104
|
+
}
|
|
105
|
+
const actual = hash.digest("hex");
|
|
106
|
+
if (actual.toLowerCase() !== expectedHash.toLowerCase()) {
|
|
107
|
+
throw new Error(`Checksum mismatch for ${path.basename(archivePath)}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function extractArchive(archivePath, destDir) {
|
|
112
|
+
fs.rmSync(destDir, { recursive: true, force: true });
|
|
113
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
114
|
+
if (isWindows) {
|
|
115
|
+
const psCommand = "$ErrorActionPreference='Stop';" +
|
|
116
|
+
"Expand-Archive -LiteralPath $env:RAINBO_CLI_ARCHIVE -DestinationPath $env:RAINBO_CLI_DEST -Force";
|
|
117
|
+
execFileSync("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCommand], {
|
|
118
|
+
stdio: "inherit",
|
|
119
|
+
env: { ...process.env, RAINBO_CLI_ARCHIVE: archivePath, RAINBO_CLI_DEST: destDir },
|
|
120
|
+
});
|
|
121
|
+
} else {
|
|
122
|
+
execFileSync("tar", ["-xzf", archivePath, "-C", destDir, "--strip-components", "1"], { stdio: "inherit" });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function install() {
|
|
127
|
+
if (!platform || !arch) throw new Error(`Unsupported platform: ${process.platform}-${process.arch}`);
|
|
128
|
+
const mirrors = resolveMirrorUrls(process.env, archiveName, VERSION);
|
|
129
|
+
for (const url of mirrors) ALLOWED_HOSTS.add(new URL(url).hostname);
|
|
130
|
+
const urls = [githubUrl, ...mirrors];
|
|
131
|
+
|
|
132
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), `${NAME}-`));
|
|
133
|
+
const archivePath = path.join(tmpDir, archiveName);
|
|
134
|
+
try {
|
|
135
|
+
let lastErr;
|
|
136
|
+
for (const url of urls) {
|
|
137
|
+
try {
|
|
138
|
+
download(url, archivePath);
|
|
139
|
+
lastErr = null;
|
|
140
|
+
break;
|
|
141
|
+
} catch (err) {
|
|
142
|
+
lastErr = err;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (lastErr) throw lastErr;
|
|
146
|
+
verifyChecksum(archivePath, getExpectedChecksum(archiveName));
|
|
147
|
+
extractArchive(archivePath, installDir);
|
|
148
|
+
console.log(`${NAME} v${VERSION} installed successfully`);
|
|
149
|
+
} finally {
|
|
150
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (require.main === module) {
|
|
155
|
+
const isNpxPostinstall = process.env.npm_command === "exec" && !process.env.RAINBO_CLI_RUN;
|
|
156
|
+
if (isNpxPostinstall) process.exit(0);
|
|
157
|
+
try {
|
|
158
|
+
install();
|
|
159
|
+
} catch (err) {
|
|
160
|
+
console.error(`Failed to install ${NAME}: ${err.message}`);
|
|
161
|
+
console.error(`Make sure Rscript is installed and the v${VERSION} GitHub Release exists.`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = { getExpectedChecksum, verifyChecksum, resolveMirrorUrls, assertAllowedHost };
|
|
167
|
+
|
|
@@ -5,40 +5,46 @@ const fs = require("fs");
|
|
|
5
5
|
const path = require("path");
|
|
6
6
|
|
|
7
7
|
const isWindows = process.platform === "win32";
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"rainbo-cli",
|
|
13
|
-
isWindows ? "rainbo-cli.cmd" : "rainbo-cli"
|
|
14
|
-
);
|
|
15
|
-
const sourceEntry = path.join(__dirname, "..", "src", "rainbo-cli.R");
|
|
8
|
+
const repoRoot = path.join(__dirname, "..", "..");
|
|
9
|
+
const vendorRoot = path.join(repoRoot, "vendor", "rainbo-cli");
|
|
10
|
+
const launcher = path.join(vendorRoot, isWindows ? "rainbo-cli.cmd" : "rainbo-cli");
|
|
11
|
+
const sourceEntry = path.join(repoRoot, "bin", "rainbo-cli.R");
|
|
16
12
|
|
|
17
13
|
function installIfMissing() {
|
|
18
|
-
if (fs.existsSync(launcher)) return;
|
|
19
|
-
if (fs.existsSync(sourceEntry)) return;
|
|
20
|
-
|
|
14
|
+
if (fs.existsSync(launcher) || fs.existsSync(sourceEntry)) return;
|
|
21
15
|
const result = spawnSync(process.execPath, [path.join(__dirname, "install.js")], {
|
|
22
16
|
stdio: "inherit",
|
|
23
17
|
env: { ...process.env, RAINBO_CLI_RUN: "true" },
|
|
24
18
|
});
|
|
19
|
+
if (result.status !== 0) process.exit(result.status || 1);
|
|
20
|
+
}
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
function recoverWindowsOldLauncher() {
|
|
23
|
+
if (!isWindows) return;
|
|
24
|
+
const oldLauncher = launcher + ".old";
|
|
25
|
+
if (!fs.existsSync(oldLauncher)) return;
|
|
26
|
+
try {
|
|
27
|
+
if (!fs.existsSync(launcher)) {
|
|
28
|
+
fs.renameSync(oldLauncher, launcher);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
fs.rmSync(oldLauncher, { force: true });
|
|
32
|
+
} catch (_) {
|
|
33
|
+
// Best-effort cleanup only.
|
|
28
34
|
}
|
|
29
35
|
}
|
|
30
36
|
|
|
37
|
+
recoverWindowsOldLauncher();
|
|
31
38
|
installIfMissing();
|
|
32
39
|
|
|
33
40
|
const useSource = fs.existsSync(sourceEntry);
|
|
34
41
|
const command = useSource ? "Rscript" : launcher;
|
|
35
42
|
const args = useSource ? [sourceEntry, ...process.argv.slice(2)] : process.argv.slice(2);
|
|
36
|
-
|
|
37
43
|
const result = spawnSync(command, args, { stdio: "inherit", shell: isWindows });
|
|
38
44
|
|
|
39
45
|
if (result.error) {
|
|
40
46
|
console.error(result.error.message);
|
|
41
47
|
process.exit(1);
|
|
42
48
|
}
|
|
43
|
-
|
|
44
49
|
process.exit(result.status || 0);
|
|
50
|
+
|
package/scripts/install.js
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const crypto = require("crypto");
|
|
4
|
-
const fs = require("fs");
|
|
5
|
-
const os = require("os");
|
|
6
|
-
const path = require("path");
|
|
7
|
-
const { execFileSync } = require("child_process");
|
|
8
|
-
|
|
9
|
-
const VERSION = require("../package.json").version.replace(/-.*$/, "");
|
|
10
|
-
const REPO = "work2a/rainbo-cli";
|
|
11
|
-
const NAME = "rainbo-cli";
|
|
12
|
-
|
|
13
|
-
const PLATFORM_MAP = {
|
|
14
|
-
darwin: "darwin",
|
|
15
|
-
linux: "linux",
|
|
16
|
-
win32: "windows",
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const ARCH_MAP = {
|
|
20
|
-
x64: "amd64",
|
|
21
|
-
arm64: "arm64",
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const platform = PLATFORM_MAP[process.platform];
|
|
25
|
-
const arch = ARCH_MAP[process.arch];
|
|
26
|
-
const isWindows = process.platform === "win32";
|
|
27
|
-
const ext = isWindows ? ".zip" : ".tar.gz";
|
|
28
|
-
const archiveName = `${NAME}-${VERSION}-${platform}-${arch}${ext}`;
|
|
29
|
-
const githubUrl = `https://github.com/${REPO}/releases/download/v${VERSION}/${archiveName}`;
|
|
30
|
-
const vendorDir = path.join(__dirname, "..", "vendor");
|
|
31
|
-
const installDir = path.join(vendorDir, NAME);
|
|
32
|
-
|
|
33
|
-
function download(url, destPath) {
|
|
34
|
-
execFileSync("curl", [
|
|
35
|
-
"--fail",
|
|
36
|
-
"--location",
|
|
37
|
-
"--silent",
|
|
38
|
-
"--show-error",
|
|
39
|
-
"--connect-timeout",
|
|
40
|
-
"10",
|
|
41
|
-
"--max-time",
|
|
42
|
-
"120",
|
|
43
|
-
"--max-redirs",
|
|
44
|
-
"3",
|
|
45
|
-
"--output",
|
|
46
|
-
destPath,
|
|
47
|
-
url,
|
|
48
|
-
], { stdio: ["ignore", "ignore", "pipe"] });
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function getExpectedChecksum(archive) {
|
|
52
|
-
const checksumsPath = path.join(__dirname, "..", "checksums.txt");
|
|
53
|
-
if (!fs.existsSync(checksumsPath)) {
|
|
54
|
-
console.error("[WARN] checksums.txt not found, skipping checksum verification");
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const content = fs.readFileSync(checksumsPath, "utf8");
|
|
59
|
-
for (const line of content.split("\n")) {
|
|
60
|
-
const trimmed = line.trim();
|
|
61
|
-
if (!trimmed) continue;
|
|
62
|
-
const idx = trimmed.indexOf(" ");
|
|
63
|
-
if (idx === -1) continue;
|
|
64
|
-
if (trimmed.slice(idx + 2) === archive) return trimmed.slice(0, idx);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
throw new Error(`Checksum entry not found for ${archive}`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function verifyChecksum(archivePath, expectedHash) {
|
|
71
|
-
if (expectedHash === null) return;
|
|
72
|
-
|
|
73
|
-
const actual = crypto.createHash("sha256")
|
|
74
|
-
.update(fs.readFileSync(archivePath))
|
|
75
|
-
.digest("hex");
|
|
76
|
-
|
|
77
|
-
if (actual.toLowerCase() !== expectedHash.toLowerCase()) {
|
|
78
|
-
throw new Error(`Checksum mismatch for ${path.basename(archivePath)}`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function extractArchive(archivePath, destDir) {
|
|
83
|
-
fs.rmSync(destDir, { recursive: true, force: true });
|
|
84
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
85
|
-
|
|
86
|
-
if (isWindows) {
|
|
87
|
-
const psCommand =
|
|
88
|
-
"$ErrorActionPreference='Stop';" +
|
|
89
|
-
"Expand-Archive -LiteralPath $env:RAINBO_CLI_ARCHIVE -DestinationPath $env:RAINBO_CLI_DEST -Force";
|
|
90
|
-
execFileSync("powershell.exe", [
|
|
91
|
-
"-NoProfile",
|
|
92
|
-
"-ExecutionPolicy",
|
|
93
|
-
"Bypass",
|
|
94
|
-
"-Command",
|
|
95
|
-
psCommand,
|
|
96
|
-
], {
|
|
97
|
-
stdio: "inherit",
|
|
98
|
-
env: {
|
|
99
|
-
...process.env,
|
|
100
|
-
RAINBO_CLI_ARCHIVE: archivePath,
|
|
101
|
-
RAINBO_CLI_DEST: destDir,
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
} else {
|
|
105
|
-
execFileSync("tar", ["-xzf", archivePath, "-C", destDir, "--strip-components", "1"], {
|
|
106
|
-
stdio: "inherit",
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function install() {
|
|
112
|
-
if (!platform || !arch) {
|
|
113
|
-
throw new Error(`Unsupported platform: ${process.platform}-${process.arch}`);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), `${NAME}-`));
|
|
117
|
-
const archivePath = path.join(tmpDir, archiveName);
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
download(githubUrl, archivePath);
|
|
121
|
-
verifyChecksum(archivePath, getExpectedChecksum(archiveName));
|
|
122
|
-
extractArchive(archivePath, installDir);
|
|
123
|
-
console.log(`${NAME} v${VERSION} installed successfully`);
|
|
124
|
-
} finally {
|
|
125
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (require.main === module) {
|
|
130
|
-
const isNpxPostinstall = process.env.npm_command === "exec" && !process.env.RAINBO_CLI_RUN;
|
|
131
|
-
if (isNpxPostinstall) process.exit(0);
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
install();
|
|
135
|
-
} catch (err) {
|
|
136
|
-
console.error(`Failed to install ${NAME}: ${err.message}`);
|
|
137
|
-
console.error(`Make sure the v${VERSION} GitHub Release exists and Rscript is installed.`);
|
|
138
|
-
process.exit(1);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
module.exports = { getExpectedChecksum, verifyChecksum };
|
|
143
|
-
|