@colony2/c2j 0.0.41 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # c2j
2
2
 
3
- `c2j` is the local job-oriented CLI for submitting and running recipe jobs through an SWF runtime.
3
+ `c2j` is the local job-oriented CLI for submitting and running recipe jobs through JobDB.
4
4
 
5
5
  Use it when you want to:
6
6
 
@@ -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
@@ -91,7 +99,7 @@ That does all of the following:
91
99
 
92
100
  - loads the local YAML file
93
101
  - embeds that recipe into the submitted job
94
- - starts an embedded SWF runtime
102
+ - starts an embedded JobDB runtime
95
103
  - submits the job
96
104
  - immediately executes it
97
105
 
@@ -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
- The generated template can derive values from the Go module in the current repo when `base: go` is appropriate.
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
 
@@ -333,7 +360,7 @@ Useful flags:
333
360
  - `--case <id>` filters suite mode to selected cases
334
361
  - `--parallelism <n>` controls local case concurrency
335
362
  - `--out-dir <dir>` defaults to `.c2j/test-results/<timestamp>/`
336
- - passthrough cases use a disposable embedded runtime root automatically; use `--runtime-root` and `--keep-runtime` only for debugging
363
+ - passthrough cases use a disposable embedded runtime automatically
337
364
 
338
365
  ## Running Jobs
339
366
 
@@ -403,21 +430,20 @@ the job becomes runnable or the wait timeout is reached.
403
430
  ## Worker Modes
404
431
 
405
432
  `c2j run loop` runs a long-lived worker for one tenant. It leases available jobs
406
- from an external SWF runtime and executes up to the configured local concurrency.
433
+ from a remote JobDB runtime and executes up to the configured local concurrency.
407
434
 
408
435
  Basic usage:
409
436
 
410
437
  ```bash
411
- c2j run loop --tenant-id <tenant-id> --swf-url http://localhost:9047 --concurrency 4
438
+ c2j run loop --jobdb http://localhost:9047/<tenant-id> --concurrency 4
412
439
  ```
413
440
 
414
441
  Important behavior:
415
442
 
416
443
  - `--concurrency <n>` controls how many jobs this process can run at once
417
- - `--tenant-id` defaults the same way as `submit`, `run`, and `list`
418
- - `--swf-url` must be an external `http://` or `https://` runtime
444
+ - `--jobdb` must be a remote `http://host/tenant` or `https://host/tenant` URI
419
445
  - `--embed` is not available for `run loop`
420
- - `--swf-url embed:///` is rejected for `run loop`
446
+ - `--jobdb embed:///` is rejected for `run loop`
421
447
  - `run loop` is non-interactive; use `run` or an ops surface for jobs that need input
422
448
 
423
449
  ### Loose scheduling with `ready` and `run any`
@@ -425,7 +451,7 @@ Important behavior:
425
451
  `c2j ready` prints the number of currently ready recipe jobs for one tenant:
426
452
 
427
453
  ```bash
428
- c2j ready --tenant-id <tenant-id> --swf-url http://localhost:9047
454
+ c2j ready --jobdb http://localhost:9047/<tenant-id>
429
455
  ```
430
456
 
431
457
  `c2j run any` atomically polls for one available item of recipe work, leases it,
@@ -433,23 +459,23 @@ runs it, and exits. If no lease is available, it exits successfully after
433
459
  printing `no jobs found`.
434
460
 
435
461
  ```bash
436
- c2j run any --tenant-id <tenant-id> --swf-url http://localhost:9047
462
+ c2j run any --jobdb http://localhost:9047/<tenant-id>
437
463
  ```
438
464
 
439
465
  These can be composed by an external scheduler:
440
466
 
