@workrail/cli 0.5.5 → 0.5.7

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.
Files changed (2) hide show
  1. package/bin/workrail.js +138 -12
  2. package/package.json +1 -1
package/bin/workrail.js CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  const fs = require("node:fs");
4
4
  const path = require("node:path");
5
+ const https = require("node:https");
6
+ const crypto = require("node:crypto");
5
7
  const { spawn } = require("node:child_process");
6
8
 
7
9
  function resolveBinaryName() {
@@ -25,20 +27,144 @@ if (!binName) {
25
27
 
26
28
  const binPath = path.join(__dirname, "native", binName);
27
29
 
28
- if (!fs.existsSync(binPath)) {
29
- console.error("Workrail binary is not installed. Try reinstalling:");
30
+ function getPackageVersion() {
31
+ const pkgPath = path.join(__dirname, "..", "package.json");
32
+ try {
33
+ const raw = fs.readFileSync(pkgPath, "utf8");
34
+ const parsed = JSON.parse(raw);
35
+ if (typeof parsed.version === "string" && parsed.version.trim()) {
36
+ return parsed.version.trim();
37
+ }
38
+ } catch {}
39
+ return null;
40
+ }
41
+
42
+ function sha256File(filePath) {
43
+ const hash = crypto.createHash("sha256");
44
+ hash.update(fs.readFileSync(filePath));
45
+ return hash.digest("hex");
46
+ }
47
+
48
+ function parseChecksums(text) {
49
+ const map = new Map();
50
+ for (const line of text.split("\n")) {
51
+ const trimmed = line.trim();
52
+ if (!trimmed) continue;
53
+ const parts = trimmed.split(/\s+/);
54
+ if (parts.length < 2) continue;
55
+ map.set(parts[parts.length - 1], parts[0]);
56
+ }
57
+ return map;
58
+ }
59
+
60
+ function download(url) {
61
+ return new Promise((resolve, reject) => {
62
+ https
63
+ .get(url, (res) => {
64
+ if (
65
+ res.statusCode &&
66
+ res.statusCode >= 300 &&
67
+ res.statusCode < 400 &&
68
+ typeof res.headers.location === "string"
69
+ ) {
70
+ resolve(download(new URL(res.headers.location, url).toString()));
71
+ return;
72
+ }
73
+ if (res.statusCode !== 200) {
74
+ reject(new Error(`Download failed (${res.statusCode}) for ${url}`));
75
+ return;
76
+ }
77
+ const chunks = [];
78
+ res.on("data", (chunk) => chunks.push(chunk));
79
+ res.on("end", () => resolve(Buffer.concat(chunks)));
80
+ })
81
+ .on("error", (err) => reject(err));
82
+ });
83
+ }
84
+
85
+ async function ensureBinaryInstalled(binaryPath, binaryName) {
86
+ if (fs.existsSync(binaryPath)) return;
87
+
88
+ const version = getPackageVersion();
89
+ if (!version) {
90
+ throw new Error(
91
+ "Unable to resolve @workrail/cli version for binary download",
92
+ );
93
+ }
94
+
95
+ const baseUrl =
96
+ process.env.WORKRAIL_BINARY_BASE_URL ||
97
+ `https://cli.workrail.dev/workrail/v${version}`;
98
+ const binUrl = `${baseUrl}/${binaryName}`;
99
+ const checksumsUrl = `${baseUrl}/checksums.txt`;
100
+
101
+ fs.mkdirSync(path.dirname(binaryPath), { recursive: true });
102
+ console.error(
103
+ `[workrail] Native binary missing, downloading ${binaryName}...`,
104
+ );
105
+
106
+ const binaryBytes = await download(binUrl);
107
+ fs.writeFileSync(binaryPath, binaryBytes);
108
+
109
+ try {
110
+ const checksumsText = (await download(checksumsUrl)).toString("utf8");
111
+ const checksums = parseChecksums(checksumsText);
112
+ const expected = checksums.get(binaryName);
113
+ if (!expected) {
114
+ throw new Error(`No checksum entry found for ${binaryName}`);
115
+ }
116
+ const actual = sha256File(binaryPath);
117
+ if (actual !== expected) {
118
+ throw new Error(
119
+ `Checksum mismatch for ${binaryName}. expected=${expected} actual=${actual}`,
120
+ );
121
+ }
122
+ if (process.platform !== "win32") {
123
+ fs.chmodSync(binaryPath, 0o755);
124
+ }
125
+ } catch (error) {
126
+ try {
127
+ fs.unlinkSync(binaryPath);
128
+ } catch {}
129
+ throw error;
130
+ }
131
+ }
132
+
133
+ function printInstallHelp() {
134
+ console.error("Workrail binary is not installed.");
135
+ console.error("Try reinstalling with one of:");
30
136
  console.error(" npm i -g @workrail/cli");
31
- process.exit(1);
137
+ console.error(" pnpm add -g @workrail/cli");
138
+ console.error(" bun add -g @workrail/cli");
32
139
  }
33
140
 
34
- const child = spawn(binPath, process.argv.slice(2), { stdio: "inherit" });
141
+ async function main() {
142
+ try {
143
+ await ensureBinaryInstalled(binPath, binName);
144
+ } catch (error) {
145
+ console.error(
146
+ `[workrail] Failed to install native binary: ${error instanceof Error ? error.message : String(error)}`,
147
+ );
148
+ printInstallHelp();
149
+ process.exit(1);
150
+ }
35
151
 
36
- child.on("error", (err) => {
37
- console.error(`Failed to run Workrail binary: ${err.message}`);
38
- process.exit(1);
39
- });
152
+ if (!fs.existsSync(binPath)) {
153
+ printInstallHelp();
154
+ process.exit(1);
155
+ }
156
+
157
+ const child = spawn(binPath, process.argv.slice(2), { stdio: "inherit" });
158
+
159
+ child.on("error", (err) => {
160
+ console.error(`Failed to run Workrail binary: ${err.message}`);
161
+ process.exit(1);
162
+ });
163
+
164
+ child.on("exit", (code, signal) => {
165
+ if (signal) process.exit(1);
166
+ process.exit(code ?? 1);
167
+ });
168
+ }
40
169
 
41
- child.on("exit", (code, signal) => {
42
- if (signal) process.exit(1);
43
- process.exit(code ?? 1);
44
- });
170
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workrail/cli",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
4
4
  "description": "Workrail CLI - Track your engineering work and build a career record",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {