@rstf/cli 0.1.0-alpha.3

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/bin/rstf.js ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawnSync } = require("node:child_process");
4
+ const { existsSync, readFileSync } = require("node:fs");
5
+ const path = require("node:path");
6
+
7
+ const pkg = JSON.parse(readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
8
+ const localBinary = process.env.RSTF_CLI_LOCAL_BINARY;
9
+ const localSource = process.env.RSTF_CLI_LOCAL_SOURCE;
10
+ const installedBinary = path.join(__dirname, "..", "scripts", "vendor", process.platform, process.arch, "rstf");
11
+
12
+ if (localBinary) {
13
+ const result = spawnSync(path.resolve(localBinary), process.argv.slice(2), {
14
+ cwd: process.cwd(),
15
+ env: process.env,
16
+ stdio: "inherit",
17
+ });
18
+
19
+ process.exit(result.status === null ? 1 : result.status);
20
+ }
21
+
22
+ if (existsSync(installedBinary)) {
23
+ const result = spawnSync(installedBinary, process.argv.slice(2), {
24
+ cwd: process.cwd(),
25
+ env: process.env,
26
+ stdio: "inherit",
27
+ });
28
+
29
+ process.exit(result.status === null ? 1 : result.status);
30
+ }
31
+
32
+ const cliTarget = localSource
33
+ ? path.join(path.resolve(localSource), "cmd", "rstf")
34
+ : `${pkg.rstf.goPackage}@${pkg.rstf.goVersion}`;
35
+
36
+ const result = spawnSync("go", ["run", cliTarget, ...process.argv.slice(2)], {
37
+ cwd: process.cwd(),
38
+ env: process.env,
39
+ stdio: "inherit",
40
+ });
41
+
42
+ if (result.error && result.error.code === "ENOENT") {
43
+ console.error("rstf: Go 1.24 is required and `go` was not found on PATH.");
44
+ process.exit(1);
45
+ }
46
+
47
+ process.exit(result.status === null ? 1 : result.status);
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@rstf/cli",
3
+ "version": "0.1.0-alpha.3",
4
+ "description": "Project-local rstf CLI",
5
+ "homepage": "https://github.com/rafbgarcia/rstf#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/rafbgarcia/rstf/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/rafbgarcia/rstf.git"
12
+ },
13
+ "license": "ISC",
14
+ "author": "Rafael Garcia",
15
+ "type": "commonjs",
16
+ "bin": {
17
+ "rstf": "bin/rstf.js"
18
+ },
19
+ "files": [
20
+ "bin",
21
+ "scripts/install.js"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "scripts": {
27
+ "postinstall": "node ./scripts/install.js"
28
+ },
29
+ "engines": {
30
+ "node": ">=22"
31
+ },
32
+ "os": [
33
+ "darwin",
34
+ "linux"
35
+ ],
36
+ "cpu": [
37
+ "arm64",
38
+ "x64"
39
+ ],
40
+ "rstf": {
41
+ "goPackage": "github.com/rafbgarcia/rstf/cmd/rstf",
42
+ "goVersion": "v0.1.0-alpha.3",
43
+ "repo": "rafbgarcia/rstf"
44
+ }
45
+ }
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } = require("node:fs");
4
+ const { createHash } = require("node:crypto");
5
+ const path = require("node:path");
6
+
7
+ const supportedPlatforms = new Map([
8
+ ["darwin", "darwin"],
9
+ ["linux", "linux"],
10
+ ]);
11
+
12
+ const supportedArchitectures = new Map([
13
+ ["arm64", "arm64"],
14
+ ["x64", "amd64"],
15
+ ]);
16
+
17
+ function currentPackage(packageDir = path.join(__dirname, "..")) {
18
+ return JSON.parse(readFileSync(path.join(packageDir, "package.json"), "utf8"));
19
+ }
20
+
21
+ function normalizeTarget(platform = process.platform, arch = process.arch) {
22
+ const goos = supportedPlatforms.get(platform);
23
+ const goarch = supportedArchitectures.get(arch);
24
+ if (!goos || !goarch) {
25
+ throw new Error(`unsupported platform ${platform}/${arch}; rstf currently supports macOS and Linux on x64 and arm64`);
26
+ }
27
+ return { goos, goarch, platform, arch };
28
+ }
29
+
30
+ function releaseBaseURL(pkg, env = process.env) {
31
+ return env.RSTF_CLI_DOWNLOAD_BASE_URL || `https://github.com/${pkg.rstf.repo}/releases/download/${pkg.rstf.goVersion}`;
32
+ }
33
+
34
+ function checksumAssetName() {
35
+ return "rstf-checksums.txt";
36
+ }
37
+
38
+ function checksumSignatureAssetName() {
39
+ return "rstf-checksums.txt.sig";
40
+ }
41
+
42
+ function checksumCertificateAssetName() {
43
+ return "rstf-checksums.txt.pem";
44
+ }
45
+
46
+ function binaryAssetName(goos, goarch) {
47
+ return `rstf-${goos}-${goarch}`;
48
+ }
49
+
50
+ function vendorBinaryPath(platform = process.platform, arch = process.arch, scriptsDir = __dirname) {
51
+ return path.join(scriptsDir, "vendor", platform, arch, "rstf");
52
+ }
53
+
54
+ function sha256Hex(data) {
55
+ return createHash("sha256").update(data).digest("hex");
56
+ }
57
+
58
+ function parseChecksums(contents) {
59
+ const checksums = new Map();
60
+ for (const line of contents.split(/\r?\n/)) {
61
+ const trimmed = line.trim();
62
+ if (!trimmed) {
63
+ continue;
64
+ }
65
+ const match = trimmed.match(/^([a-f0-9]{64})\s+\*?(.+)$/i);
66
+ if (!match) {
67
+ throw new Error(`invalid checksum line: ${trimmed}`);
68
+ }
69
+ checksums.set(match[2], match[1].toLowerCase());
70
+ }
71
+ return checksums;
72
+ }
73
+
74
+ async function fetchBuffer(fetchImpl, url) {
75
+ const response = await fetchImpl(url);
76
+ if (!response.ok) {
77
+ throw new Error(`download failed from ${url}: ${response.status} ${response.statusText}`);
78
+ }
79
+ return Buffer.from(await response.arrayBuffer());
80
+ }
81
+
82
+ async function installBinary({
83
+ pkg = currentPackage(),
84
+ env = process.env,
85
+ fetchImpl = fetch,
86
+ platform = process.platform,
87
+ arch = process.arch,
88
+ scriptsDir = __dirname,
89
+ } = {}) {
90
+ if (env.RSTF_CLI_SKIP_INSTALL === "1" || env.RSTF_CLI_LOCAL_BINARY || env.RSTF_CLI_LOCAL_SOURCE) {
91
+ return null;
92
+ }
93
+
94
+ const target = normalizeTarget(platform, arch);
95
+ const assetName = binaryAssetName(target.goos, target.goarch);
96
+ const baseURL = releaseBaseURL(pkg, env);
97
+ const binaryURL = `${baseURL}/${assetName}`;
98
+ const checksumsURL = `${baseURL}/${checksumAssetName()}`;
99
+ const binaryPath = vendorBinaryPath(platform, arch, scriptsDir);
100
+
101
+ if (existsSync(binaryPath)) {
102
+ return binaryPath;
103
+ }
104
+
105
+ const vendorDir = path.dirname(binaryPath);
106
+ mkdirSync(vendorDir, { recursive: true });
107
+
108
+ const [checksumsData, binaryData] = await Promise.all([
109
+ fetchBuffer(fetchImpl, checksumsURL),
110
+ fetchBuffer(fetchImpl, binaryURL),
111
+ ]);
112
+
113
+ const checksums = parseChecksums(checksumsData.toString("utf8"));
114
+ const expectedDigest = checksums.get(assetName);
115
+ if (!expectedDigest) {
116
+ throw new Error(`checksum missing for ${assetName} in ${checksumsURL}`);
117
+ }
118
+
119
+ const actualDigest = sha256Hex(binaryData);
120
+ if (actualDigest !== expectedDigest) {
121
+ throw new Error(`checksum mismatch for ${assetName}: expected ${expectedDigest}, got ${actualDigest}`);
122
+ }
123
+
124
+ writeFileSync(binaryPath, binaryData);
125
+ chmodSync(binaryPath, 0o755);
126
+ return binaryPath;
127
+ }
128
+
129
+ async function main() {
130
+ await installBinary();
131
+ }
132
+
133
+ if (require.main === module) {
134
+ main().catch((error) => {
135
+ console.error(`rstf: failed to install CLI binary: ${error.message}`);
136
+ process.exit(1);
137
+ });
138
+ }
139
+
140
+ module.exports = {
141
+ binaryAssetName,
142
+ checksumAssetName,
143
+ checksumCertificateAssetName,
144
+ checksumSignatureAssetName,
145
+ currentPackage,
146
+ installBinary,
147
+ normalizeTarget,
148
+ parseChecksums,
149
+ releaseBaseURL,
150
+ sha256Hex,
151
+ vendorBinaryPath,
152
+ };