441
467
  ```bash
442
- count=$(c2j ready --tenant-id <tenant-id> --swf-url http://localhost:9047)
468
+ count=$(c2j ready --jobdb http://localhost:9047/<tenant-id>)
443
469
  if [ "$count" -gt 0 ]; then
444
470
  for _ in $(seq 1 "$count"); do
445
- c2j run any --tenant-id <tenant-id> --swf-url http://localhost:9047 &
471
+ c2j run any --jobdb http://localhost:9047/<tenant-id> &
446
472
  done
447
473
  wait
448
474
  fi
449
475
  ```
450
476
 
451
477
  `ready` is only a non-mutating snapshot and can become stale under competing
452
- workers. `run any` uses SWF polling so finding available work and acquiring the
478
+ workers. `run any` uses JobDB polling so finding available work and acquiring the
453
479
  lease happen in one runtime operation.
454
480
 
455
481
  ## Listing Jobs
@@ -490,20 +516,35 @@ Useful filters:
490
516
  - `--page-token`
491
517
  - `--all`
492
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
+
493
533
  ## Embedded Runtime
494
534
 
495
535
  `--embed` is shorthand for:
496
536
 
497
537
  ```bash
498
- --swf-url embed:///
538
+ --jobdb embed:///
499
539
  ```
500
540
 
501
- Use it when you want a local self-contained runtime instead of an external SWF server.
541
+ Use it when you want a local self-contained runtime instead of a remote JobDB server.
502
542
 
503
543
  Behavior:
504
544
 
505
545
  - starts embedded Postgres and Strata as needed
506
546
  - uses a persistent runtime root on disk
547
+ - always uses tenant `0`
507
548
  - works well for local recipe authoring and debugging
508
549
 
509
550
  Defaults:
@@ -511,41 +552,39 @@ Defaults:
511
552
  - runtime URL: `embed:///`
512
553
  - runtime root: `~/.c2j/embed/default`
513
554
 
514
- You can override the root with:
515
-
516
- ```bash
517
- export C2J_EMBED_ROOT=/absolute/path/to/embed-root
518
- ```
519
-
520
555
  Notes:
521
556
 
522
- - `C2J_EMBED_ROOT` must be an absolute path
523
557
  - only one `c2j` process can own a given embedded runtime root at a time
524
- - if you need parallel embedded runtimes, give each process a different `C2J_EMBED_ROOT`
525
558
 
526
- ## Runtime and Tenant Defaults
559
+ ## JobDB Configuration
527
560
 
528
561
  `c2j` reads these environment variables:
529
562
 
530
- - `C2J_SWF_URL`
531
- - `C2J_TENANT_ID`
532
- - `C2J_EMBED_ROOT`
563
+ - `C2J_JOBDB`
533
564
 
534
- Built-in defaults:
565
+ JobDB URI forms:
535
566
 
536
- - `C2J_SWF_URL`: `http://localhost:9047`
537
- - `C2J_TENANT_ID`: `0`
567
+ - `https://jobdb.example.com/<tenant-id>`
568
+ - `http://localhost:9047/<tenant-id>`
569
+ - `embed:///`
538
570
 
539
571
  Examples:
540
572
 
541
573
  ```bash
542
- export C2J_SWF_URL=http://localhost:9047
543
- export C2J_TENANT_ID=123
574
+ export C2J_JOBDB=http://localhost:9047/my-tenant
544
575
  ```
545
576
 
546
- When `--embed` is present, it overrides `C2J_SWF_URL` for that command.
547
- `c2j run loop` is the exception: it does not support `--embed` and rejects
548
- `C2J_SWF_URL=embed:///`.
577
+ `--jobdb` overrides `C2J_JOBDB` for that command. Project config may also define:
578
+
579
+ ```yaml
580
+ jobdb: https://jobdb.example.com/my-tenant
581
+ ```
582
+
583
+ For local embedded mode in project config:
584
+
585
+ ```yaml
586
+ jobdb: embed:///
587
+ ```
549
588
 
550
589
  ## Common Workflows
551
590
 
