@colony2/c2j 0.0.50 → 0.0.51
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 +43 -2
- package/bin/cli.js +30 -0
- package/package.json +31 -73
- package/scripts/postinstall.js +152 -0
- package/install.js +0 -6
- package/lib.js +0 -259
- package/run-c2j.js +0 -6
package/README.md
CHANGED
|
@@ -14,6 +14,14 @@ Use it when you want to:
|
|
|
14
14
|
|
|
15
15
|
Examples below assume you are running from the repo root.
|
|
16
16
|
|
|
17
|
+
## JobDB upgrade compatibility
|
|
18
|
+
|
|
19
|
+
The typed-route JobDB update requires fresh format-3 databases/artifact storage
|
|
20
|
+
and matching server/worker versions; existing jobs are not migrated. Job and
|
|
21
|
+
task types now travel separately, preserving existing task names. Read
|
|
22
|
+
[the upgrade notes](JOBDB_UPGRADE_NOTES.md) before deploying it. No existing data
|
|
23
|
+
is automatically reset.
|
|
24
|
+
|
|
17
25
|
## Command Summary
|
|
18
26
|
|
|
19
27
|
```bash
|
|
@@ -142,15 +150,34 @@ This is mainly useful when you want to target another cell by short name and nee
|
|
|
142
150
|
|
|
143
151
|
### `c2j init`
|
|
144
152
|
|
|
145
|
-
Writes a commented `.c2j/config.yaml` template.
|
|
153
|
+
Writes a commented `.c2j/config.yaml` template and installs bundled c2j skills for Codex-compatible agents.
|
|
146
154
|
|
|
147
155
|
```bash
|
|
148
156
|
c2j init
|
|
149
157
|
c2j init --stdout
|
|
150
158
|
c2j init --force
|
|
159
|
+
c2j init --no-skills
|
|
160
|
+
c2j init --with-op-skills
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The generated template can derive values from the Go module in the current repo when `base: go` is appropriate. `--stdout` prints only the config and does not install skills.
|
|
164
|
+
|
|
165
|
+
Bundled skill install controls:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
c2j init --skills-scope auto
|
|
169
|
+
c2j init --skills-scope project
|
|
170
|
+
c2j init --skills-scope user
|
|
171
|
+
c2j init --skills-force
|
|
151
172
|
```
|
|
152
173
|
|
|
153
|
-
|
|
174
|
+
`auto` currently installs to the project-local `.agents/skills` directory. `user` installs to `${CODEX_HOME:-$HOME/.codex}/skills`. Install metadata is recorded in `.c2j/skills-lock.yaml`.
|
|
175
|
+
|
|
176
|
+
You can also install the repo skills directly with the public `skills` tool:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npx skills add colony-2/c2j
|
|
180
|
+
```
|
|
154
181
|
|
|
155
182
|
## Submitting Jobs
|
|
156
183
|
|
|
@@ -489,6 +516,20 @@ Useful filters:
|
|
|
489
516
|
- `--page-token`
|
|
490
517
|
- `--all`
|
|
491
518
|
|
|
519
|
+
Filter by a pending task using a JSON route:
|
|
520
|
+
|
|
521
|
+
```bash
|
|
522
|
+
c2j list --self --waiting-for '{"jobType":"recipe","taskType":"input:collect_user_input"}' --embed
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Repeat `--waiting-for` for multiple routes and `--job-type` for multiple job
|
|
526
|
+
types. These flags accept literal identifiers, not comma-separated lists;
|
|
527
|
+
`--waiting-for JOBTYPE:TASKTYPE` is no longer supported because either type may
|
|
528
|
+
contain colons. Identifiers are case-sensitive and are not trimmed or encoded.
|
|
529
|
+
List JSON exposes `next_route` (`jobType`, optional `taskType`) and `task_wait`
|
|
530
|
+
instead of the old `next_need` and flat `task_wait_*` fields. The human-readable
|
|
531
|
+
`NEXT` column also shows the route as JSON.
|
|
532
|
+
|
|
492
533
|
## Embedded Runtime
|
|
493
534
|
|
|
494
535
|
`--embed` is shorthand for:
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const { spawnSync } = require("child_process");
|
|
8
|
+
|
|
9
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
10
|
+
const packageJson = require(path.join(packageRoot, "package.json"));
|
|
11
|
+
const packageName = packageJson.name.split("/").pop();
|
|
12
|
+
const binaryPath = path.join(packageRoot, "vendor", packageName);
|
|
13
|
+
|
|
14
|
+
if (!fs.existsSync(binaryPath)) {
|
|
15
|
+
console.error(
|
|
16
|
+
`${packageName} binary is missing. Reinstall the package or run npm rebuild ${packageJson.name}.`
|
|
17
|
+
);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const result = spawnSync(binaryPath, process.argv.slice(2), {
|
|
22
|
+
stdio: "inherit",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (result.error) {
|
|
26
|
+
console.error(result.error.message);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
process.exit(result.status === null ? 1 : result.status);
|
package/package.json
CHANGED
|
@@ -1,85 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colony2/c2j",
|
|
3
|
-
"
|
|
4
|
-
"version": "0.0.50",
|
|
3
|
+
"version": "0.0.51",
|
|
5
4
|
"description": "c2j job system",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
"run": "node run-c2j.js"
|
|
9
|
-
},
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"homepage": "https://github.com/colony-2/c2j",
|
|
10
7
|
"repository": {
|
|
11
8
|
"type": "git",
|
|
12
9
|
"url": "git+https://github.com/colony-2/c2j.git"
|
|
13
10
|
},
|
|
14
|
-
"author": {
|
|
15
|
-
"email": "noreply@colony2.com",
|
|
16
|
-
"name": "Colony 2"
|
|
17
|
-
},
|
|
18
|
-
"license": "Apache-2.0",
|
|
19
11
|
"bugs": {
|
|
20
12
|
"url": "https://github.com/colony-2/c2j/issues"
|
|
21
13
|
},
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
},
|
|
26
|
-
"dependencies": {
|
|
27
|
-
"jszip": "3.10.1",
|
|
28
|
-
"proxy-agent": "8.0.1",
|
|
29
|
-
"tar": "7.5.15"
|
|
30
|
-
},
|
|
31
|
-
"archives": {
|
|
32
|
-
"darwin-arm64": {
|
|
33
|
-
"name": "c2j_0.0.50_Darwin_arm64.tar.gz",
|
|
34
|
-
"url": "https://github.com/colony-2/c2j/releases/download/v0.0.50/c2j_0.0.50_Darwin_arm64.tar.gz",
|
|
35
|
-
"bins": [
|
|
36
|
-
"c2j"
|
|
37
|
-
],
|
|
38
|
-
"format": "tar.gz",
|
|
39
|
-
"checksum": {
|
|
40
|
-
"algorithm": "sha256",
|
|
41
|
-
"digest": "b91fdeb7d21a36f2600eaf3ffc1661ce014ab442aecceae26fd4254d402656a1"
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
"darwin-x64": {
|
|
45
|
-
"name": "c2j_0.0.50_Darwin_x86_64.tar.gz",
|
|
46
|
-
"url": "https://github.com/colony-2/c2j/releases/download/v0.0.50/c2j_0.0.50_Darwin_x86_64.tar.gz",
|
|
47
|
-
"bins": [
|
|
48
|
-
"c2j"
|
|
49
|
-
],
|
|
50
|
-
"format": "tar.gz",
|
|
51
|
-
"checksum": {
|
|
52
|
-
"algorithm": "sha256",
|
|
53
|
-
"digest": "ae12ffdda3d24b6b14f55dbd329b488f69c02907bde62589ca0c234deb7080ce"
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
"linux-arm64": {
|
|
57
|
-
"name": "c2j_0.0.50_Linux_arm64.tar.gz",
|
|
58
|
-
"url": "https://github.com/colony-2/c2j/releases/download/v0.0.50/c2j_0.0.50_Linux_arm64.tar.gz",
|
|
59
|
-
"bins": [
|
|
60
|
-
"c2j"
|
|
61
|
-
],
|
|
62
|
-
"format": "tar.gz",
|
|
63
|
-
"checksum": {
|
|
64
|
-
"algorithm": "sha256",
|
|
65
|
-
"digest": "e198f9037bd138270aa92ac8b5caa4509b9f76056273d4191909e810d406ba72"
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
"linux-x64": {
|
|
69
|
-
"name": "c2j_0.0.50_Linux_x86_64.tar.gz",
|
|
70
|
-
"url": "https://github.com/colony-2/c2j/releases/download/v0.0.50/c2j_0.0.50_Linux_x86_64.tar.gz",
|
|
71
|
-
"bins": [
|
|
72
|
-
"c2j"
|
|
73
|
-
],
|
|
74
|
-
"format": "tar.gz",
|
|
75
|
-
"checksum": {
|
|
76
|
-
"algorithm": "sha256",
|
|
77
|
-
"digest": "c8b720b153a9b7354887bfb9dd3f7d26ff615cd29a74dd8385fecda8f79d6e0f"
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
"engines": {
|
|
82
|
-
"node": "\u003e=14.0.0"
|
|
14
|
+
"author": {
|
|
15
|
+
"name": "Colony 2",
|
|
16
|
+
"email": "noreply@colony2.com"
|
|
83
17
|
},
|
|
84
18
|
"keywords": [
|
|
85
19
|
"sandbox",
|
|
@@ -88,5 +22,29 @@
|
|
|
88
22
|
"agent",
|
|
89
23
|
"cli",
|
|
90
24
|
"security"
|
|
25
|
+
],
|
|
26
|
+
"bin": {
|
|
27
|
+
"c2j": "bin/cli.js"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"postinstall": "node scripts/postinstall.js"
|
|
31
|
+
},
|
|
32
|
+
"os": [
|
|
33
|
+
"darwin",
|
|
34
|
+
"linux"
|
|
35
|
+
],
|
|
36
|
+
"cpu": [
|
|
37
|
+
"x64",
|
|
38
|
+
"arm64"
|
|
39
|
+
],
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=14.0.0"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"bin/",
|
|
45
|
+
"scripts/",
|
|
46
|
+
"vendor/",
|
|
47
|
+
"README.md",
|
|
48
|
+
"LICENSE"
|
|
91
49
|
]
|
|
92
|
-
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const crypto = require("crypto");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const os = require("os");
|
|
8
|
+
const path = require("path");
|
|
9
|
+
const { execFileSync } = require("child_process");
|
|
10
|
+
const { get } = require("https");
|
|
11
|
+
|
|
12
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
13
|
+
const packageJson = require(path.join(packageRoot, "package.json"));
|
|
14
|
+
const packageName = packageJson.name.split("/").pop();
|
|
15
|
+
const version = packageJson.version;
|
|
16
|
+
|
|
17
|
+
const platforms = {
|
|
18
|
+
"linux:x64": ["Linux", "x86_64"],
|
|
19
|
+
"linux:arm64": ["Linux", "arm64"],
|
|
20
|
+
"darwin:x64": ["Darwin", "x86_64"],
|
|
21
|
+
"darwin:arm64": ["Darwin", "arm64"],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const platformKey = `${process.platform}:${process.arch}`;
|
|
25
|
+
const target = platforms[platformKey];
|
|
26
|
+
|
|
27
|
+
if (!target) {
|
|
28
|
+
throw new Error(`Unsupported platform for ${packageJson.name}: ${platformKey}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const repo = repositoryPath(packageJson);
|
|
32
|
+
const tag = `v${version}`;
|
|
33
|
+
const assetName = `${packageName}_${version}_${target[0]}_${target[1]}.tar.gz`;
|
|
34
|
+
const baseUrl = `https://github.com/${repo}/releases/download/${tag}`;
|
|
35
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `${packageName}-`));
|
|
36
|
+
const archivePath = path.join(tempDir, assetName);
|
|
37
|
+
const checksumsPath = path.join(tempDir, "checksums.txt");
|
|
38
|
+
const extractDir = path.join(tempDir, "extract");
|
|
39
|
+
const vendorDir = path.join(packageRoot, "vendor");
|
|
40
|
+
const installedBinary = path.join(vendorDir, packageName);
|
|
41
|
+
|
|
42
|
+
fs.mkdirSync(extractDir);
|
|
43
|
+
fs.mkdirSync(vendorDir, { recursive: true });
|
|
44
|
+
|
|
45
|
+
downloadWithRetry(`${baseUrl}/${assetName}`, archivePath)
|
|
46
|
+
.then(() => downloadWithRetry(`${baseUrl}/checksums.txt`, checksumsPath))
|
|
47
|
+
.then(() => {
|
|
48
|
+
verifyChecksum(archivePath, checksumsPath, assetName);
|
|
49
|
+
execFileSync("tar", ["-xzf", archivePath, "-C", extractDir], {
|
|
50
|
+
stdio: "inherit",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const extractedBinary = path.join(extractDir, packageName);
|
|
54
|
+
if (!fs.existsSync(extractedBinary)) {
|
|
55
|
+
throw new Error(`Release archive did not contain ${packageName}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fs.copyFileSync(extractedBinary, installedBinary);
|
|
59
|
+
fs.chmodSync(installedBinary, 0o755);
|
|
60
|
+
})
|
|
61
|
+
.catch((error) => {
|
|
62
|
+
console.error(error.message || error);
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
})
|
|
65
|
+
.finally(() => {
|
|
66
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
function repositoryPath(pkg) {
|
|
70
|
+
const raw =
|
|
71
|
+
typeof pkg.repository === "string" ? pkg.repository : pkg.repository && pkg.repository.url;
|
|
72
|
+
|
|
73
|
+
if (!raw) {
|
|
74
|
+
throw new Error("package.json repository is required to locate release assets");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const match = raw.match(/github\.com[:/]([^/]+\/[^/.]+)(?:\.git)?/);
|
|
78
|
+
if (!match) {
|
|
79
|
+
throw new Error(`Unsupported repository URL for release assets: ${raw}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return match[1];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function downloadWithRetry(url, destination) {
|
|
86
|
+
let lastError;
|
|
87
|
+
|
|
88
|
+
for (let attempt = 1; attempt <= 5; attempt += 1) {
|
|
89
|
+
try {
|
|
90
|
+
await download(url, destination);
|
|
91
|
+
return;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
lastError = error;
|
|
94
|
+
if (attempt === 5) {
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
await delay(attempt * 1000);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw lastError;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function download(url, destination) {
|
|
105
|
+
return new Promise((resolve, reject) => {
|
|
106
|
+
const request = get(url, (response) => {
|
|
107
|
+
if (
|
|
108
|
+
response.statusCode >= 300 &&
|
|
109
|
+
response.statusCode < 400 &&
|
|
110
|
+
response.headers.location
|
|
111
|
+
) {
|
|
112
|
+
response.resume();
|
|
113
|
+
download(response.headers.location, destination).then(resolve, reject);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (response.statusCode !== 200) {
|
|
118
|
+
response.resume();
|
|
119
|
+
reject(new Error(`Failed to download ${url}: HTTP ${response.statusCode}`));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const file = fs.createWriteStream(destination, { mode: 0o600 });
|
|
124
|
+
response.pipe(file);
|
|
125
|
+
file.on("finish", () => file.close(resolve));
|
|
126
|
+
file.on("error", reject);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
request.on("error", reject);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function delay(ms) {
|
|
134
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function verifyChecksum(archive, checksums, assetName) {
|
|
138
|
+
const expected = fs
|
|
139
|
+
.readFileSync(checksums, "utf8")
|
|
140
|
+
.split(/\r?\n/)
|
|
141
|
+
.map((line) => line.trim().split(/\s+/))
|
|
142
|
+
.find((parts) => parts[1] === assetName);
|
|
143
|
+
|
|
144
|
+
if (!expected) {
|
|
145
|
+
throw new Error(`No checksum found for ${assetName}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const actual = crypto.createHash("sha256").update(fs.readFileSync(archive)).digest("hex");
|
|
149
|
+
if (actual !== expected[0]) {
|
|
150
|
+
throw new Error(`Checksum mismatch for ${assetName}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
package/install.js
DELETED
package/lib.js
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
// This file was generated by GoReleaser. DO NOT EDIT.
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import crypto from "crypto";
|
|
4
|
-
import http from "http";
|
|
5
|
-
import https from "https";
|
|
6
|
-
import path from "path";
|
|
7
|
-
import JSZip from "jszip";
|
|
8
|
-
import { x as tarExtract } from "tar";
|
|
9
|
-
import { ProxyAgent } from "proxy-agent";
|
|
10
|
-
import { spawnSync } from "child_process";
|
|
11
|
-
import { fileURLToPath } from "url";
|
|
12
|
-
|
|
13
|
-
const { archives, name, version } = JSON.parse(
|
|
14
|
-
fs.readFileSync(new URL("./package.json", import.meta.url), "utf8"),
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
const agent = new ProxyAgent();
|
|
18
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
-
|
|
20
|
-
const getArchive = () => {
|
|
21
|
-
let target = `${process.platform}-${process.arch}`;
|
|
22
|
-
const archive = archives[target];
|
|
23
|
-
if (!archive) {
|
|
24
|
-
throw new Error(`No archive available for ${target}`);
|
|
25
|
-
}
|
|
26
|
-
return archive;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const binDir = path.join(__dirname, "bin");
|
|
30
|
-
|
|
31
|
-
async function extractTar(tarPath, binaries, dir, wrappedIn) {
|
|
32
|
-
try {
|
|
33
|
-
const filesToExtract = wrappedIn
|
|
34
|
-
? binaries.map((bin) =>
|
|
35
|
-
path.join(wrappedIn, bin).replace(/\\/g, "/"),
|
|
36
|
-
)
|
|
37
|
-
: binaries;
|
|
38
|
-
|
|
39
|
-
await tarExtract({
|
|
40
|
-
file: tarPath,
|
|
41
|
-
cwd: dir,
|
|
42
|
-
filter: (path) => filesToExtract.includes(path),
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// If wrapped, move files from wrapped directory to bin directory
|
|
46
|
-
if (wrappedIn) {
|
|
47
|
-
const wrappedDir = path.join(dir, wrappedIn);
|
|
48
|
-
for (const binary of binaries) {
|
|
49
|
-
const srcPath = path.join(wrappedDir, binary);
|
|
50
|
-
const destPath = path.join(dir, binary);
|
|
51
|
-
if (fs.existsSync(srcPath)) {
|
|
52
|
-
fs.renameSync(srcPath, destPath);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
// Clean up empty wrapped directory
|
|
56
|
-
try {
|
|
57
|
-
fs.rmSync(wrappedDir, { recursive: true, force: true });
|
|
58
|
-
} catch (err) {
|
|
59
|
-
// Ignore cleanup errors
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
console.log(`Successfully extracted ${binaries} to "${dir}"`);
|
|
64
|
-
} catch (err) {
|
|
65
|
-
throw new Error(`Extraction failed: ${err.message}`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async function extractZip(zipPath, binaries, dir, wrappedIn) {
|
|
70
|
-
try {
|
|
71
|
-
const zipData = fs.readFileSync(zipPath);
|
|
72
|
-
const zip = await JSZip.loadAsync(zipData);
|
|
73
|
-
|
|
74
|
-
for (const binary of binaries) {
|
|
75
|
-
const binaryPath = wrappedIn
|
|
76
|
-
? path.join(wrappedIn, binary).replace(/\\/g, "/")
|
|
77
|
-
: binary;
|
|
78
|
-
|
|
79
|
-
if (!zip.files[binaryPath]) {
|
|
80
|
-
throw new Error(
|
|
81
|
-
`Error: ${binaryPath} does not exist in ${zipPath}`,
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const content = await zip.files[binaryPath].async("nodebuffer");
|
|
86
|
-
if (!fs.existsSync(dir)) {
|
|
87
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
88
|
-
}
|
|
89
|
-
const file = path.join(dir, binary);
|
|
90
|
-
fs.writeFileSync(file, content);
|
|
91
|
-
fs.chmodSync(file, "755");
|
|
92
|
-
console.log(`Successfully extracted "${binary}" to "${dir}"`);
|
|
93
|
-
}
|
|
94
|
-
} catch (err) {
|
|
95
|
-
throw new Error(`Extraction failed: ${err.message}`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const run = async (bin) => {
|
|
100
|
-
await install();
|
|
101
|
-
if (process.platform === "win32") {
|
|
102
|
-
bin += ".exe";
|
|
103
|
-
}
|
|
104
|
-
const [, , ...args] = process.argv;
|
|
105
|
-
let result = spawnSync(path.join(binDir, bin), args, {
|
|
106
|
-
cwd: process.cwd(),
|
|
107
|
-
stdio: "inherit",
|
|
108
|
-
});
|
|
109
|
-
if (result.error) {
|
|
110
|
-
console.error(result.error);
|
|
111
|
-
}
|
|
112
|
-
return result.status;
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const install = async () => {
|
|
116
|
-
try {
|
|
117
|
-
let archive = getArchive();
|
|
118
|
-
if (await exists(archive)) {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
let tmp = fs.mkdtempSync("archive-");
|
|
122
|
-
let archivePath = path.join(tmp, archive.name);
|
|
123
|
-
await download(archive.url, archivePath);
|
|
124
|
-
verify(archivePath, archive.checksum);
|
|
125
|
-
|
|
126
|
-
if (!fs.existsSync(binDir)) {
|
|
127
|
-
fs.mkdirSync(binDir);
|
|
128
|
-
}
|
|
129
|
-
switch (archive.format) {
|
|
130
|
-
case "binary":
|
|
131
|
-
const bin = path.join(binDir, archive.bins[0]);
|
|
132
|
-
fs.copyFileSync(archivePath, bin);
|
|
133
|
-
fs.chmodSync(bin, 0o755);
|
|
134
|
-
break;
|
|
135
|
-
case "zip":
|
|
136
|
-
await extractZip(
|
|
137
|
-
archivePath,
|
|
138
|
-
archive.bins,
|
|
139
|
-
binDir,
|
|
140
|
-
archive.wrappedIn,
|
|
141
|
-
);
|
|
142
|
-
break;
|
|
143
|
-
case "tar":
|
|
144
|
-
case "tar.gz":
|
|
145
|
-
case "tgz":
|
|
146
|
-
await extractTar(
|
|
147
|
-
archivePath,
|
|
148
|
-
archive.bins,
|
|
149
|
-
binDir,
|
|
150
|
-
archive.wrappedIn,
|
|
151
|
-
);
|
|
152
|
-
break;
|
|
153
|
-
case "tar.zst":
|
|
154
|
-
case "tzst":
|
|
155
|
-
case "tar.xz":
|
|
156
|
-
case "txz":
|
|
157
|
-
default:
|
|
158
|
-
throw new Error(`unsupported format: ${archive.format}`);
|
|
159
|
-
}
|
|
160
|
-
console.log(`Installed ${name} ${version} to ${binDir}`);
|
|
161
|
-
} catch (err) {
|
|
162
|
-
throw new Error(`Installation failed: ${err.message}`);
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
const verify = (filename, checksum) => {
|
|
167
|
-
if (checksum.algorithm == "" || checksum.digest == "") {
|
|
168
|
-
console.warn("Warning: No checksum provided for verification");
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
let digest = crypto
|
|
172
|
-
.createHash(checksum.algorithm)
|
|
173
|
-
.update(fs.readFileSync(filename))
|
|
174
|
-
.digest("hex");
|
|
175
|
-
if (digest != checksum.digest) {
|
|
176
|
-
throw new Error(
|
|
177
|
-
`${filename}: ${checksum.algorithm} does not match, expected ${checksum.digest}, got ${digest}`,
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const download = async (url, filename, maxRedirects = 10) => {
|
|
183
|
-
try {
|
|
184
|
-
console.log(`Downloading ${url} to ${filename}...`);
|
|
185
|
-
const dir = path.dirname(filename);
|
|
186
|
-
if (!fs.existsSync(dir)) {
|
|
187
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return new Promise((resolve, reject) => {
|
|
191
|
-
const parsedUrl = new URL(url);
|
|
192
|
-
const mod = parsedUrl.protocol === "https:" ? https : http;
|
|
193
|
-
|
|
194
|
-
const request = mod.get(url, { agent }, (response) => {
|
|
195
|
-
if (
|
|
196
|
-
response.statusCode >= 300 &&
|
|
197
|
-
response.statusCode < 400 &&
|
|
198
|
-
response.headers.location
|
|
199
|
-
) {
|
|
200
|
-
if (maxRedirects <= 0) {
|
|
201
|
-
reject(new Error("Too many redirects"));
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
download(response.headers.location, filename, maxRedirects - 1)
|
|
205
|
-
.then(resolve)
|
|
206
|
-
.catch(reject);
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (response.statusCode !== 200) {
|
|
211
|
-
reject(
|
|
212
|
-
new Error(
|
|
213
|
-
`HTTP ${response.statusCode}: ${response.statusMessage}`,
|
|
214
|
-
),
|
|
215
|
-
);
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const writer = fs.createWriteStream(filename);
|
|
220
|
-
response.pipe(writer);
|
|
221
|
-
|
|
222
|
-
writer.on("finish", () => {
|
|
223
|
-
console.log(`Download complete: ${filename}`);
|
|
224
|
-
resolve(dir);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
writer.on("error", (err) => {
|
|
228
|
-
console.error(`Error writing file: ${err.message}`);
|
|
229
|
-
reject(err);
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
request.on("error", (err) => {
|
|
234
|
-
reject(new Error(`Request failed: ${err.message}`));
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
request.setTimeout(300000, () => {
|
|
238
|
-
request.destroy();
|
|
239
|
-
reject(new Error("Request timed out"));
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
} catch (err) {
|
|
243
|
-
throw new Error(`Download failed: ${err.message}`);
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
function exists(archive) {
|
|
248
|
-
if (!fs.existsSync(binDir)) {
|
|
249
|
-
return false;
|
|
250
|
-
}
|
|
251
|
-
return archive.bins.every((bin) => fs.existsSync(path.join(binDir, bin)));
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export {
|
|
255
|
-
install,
|
|
256
|
-
run,
|
|
257
|
-
getArchive,
|
|
258
|
-
download,
|
|
259
|
-
};
|