@agscale/agscale 0.1.2

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 (3) hide show
  1. package/README.md +17 -0
  2. package/bin/agscale.js +136 -0
  3. package/package.json +28 -0
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # @agscale/agscale
2
+
3
+ NPM wrapper for AGScale CLI.
4
+
5
+ It downloads the matching native binary from GitHub Releases on first run and executes it.
6
+
7
+ ## Usage
8
+
9
+ ```bash
10
+ npx @agscale/agscale version
11
+ ```
12
+
13
+ Override release source if needed:
14
+
15
+ ```bash
16
+ AGSCALE_REPO=yangpuyu/agscale AGSCALE_RELEASE_TAG=v0.3.1 npx @agscale/agscale version
17
+ ```
package/bin/agscale.js ADDED
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+ const crypto = require("node:crypto");
4
+ const fs = require("node:fs");
5
+ const os = require("node:os");
6
+ const path = require("node:path");
7
+ const https = require("node:https");
8
+ const { spawnSync } = require("node:child_process");
9
+
10
+ const PKG = require("../package.json");
11
+
12
+ function mapPlatform() {
13
+ const p = process.platform;
14
+ if (p === "linux") return "linux";
15
+ if (p === "darwin") return "darwin";
16
+ if (p === "win32") return "windows";
17
+ throw new Error(`unsupported platform: ${p}`);
18
+ }
19
+
20
+ function mapArch() {
21
+ const a = process.arch;
22
+ if (a === "x64") return "amd64";
23
+ if (a === "arm64") return "arm64";
24
+ throw new Error(`unsupported architecture: ${a}`);
25
+ }
26
+
27
+ function getTag() {
28
+ const envTag = process.env.AGSCALE_RELEASE_TAG;
29
+ if (envTag && envTag.trim()) return envTag.trim();
30
+ if (PKG.version === "0.0.0-dev") {
31
+ throw new Error("package version is 0.0.0-dev; set AGSCALE_RELEASE_TAG=vX.Y.Z");
32
+ }
33
+ return `v${PKG.version}`;
34
+ }
35
+
36
+ function getRepo() {
37
+ const envRepo = process.env.AGSCALE_REPO;
38
+ if (envRepo && envRepo.trim()) return envRepo.trim();
39
+ return "yangpuyu/agscale";
40
+ }
41
+
42
+ function request(url) {
43
+ return new Promise((resolve, reject) => {
44
+ const req = https.request(
45
+ url,
46
+ {
47
+ method: "GET",
48
+ headers: process.env.GITHUB_TOKEN
49
+ ? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` }
50
+ : {}
51
+ },
52
+ (res) => {
53
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
54
+ resolve(request(res.headers.location));
55
+ return;
56
+ }
57
+ if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
58
+ reject(new Error(`request failed ${res.statusCode || 0}: ${url}`));
59
+ return;
60
+ }
61
+ const chunks = [];
62
+ res.on("data", (c) => chunks.push(c));
63
+ res.on("end", () => resolve(Buffer.concat(chunks)));
64
+ }
65
+ );
66
+ req.on("error", reject);
67
+ req.end();
68
+ });
69
+ }
70
+
71
+ function sha256(buf) {
72
+ return crypto.createHash("sha256").update(buf).digest("hex");
73
+ }
74
+
75
+ function binaryFilename(tag, osName, arch) {
76
+ const base = `agscale_${tag}_${osName}_${arch}`;
77
+ if (osName === "windows") return `${base}.exe`;
78
+ return base;
79
+ }
80
+
81
+ async function ensureBinary() {
82
+ const repo = getRepo();
83
+ const tag = getTag();
84
+ const osName = mapPlatform();
85
+ const arch = mapArch();
86
+ const assetName = binaryFilename(tag, osName, arch);
87
+ const checksumsName = "checksums.txt";
88
+
89
+ const cacheRoot = process.env.AGSCALE_CACHE_DIR
90
+ ? process.env.AGSCALE_CACHE_DIR
91
+ : path.join(os.homedir(), ".cache", "agscale-npm");
92
+ const cacheDir = path.join(cacheRoot, tag, `${osName}-${arch}`);
93
+ const binPath = path.join(cacheDir, osName === "windows" ? "agscale.exe" : "agscale");
94
+ if (fs.existsSync(binPath)) return binPath;
95
+
96
+ fs.mkdirSync(cacheDir, { recursive: true });
97
+
98
+ const base = `https://github.com/${repo}/releases/download/${tag}`;
99
+ const binURL = `${base}/${assetName}`;
100
+ const checksumsURL = `${base}/${checksumsName}`;
101
+
102
+ const [binBuf, checksumsBuf] = await Promise.all([request(binURL), request(checksumsURL)]);
103
+ const checksumsText = checksumsBuf.toString("utf8");
104
+ const expected = checksumsText
105
+ .split("\n")
106
+ .map((l) => l.trim())
107
+ .filter(Boolean)
108
+ .map((l) => l.split(/\s+/))
109
+ .find((parts) => parts.length >= 2 && parts[1] === assetName);
110
+ if (!expected) {
111
+ throw new Error(`checksum entry missing for asset ${assetName}`);
112
+ }
113
+ const actual = sha256(binBuf);
114
+ if (actual !== expected[0]) {
115
+ throw new Error(`checksum mismatch for ${assetName}`);
116
+ }
117
+
118
+ fs.writeFileSync(binPath, binBuf, { mode: 0o755 });
119
+ fs.chmodSync(binPath, 0o755);
120
+ return binPath;
121
+ }
122
+
123
+ async function main() {
124
+ const args = process.argv.slice(2);
125
+ try {
126
+ const bin = await ensureBinary();
127
+ const child = spawnSync(bin, args, { stdio: "inherit" });
128
+ if (child.error) throw child.error;
129
+ process.exit(child.status || 0);
130
+ } catch (err) {
131
+ console.error(`[agscale npm wrapper] ${err.message}`);
132
+ process.exit(1);
133
+ }
134
+ }
135
+
136
+ main();
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@agscale/agscale",
3
+ "version": "0.1.2",
4
+ "description": "AGScale CLI npm wrapper (downloads native binary from GitHub Releases)",
5
+ "license": "MIT",
6
+ "private": false,
7
+ "type": "commonjs",
8
+ "bin": {
9
+ "agscale": "bin/agscale.js"
10
+ },
11
+ "files": [
12
+ "bin",
13
+ "README.md"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/yangpuyu/agscale.git"
21
+ },
22
+ "keywords": [
23
+ "agscale",
24
+ "agent",
25
+ "kubernetes",
26
+ "cli"
27
+ ]
28
+ }