shellman 0.1.0

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 ADDED
@@ -0,0 +1,16 @@
1
+ # shellman
2
+
3
+ Global installer package for Shellman.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g shellman
9
+ ```
10
+
11
+ The installer downloads a prebuilt release package from GitHub Releases for:
12
+
13
+ - darwin-arm64
14
+ - darwin-amd64
15
+ - linux-arm64
16
+ - linux-amd64
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from "node:child_process";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { homedir, platform, arch } from "node:os";
6
+ import path from "node:path";
7
+ import process from "node:process";
8
+
9
+ function resolveTargetTriple() {
10
+ const os = platform();
11
+ const cpu = arch();
12
+
13
+ if ((os === "darwin" || os === "linux") && (cpu === "arm64" || cpu === "x64")) {
14
+ return `${os}-${cpu === "x64" ? "amd64" : "arm64"}`;
15
+ }
16
+
17
+ return "";
18
+ }
19
+
20
+ function resolveInstalledBinary() {
21
+ const triple = resolveTargetTriple();
22
+ if (!triple) {
23
+ console.error(`shellman: unsupported platform ${platform()}/${arch()} (only darwin/linux + arm64/x64 supported)`);
24
+ process.exit(1);
25
+ }
26
+
27
+ const pkgVersion = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
28
+ const installRoot = path.join(homedir(), ".shellman", "versions", `v${pkgVersion}`, triple, "bin");
29
+ const binPath = path.join(installRoot, "shellman");
30
+
31
+ if (!existsSync(binPath)) {
32
+ console.error("shellman: prebuilt binary is not installed. Reinstall package:");
33
+ console.error(" npm install -g shellman");
34
+ process.exit(1);
35
+ }
36
+
37
+ return binPath;
38
+ }
39
+
40
+ const bin = resolveInstalledBinary();
41
+ const child = spawn(bin, process.argv.slice(2), {
42
+ stdio: "inherit",
43
+ env: process.env,
44
+ });
45
+
46
+ child.on("exit", (code, signal) => {
47
+ if (signal) {
48
+ process.kill(process.pid, signal);
49
+ return;
50
+ }
51
+ process.exit(code ?? 1);
52
+ });
53
+
54
+ child.on("error", (error) => {
55
+ console.error(`shellman: failed to start binary: ${error.message}`);
56
+ process.exit(1);
57
+ });
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "shellman",
3
+ "version": "0.1.0",
4
+ "description": "Shellman CLI installer package",
5
+ "license": "AGPL-3.0",
6
+ "type": "module",
7
+ "bin": {
8
+ "shellman": "bin/shellman.js"
9
+ },
10
+ "files": [
11
+ "bin/**",
12
+ "scripts/**",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "postinstall": "node ./scripts/postinstall.js"
17
+ },
18
+ "dependencies": {
19
+ "unzipper": "^0.12.3"
20
+ }
21
+ }
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createHash } from "node:crypto";
4
+ import { createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
5
+ import { Readable } from "node:stream";
6
+ import { pipeline } from "node:stream/promises";
7
+ import { homedir, platform, arch } from "node:os";
8
+ import path from "node:path";
9
+ import process from "node:process";
10
+ import * as unzipper from "unzipper";
11
+
12
+ function resolveTargetTriple() {
13
+ const os = platform();
14
+ const cpu = arch();
15
+ if ((os === "darwin" || os === "linux") && (cpu === "arm64" || cpu === "x64")) {
16
+ return `${os}-${cpu === "x64" ? "amd64" : "arm64"}`;
17
+ }
18
+ return "";
19
+ }
20
+
21
+ function getPackageVersion() {
22
+ const packageJSON = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
23
+ return packageJSON.version;
24
+ }
25
+
26
+ function getRepo() {
27
+ const raw = (process.env.SHELLMAN_GITHUB_REPO || "cybersailor/shellman-project").trim();
28
+ return raw || "cybersailor/shellman-project";
29
+ }
30
+
31
+ function getDownloadBase(version) {
32
+ const customBase = (process.env.SHELLMAN_DOWNLOAD_BASE_URL || "").trim();
33
+ if (customBase) {
34
+ return customBase.replace(/\/+$/, "");
35
+ }
36
+ return `https://github.com/${getRepo()}/releases/download/v${version}`;
37
+ }
38
+
39
+ async function download(url, outFile) {
40
+ const res = await fetch(url);
41
+ if (!res.ok || !res.body) {
42
+ throw new Error(`download failed ${res.status} ${res.statusText}: ${url}`);
43
+ }
44
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(outFile));
45
+ }
46
+
47
+ function sha256File(filePath) {
48
+ const hash = createHash("sha256");
49
+ hash.update(readFileSync(filePath));
50
+ return hash.digest("hex");
51
+ }
52
+
53
+ function parseSHA256Sums(filePath) {
54
+ const lines = readFileSync(filePath, "utf8").split(/\r?\n/);
55
+ const out = new Map();
56
+ for (const line of lines) {
57
+ const trimmed = line.trim();
58
+ if (!trimmed) continue;
59
+ const m = trimmed.match(/^([a-fA-F0-9]{64})\s+\*?(.+)$/);
60
+ if (!m) continue;
61
+ out.set(m[2].trim(), m[1].toLowerCase());
62
+ }
63
+ return out;
64
+ }
65
+
66
+ async function main() {
67
+ if ((process.env.SHELLMAN_SKIP_POSTINSTALL || "").trim() === "1") {
68
+ console.log("shellman: skip postinstall by SHELLMAN_SKIP_POSTINSTALL=1");
69
+ return;
70
+ }
71
+
72
+ const triple = resolveTargetTriple();
73
+ if (!triple) {
74
+ console.error(`shellman: unsupported platform ${platform()}/${arch()} (only darwin/linux + arm64/x64 supported)`);
75
+ process.exit(1);
76
+ }
77
+
78
+ const version = getPackageVersion();
79
+ const assetName = `shellman-v${version}-${triple}.zip`;
80
+ const checksumsName = "SHA256SUMS";
81
+ const base = getDownloadBase(version);
82
+
83
+ const installRoot = path.join(homedir(), ".shellman", "versions", `v${version}`, triple);
84
+ const binPath = path.join(installRoot, "bin", "shellman");
85
+ if (existsSync(binPath)) {
86
+ console.log(`shellman: binary already installed at ${binPath}`);
87
+ return;
88
+ }
89
+
90
+ const tmpRoot = path.join(homedir(), ".shellman", "tmp", `v${version}-${triple}`);
91
+ rmSync(tmpRoot, { recursive: true, force: true });
92
+ mkdirSync(tmpRoot, { recursive: true });
93
+
94
+ const zipPath = path.join(tmpRoot, assetName);
95
+ const sumsPath = path.join(tmpRoot, checksumsName);
96
+ const zipURL = `${base}/${assetName}`;
97
+ const sumsURL = `${base}/${checksumsName}`;
98
+
99
+ console.log(`shellman: downloading ${zipURL}`);
100
+ await download(zipURL, zipPath);
101
+ await download(sumsURL, sumsPath);
102
+
103
+ const parsed = parseSHA256Sums(sumsPath);
104
+ const expected = parsed.get(assetName);
105
+ if (!expected) {
106
+ throw new Error(`missing checksum entry for ${assetName}`);
107
+ }
108
+ const actual = sha256File(zipPath);
109
+ if (actual !== expected) {
110
+ throw new Error(`checksum mismatch for ${assetName}`);
111
+ }
112
+
113
+ rmSync(installRoot, { recursive: true, force: true });
114
+ mkdirSync(installRoot, { recursive: true });
115
+ await pipeline(
116
+ createReadStream(zipPath),
117
+ unzipper.Extract({ path: installRoot }),
118
+ );
119
+
120
+ if (!existsSync(binPath)) {
121
+ throw new Error(`installed package missing binary: ${binPath}`);
122
+ }
123
+
124
+ console.log(`shellman: installed v${version} for ${triple}`);
125
+ }
126
+
127
+ main().catch((error) => {
128
+ console.error(`shellman: postinstall failed: ${error.message}`);
129
+ process.exit(1);
130
+ });