@workrail/cli 0.5.10 → 0.6.1
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 +20 -12
- package/bin/workrail.js +36 -38
- package/lib/integrity.cjs +59 -0
- package/package.json +4 -1
- package/release-checksums.txt +10 -0
- package/scripts/postinstall.mjs +61 -60
- package/scripts/prepare-release.mjs +53 -0
package/README.md
CHANGED
|
@@ -36,43 +36,43 @@ The CLI automatically downloads the correct binary for your platform during inst
|
|
|
36
36
|
|
|
37
37
|
## Quick Start
|
|
38
38
|
|
|
39
|
-
1. **
|
|
39
|
+
1. **Run guided setup**:
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
workrail
|
|
42
|
+
workrail setup
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
On a new installation, the same guided flow starts automatically in an
|
|
46
|
+
interactive terminal. It connects your account, links repositories to your
|
|
47
|
+
Default Project, offers optional GitHub context and autosync, and runs your
|
|
48
|
+
first sync.
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
workrail link
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
3. **Sync your work** (captures commit metadata, not source code):
|
|
50
|
+
2. **Sync your work** (captures metadata, not source code):
|
|
52
51
|
|
|
53
52
|
```bash
|
|
54
53
|
workrail sync
|
|
55
54
|
```
|
|
56
55
|
|
|
57
|
-
|
|
56
|
+
3. **Create an entry manually** (for non-code achievements):
|
|
58
57
|
|
|
59
58
|
```bash
|
|
60
59
|
workrail entry add
|
|
61
60
|
```
|
|
62
61
|
|
|
63
|
-
|
|
62
|
+
4. **Enable autosync** (optional - runs sync on a schedule):
|
|
64
63
|
|
|
65
64
|
```bash
|
|
66
65
|
workrail autosync enable
|
|
67
66
|
```
|
|
68
67
|
|
|
69
|
-
|
|
68
|
+
5. **View your dashboard** at [workrail.dev](https://workrail.dev)
|
|
70
69
|
|
|
71
70
|
## Common Commands
|
|
72
71
|
|
|
73
72
|
| Command | Description |
|
|
74
73
|
| --------------------------- | --------------------------------------- |
|
|
75
74
|
| `workrail auth login` | Authenticate via browser OAuth |
|
|
75
|
+
| `workrail setup` | Run or revisit guided onboarding |
|
|
76
76
|
| `workrail auth status` | Check authentication status |
|
|
77
77
|
| `workrail link` | Link current repo to a Workrail project |
|
|
78
78
|
| `workrail sync` | Sync commits and create entries |
|
|
@@ -86,7 +86,15 @@ The CLI automatically downloads the correct binary for your platform during inst
|
|
|
86
86
|
|
|
87
87
|
## Privacy
|
|
88
88
|
|
|
89
|
-
Workrail syncs **metadata only** (commit hashes, timestamps, messages). Your
|
|
89
|
+
Workrail syncs **metadata only** (commit hashes, timestamps, messages). Your
|
|
90
|
+
source code never leaves your machine.
|
|
91
|
+
|
|
92
|
+
Optional GitHub context uses your locally authenticated GitHub CLI to add
|
|
93
|
+
repository-scoped PR and issue titles, descriptions, labels, dates, links, and
|
|
94
|
+
deterministic commit relationships. It does not request source, diffs, comments,
|
|
95
|
+
reviews, checks, or authentication tokens.
|
|
96
|
+
|
|
97
|
+
[Read more about our privacy practices](https://workrail.dev/privacy).
|
|
90
98
|
|
|
91
99
|
## Updating
|
|
92
100
|
|
package/bin/workrail.js
CHANGED
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
const fs = require("node:fs");
|
|
4
4
|
const path = require("node:path");
|
|
5
5
|
const https = require("node:https");
|
|
6
|
-
const crypto = require("node:crypto");
|
|
7
6
|
const { spawn } = require("node:child_process");
|
|
7
|
+
const {
|
|
8
|
+
loadPackagedChecksums,
|
|
9
|
+
verifyArtifact,
|
|
10
|
+
} = require("../lib/integrity.cjs");
|
|
8
11
|
|
|
9
12
|
function resolveBinaryName() {
|
|
10
13
|
const platform = process.platform;
|
|
@@ -19,6 +22,10 @@ function resolveBinaryName() {
|
|
|
19
22
|
return null;
|
|
20
23
|
}
|
|
21
24
|
|
|
25
|
+
function resolveHelperBinaryName(binaryName) {
|
|
26
|
+
return binaryName.replace(/^workrail-/, "workrail-gh-collector-");
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
const binName = resolveBinaryName();
|
|
23
30
|
if (!binName) {
|
|
24
31
|
console.error(`Unsupported platform: ${process.platform} ${process.arch}`);
|
|
@@ -26,6 +33,14 @@ if (!binName) {
|
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
const binPath = path.join(__dirname, "native", binName);
|
|
36
|
+
const helperName = resolveHelperBinaryName(binName);
|
|
37
|
+
const helperPath = path.join(
|
|
38
|
+
__dirname,
|
|
39
|
+
"native",
|
|
40
|
+
process.platform === "win32"
|
|
41
|
+
? "workrail-gh-collector.exe"
|
|
42
|
+
: "workrail-gh-collector",
|
|
43
|
+
);
|
|
29
44
|
|
|
30
45
|
function getPackageVersion() {
|
|
31
46
|
const pkgPath = path.join(__dirname, "..", "package.json");
|
|
@@ -39,24 +54,6 @@ function getPackageVersion() {
|
|
|
39
54
|
return null;
|
|
40
55
|
}
|
|
41
56
|
|
|
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
57
|
function download(url) {
|
|
61
58
|
return new Promise((resolve, reject) => {
|
|
62
59
|
https
|
|
@@ -82,8 +79,11 @@ function download(url) {
|
|
|
82
79
|
});
|
|
83
80
|
}
|
|
84
81
|
|
|
85
|
-
async function ensureBinaryInstalled(binaryPath, binaryName) {
|
|
86
|
-
if (fs.existsSync(binaryPath))
|
|
82
|
+
async function ensureBinaryInstalled(binaryPath, binaryName, checksums) {
|
|
83
|
+
if (fs.existsSync(binaryPath)) {
|
|
84
|
+
verifyArtifact(binaryPath, binaryName, checksums);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
87
|
|
|
88
88
|
const version = getPackageVersion();
|
|
89
89
|
if (!version) {
|
|
@@ -96,7 +96,6 @@ async function ensureBinaryInstalled(binaryPath, binaryName) {
|
|
|
96
96
|
process.env.WORKRAIL_BINARY_BASE_URL ||
|
|
97
97
|
`https://cli.workrail.dev/workrail/v${version}`;
|
|
98
98
|
const binUrl = `${baseUrl}/${binaryName}`;
|
|
99
|
-
const checksumsUrl = `${baseUrl}/checksums.txt`;
|
|
100
99
|
|
|
101
100
|
fs.mkdirSync(path.dirname(binaryPath), { recursive: true });
|
|
102
101
|
console.error(
|
|
@@ -104,27 +103,18 @@ async function ensureBinaryInstalled(binaryPath, binaryName) {
|
|
|
104
103
|
);
|
|
105
104
|
|
|
106
105
|
const binaryBytes = await download(binUrl);
|
|
107
|
-
|
|
106
|
+
const temporaryPath = `${binaryPath}.download`;
|
|
107
|
+
fs.writeFileSync(temporaryPath, binaryBytes, { mode: 0o600 });
|
|
108
108
|
|
|
109
109
|
try {
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
}
|
|
110
|
+
verifyArtifact(temporaryPath, binaryName, checksums);
|
|
111
|
+
fs.renameSync(temporaryPath, binaryPath);
|
|
122
112
|
if (process.platform !== "win32") {
|
|
123
113
|
fs.chmodSync(binaryPath, 0o755);
|
|
124
114
|
}
|
|
125
115
|
} catch (error) {
|
|
126
116
|
try {
|
|
127
|
-
fs.unlinkSync(
|
|
117
|
+
fs.unlinkSync(temporaryPath);
|
|
128
118
|
} catch {}
|
|
129
119
|
throw error;
|
|
130
120
|
}
|
|
@@ -140,7 +130,9 @@ function printInstallHelp() {
|
|
|
140
130
|
|
|
141
131
|
async function main() {
|
|
142
132
|
try {
|
|
143
|
-
|
|
133
|
+
const checksums = loadPackagedChecksums(path.join(__dirname, ".."));
|
|
134
|
+
await ensureBinaryInstalled(binPath, binName, checksums);
|
|
135
|
+
await ensureBinaryInstalled(helperPath, helperName, checksums);
|
|
144
136
|
} catch (error) {
|
|
145
137
|
console.error(
|
|
146
138
|
`[workrail] Failed to install native binary: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -154,7 +146,13 @@ async function main() {
|
|
|
154
146
|
process.exit(1);
|
|
155
147
|
}
|
|
156
148
|
|
|
157
|
-
const child = spawn(binPath, process.argv.slice(2), {
|
|
149
|
+
const child = spawn(binPath, process.argv.slice(2), {
|
|
150
|
+
stdio: "inherit",
|
|
151
|
+
env: {
|
|
152
|
+
...process.env,
|
|
153
|
+
PATH: `${path.dirname(helperPath)}${path.delimiter}${process.env.PATH || ""}`,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
158
156
|
|
|
159
157
|
child.on("error", (err) => {
|
|
160
158
|
console.error(`Failed to run Workrail binary: ${err.message}`);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const crypto = require("node:crypto");
|
|
2
|
+
const fs = require("node:fs");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
|
|
5
|
+
function parseChecksums(text) {
|
|
6
|
+
const checksums = new Map();
|
|
7
|
+
for (const [index, line] of text.split("\n").entries()) {
|
|
8
|
+
const trimmed = line.trim();
|
|
9
|
+
if (!trimmed) continue;
|
|
10
|
+
const match = trimmed.match(/^([a-fA-F0-9]{64})\s+\*?([^/\\]+)$/);
|
|
11
|
+
if (!match) {
|
|
12
|
+
throw new Error(`Invalid checksum manifest line ${index + 1}`);
|
|
13
|
+
}
|
|
14
|
+
checksums.set(match[2], match[1].toLowerCase());
|
|
15
|
+
}
|
|
16
|
+
if (checksums.size === 0) throw new Error("Checksum manifest is empty");
|
|
17
|
+
return checksums;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function loadPackagedChecksums(packageRoot) {
|
|
21
|
+
const manifestPath = path.join(packageRoot, "release-checksums.txt");
|
|
22
|
+
try {
|
|
23
|
+
return parseChecksums(fs.readFileSync(manifestPath, "utf8"));
|
|
24
|
+
} catch (error) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Trusted release checksum manifest is unavailable: ${error instanceof Error ? error.message : String(error)}`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function sha256File(filePath) {
|
|
32
|
+
const hash = crypto.createHash("sha256");
|
|
33
|
+
hash.update(fs.readFileSync(filePath));
|
|
34
|
+
return hash.digest("hex");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function verifyArtifact(filePath, artifactName, checksums) {
|
|
38
|
+
const expected = checksums.get(artifactName);
|
|
39
|
+
if (!expected) throw new Error(`No trusted checksum for ${artifactName}`);
|
|
40
|
+
const actual = sha256File(filePath);
|
|
41
|
+
if (actual !== expected) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Checksum mismatch for ${artifactName}. expected=${expected} actual=${actual}`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function verifyRequiredArtifacts(distRoot, artifactNames, checksums) {
|
|
49
|
+
for (const artifactName of artifactNames) {
|
|
50
|
+
verifyArtifact(path.join(distRoot, artifactName), artifactName, checksums);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
loadPackagedChecksums,
|
|
56
|
+
parseChecksums,
|
|
57
|
+
verifyArtifact,
|
|
58
|
+
verifyRequiredArtifacts,
|
|
59
|
+
};
|
package/package.json
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workrail/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Workrail CLI - Track your engineering work and build a career record",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"bin": {
|
|
7
7
|
"workrail": "bin/workrail.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
+
"prepack": "node ./scripts/prepare-release.mjs",
|
|
10
11
|
"postinstall": "node ./scripts/postinstall.mjs"
|
|
11
12
|
},
|
|
12
13
|
"files": [
|
|
13
14
|
"bin",
|
|
15
|
+
"lib",
|
|
14
16
|
"scripts",
|
|
17
|
+
"release-checksums.txt",
|
|
15
18
|
"README.md"
|
|
16
19
|
],
|
|
17
20
|
"publishConfig": {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
e780027df5fdf683bf4b5c31f08757febee1798e240a6b3b7f207015702d2ff0 workrail-darwin-arm64
|
|
2
|
+
a16c4bc401f4db140f14fdb4d5301a2a674c6c7cfcfb973cf1a26cf0b2e4e5d7 workrail-darwin-x64
|
|
3
|
+
41bb3e231f6d19a8f98e1218933e2293652585ea2a08d5b2375149fb488c70d5 workrail-gh-collector-darwin-arm64
|
|
4
|
+
cd83d1f8e41805aef6d9613dac9b5f4ff59e7c3d68eba983825b4f34e2a215d1 workrail-gh-collector-darwin-x64
|
|
5
|
+
6243c3a132512df846634511b90096f03e36cd2c9a5dc11c102290c41606650b workrail-gh-collector-linux-arm64
|
|
6
|
+
533ac74bee15efb6bb44b17238f62daac20444fcb31a47b8c453e437b51e378c workrail-gh-collector-linux-x64
|
|
7
|
+
40afea1437fbe3f032c03934ac4aee50db633d1d4d9d9eae08e5e52655fb6602 workrail-gh-collector-windows-x64.exe
|
|
8
|
+
7f77ef1eaeb422c6f283705df0c68155b9c935767cb8b328e8ebb3ea83227d64 workrail-linux-arm64
|
|
9
|
+
151c902f90a3cdf8711f588d8f8fd8fe399d41bfffdbfd24f60a603d3850b424 workrail-linux-x64
|
|
10
|
+
383e4b719bc2d3868d00da09f4e41647943d64f00fe5a80dc11a80086c265bd3 workrail-windows-x64.exe
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -1,16 +1,40 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
1
|
import fs from "node:fs";
|
|
3
2
|
import https from "node:https";
|
|
4
3
|
import path from "node:path";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
8
|
const __dirname = path.dirname(__filename);
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const {
|
|
11
|
+
loadPackagedChecksums,
|
|
12
|
+
verifyArtifact,
|
|
13
|
+
} = require("../lib/integrity.cjs");
|
|
9
14
|
|
|
10
15
|
const pkgPath = path.join(__dirname, "..", "package.json");
|
|
11
16
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
12
17
|
const version = pkg.version;
|
|
13
18
|
|
|
19
|
+
function isMonorepoSourceCheckout() {
|
|
20
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
21
|
+
const repositoryRoot = path.resolve(packageRoot, "../..");
|
|
22
|
+
try {
|
|
23
|
+
const repositoryPackage = JSON.parse(
|
|
24
|
+
fs.readFileSync(path.join(repositoryRoot, "package.json"), "utf8"),
|
|
25
|
+
);
|
|
26
|
+
return (
|
|
27
|
+
repositoryPackage.private === true &&
|
|
28
|
+
Array.isArray(repositoryPackage.workspaces) &&
|
|
29
|
+
repositoryPackage.workspaces.includes("packages/*") &&
|
|
30
|
+
fs.realpathSync(packageRoot) ===
|
|
31
|
+
path.join(fs.realpathSync(repositoryRoot), "packages", "cli-wrapper")
|
|
32
|
+
);
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
14
38
|
const baseUrl =
|
|
15
39
|
process.env.WORKRAIL_BINARY_BASE_URL ||
|
|
16
40
|
`https://cli.workrail.dev/workrail/v${version}`;
|
|
@@ -28,6 +52,10 @@ function resolveBinaryName() {
|
|
|
28
52
|
return null;
|
|
29
53
|
}
|
|
30
54
|
|
|
55
|
+
function resolveHelperBinaryName(binaryName) {
|
|
56
|
+
return binaryName.replace(/^workrail-/, "workrail-gh-collector-");
|
|
57
|
+
}
|
|
58
|
+
|
|
31
59
|
function downloadToFile(url, dest) {
|
|
32
60
|
return new Promise((resolve, reject) => {
|
|
33
61
|
const file = fs.createWriteStream(dest);
|
|
@@ -44,45 +72,14 @@ function downloadToFile(url, dest) {
|
|
|
44
72
|
});
|
|
45
73
|
}
|
|
46
74
|
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
reject(new Error(`Download failed (${res.statusCode}) for ${url}`));
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
res.setEncoding("utf8");
|
|
57
|
-
res.on("data", (chunk) => (data += chunk));
|
|
58
|
-
res.on("end", () => resolve(data));
|
|
59
|
-
})
|
|
60
|
-
.on("error", (err) => reject(err));
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function sha256File(filePath) {
|
|
65
|
-
const hash = crypto.createHash("sha256");
|
|
66
|
-
const data = fs.readFileSync(filePath);
|
|
67
|
-
hash.update(data);
|
|
68
|
-
return hash.digest("hex");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function parseChecksums(text) {
|
|
72
|
-
const map = new Map();
|
|
73
|
-
for (const line of text.split("\n")) {
|
|
74
|
-
const trimmed = line.trim();
|
|
75
|
-
if (!trimmed) continue;
|
|
76
|
-
const parts = trimmed.split(/\s+/);
|
|
77
|
-
if (parts.length < 2) continue;
|
|
78
|
-
const hash = parts[0];
|
|
79
|
-
const file = parts[parts.length - 1];
|
|
80
|
-
map.set(file, hash);
|
|
75
|
+
async function main() {
|
|
76
|
+
if (isMonorepoSourceCheckout()) {
|
|
77
|
+
console.log(
|
|
78
|
+
"[workrail] Source checkout detected; skipping native binary installation.",
|
|
79
|
+
);
|
|
80
|
+
return;
|
|
81
81
|
}
|
|
82
|
-
return map;
|
|
83
|
-
}
|
|
84
82
|
|
|
85
|
-
async function main() {
|
|
86
83
|
const binaryName = resolveBinaryName();
|
|
87
84
|
if (!binaryName) {
|
|
88
85
|
console.warn(
|
|
@@ -94,32 +91,36 @@ async function main() {
|
|
|
94
91
|
const nativeDir = path.join(__dirname, "..", "bin", "native");
|
|
95
92
|
fs.mkdirSync(nativeDir, { recursive: true });
|
|
96
93
|
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
94
|
+
const checksums = loadPackagedChecksums(path.join(__dirname, ".."));
|
|
95
|
+
const helperBinaryName = resolveHelperBinaryName(binaryName);
|
|
96
|
+
const artifacts = [
|
|
97
|
+
{ remoteName: binaryName, targetName: binaryName },
|
|
98
|
+
{
|
|
99
|
+
remoteName: helperBinaryName,
|
|
100
|
+
targetName:
|
|
101
|
+
process.platform === "win32"
|
|
102
|
+
? "workrail-gh-collector.exe"
|
|
103
|
+
: "workrail-gh-collector",
|
|
104
|
+
},
|
|
105
|
+
];
|
|
100
106
|
|
|
101
107
|
console.log(
|
|
102
108
|
`📦 Installing Workrail CLI for ${process.platform} (${process.arch})...`,
|
|
103
109
|
);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (process.platform !== "win32") {
|
|
122
|
-
fs.chmodSync(targetPath, 0o755);
|
|
110
|
+
for (const artifact of artifacts) {
|
|
111
|
+
const targetPath = path.join(nativeDir, artifact.targetName);
|
|
112
|
+
const temporaryPath = `${targetPath}.download`;
|
|
113
|
+
try {
|
|
114
|
+
await downloadToFile(`${baseUrl}/${artifact.remoteName}`, temporaryPath);
|
|
115
|
+
console.log(`🔒 Verifying ${artifact.remoteName}...`);
|
|
116
|
+
verifyArtifact(temporaryPath, artifact.remoteName, checksums);
|
|
117
|
+
fs.renameSync(temporaryPath, targetPath);
|
|
118
|
+
} finally {
|
|
119
|
+
if (fs.existsSync(temporaryPath)) fs.unlinkSync(temporaryPath);
|
|
120
|
+
}
|
|
121
|
+
if (process.platform !== "win32") {
|
|
122
|
+
fs.chmodSync(targetPath, 0o755);
|
|
123
|
+
}
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
console.log("✅ Workrail CLI installed successfully!");
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const {
|
|
8
|
+
parseChecksums,
|
|
9
|
+
verifyRequiredArtifacts,
|
|
10
|
+
} = require("../lib/integrity.cjs");
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
13
|
+
const repoRoot = path.resolve(packageRoot, "../..");
|
|
14
|
+
|
|
15
|
+
const packageJson = JSON.parse(
|
|
16
|
+
fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"),
|
|
17
|
+
);
|
|
18
|
+
const denoJson = JSON.parse(
|
|
19
|
+
fs.readFileSync(path.join(repoRoot, "apps/cli/deno.json"), "utf8"),
|
|
20
|
+
);
|
|
21
|
+
if (packageJson.version !== denoJson.version) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`CLI version mismatch: npm=${packageJson.version} deno=${denoJson.version}`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const source = path.join(repoRoot, "apps/cli/dist/checksums.txt");
|
|
28
|
+
const contents = fs.readFileSync(source, "utf8");
|
|
29
|
+
const checksums = parseChecksums(contents);
|
|
30
|
+
const required = [
|
|
31
|
+
"workrail-darwin-arm64",
|
|
32
|
+
"workrail-darwin-x64",
|
|
33
|
+
"workrail-linux-arm64",
|
|
34
|
+
"workrail-linux-x64",
|
|
35
|
+
"workrail-windows-x64.exe",
|
|
36
|
+
"workrail-gh-collector-darwin-arm64",
|
|
37
|
+
"workrail-gh-collector-darwin-x64",
|
|
38
|
+
"workrail-gh-collector-linux-arm64",
|
|
39
|
+
"workrail-gh-collector-linux-x64",
|
|
40
|
+
"workrail-gh-collector-windows-x64.exe",
|
|
41
|
+
];
|
|
42
|
+
for (const artifact of required) {
|
|
43
|
+
if (!checksums.has(artifact))
|
|
44
|
+
throw new Error(`Missing checksum for ${artifact}`);
|
|
45
|
+
}
|
|
46
|
+
verifyRequiredArtifacts(path.dirname(source), required, checksums);
|
|
47
|
+
|
|
48
|
+
fs.writeFileSync(path.join(packageRoot, "release-checksums.txt"), contents, {
|
|
49
|
+
mode: 0o644,
|
|
50
|
+
});
|
|
51
|
+
console.log(
|
|
52
|
+
`Embedded trusted checksums for Workrail CLI v${packageJson.version}`,
|
|
53
|
+
);
|