@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.
- package/README.md +17 -0
- package/bin/agscale.js +136 -0
- 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
|
+
}
|