@@ -568,7 +607,7 @@ c2j run --job-id <job-id> --embed
568
607
  ```bash
569
608
  c2j submit \
570
609
  --recipe-file ./recipes/my-recipe.yaml \
571
- --swf-url http://localhost:9047 \
610
+ --jobdb http://localhost:9047/my-tenant \
572
611
  --run
573
612
  ```
574
613
 
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,82 +1,19 @@
1
1
  {
2
2
  "name": "@colony2/c2j",
3
- "type": "module",
4
- "version": "0.0.41",
3
+ "version": "0.0.51",
5
4
  "description": "c2j job system",
6
- "scripts": {
7
- "postinstall": "node install.js",
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
- "homepage": "https://github.com/colony-2/c2j",
23
- "bin": {
24
- "c2j": "run-c2j.js"
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.41_Darwin_arm64.tar.gz",
34
- "url": "https://github.com/colony-2/c2j-release/releases/download/v0.0.41/c2j_0.0.41_Darwin_arm64.tar.gz",
35
- "bins": [
36
- "c2j"
37
- ],
38
- "format": "tar.gz",
39
- "checksum": {
40
- "algorithm": "sha256",
41
- "digest": "13de517cad797c93023be1d7f03d191432fe2a3167fd614e67d0af37f666435b"
42
- }
43
- },
44
- "darwin-x64": {
45
- "name": "c2j_0.0.41_Darwin_x86_64.tar.gz",
46
- "url": "https://github.com/colony-2/c2j-release/releases/download/v0.0.41/c2j_0.0.41_Darwin_x86_64.tar.gz",
47
- "bins": [
48
- "c2j"
49
- ],
50
- "format": "tar.gz",
51
- "checksum": {
52
- "algorithm": "sha256",
53
- "digest": "167d6ef9e7cfd2fb03fa132cecbec52d25fcaa2b5d4fc6a46d767f10e1895362"
54
- }
55
- },
56
- "linux-arm64": {
57
- "name": "c2j_0.0.41_Linux_arm64.tar.gz",
58
- "url": "https://github.com/colony-2/c2j-release/releases/download/v0.0.41/c2j_0.0.41_Linux_arm64.tar.gz",
59
- "bins": [
60
- "c2j"
61
- ],
62
- "format": "tar.gz",
63
- "checksum": {
64
- "algorithm": "sha256",
65
- "digest": "0c9f35b66643466f5d4919e209c8b0b49feca80f87de6b9ea579ed781392dd5a"
66
- }
67
- },
68
- "linux-x64": {
69
- "name": "c2j_0.0.41_Linux_x86_64.tar.gz",
70
- "url": "https://github.com/colony-2/c2j-release/releases/download/v0.0.41/c2j_0.0.41_Linux_x86_64.tar.gz",
71
- "bins": [
72
- "c2j"
73
- ],
74
- "format": "tar.gz",
75
- "checksum": {
76
- "algorithm": "sha256",
77
- "digest": "e87a96e1bdf4c26fdd818b6a40a3b1edcc20ffe1137dec6afda4b88729c92897"
78
- }
79
- }
14
+ "author": {
15
+ "name": "Colony 2",
16
+ "email": "noreply@colony2.com"
80
17
  },
81
18
  "keywords": [
82
19
  "sandbox",
@@ -86,7 +23,28 @@
86
23
  "cli",
87
24
  "security"
88
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
+ ],
89
40
  "engines": {
90
- "node": "\u003e=14.0.0"
91
- }
92
- }
41
+ "node": ">=14.0.0"
42
+ },
43
+ "files": [
44
+ "bin/",
45
+ "scripts/",
46
+ "vendor/",
47
+ "README.md",
48
+ "LICENSE"
49
+ ]
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
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // This file was generated by GoReleaser. DO NOT EDIT.
4
-
5
- import { install } from "./lib.js";
6
- install();
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
- };
package/run-c2j.js DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // This file was generated by GoReleaser. DO NOT EDIT.
4
-
5
- import { run } from "./lib.js";
6
- run("c2j").then((code) => process.exit(code));