manzanasd-client 0.7.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 +27 -0
- package/bin/manzanas.js +19 -0
- package/install.js +124 -0
- package/lib/platform.js +42 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# manzanasd-client
|
|
2
|
+
|
|
3
|
+
Thin npm wrapper for the [`manzanas`](https://github.com/BariBariGood/manzanas)
|
|
4
|
+
CLI. On install it downloads the matching native `manzanas` binary from the
|
|
5
|
+
GitHub Release whose version equals the package version (esbuild-style),
|
|
6
|
+
and `manzanas` on your PATH proxies to it.
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
npx manzanasd-client --version
|
|
10
|
+
# or
|
|
11
|
+
npm i -g manzanasd-client && manzanas --version
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Supported platforms: darwin/linux × x64/arm64.
|
|
15
|
+
|
|
16
|
+
For local development against an unreleased binary:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
MANZANAS_BINARY_PATH=/path/to/manzanas npm install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
If you install from a private checkout, set `GITHUB_TOKEN` (or `GH_TOKEN`) with
|
|
23
|
+
release read access so the postinstall download is authorized.
|
|
24
|
+
|
|
25
|
+
Not yet published to npm, and the postinstall needs a published GitHub
|
|
26
|
+
Release matching the package version; until one exists, use
|
|
27
|
+
`MANZANAS_BINARY_PATH` as above and verify packaging with `npm pack`.
|
package/bin/manzanas.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// Proxy all args to the downloaded native manzanas binary.
|
|
5
|
+
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const { spawnSync } = require("child_process");
|
|
8
|
+
const { binaryPath } = require("../lib/platform");
|
|
9
|
+
|
|
10
|
+
const bin = process.env.MANZANAS_BINARY_PATH || binaryPath();
|
|
11
|
+
if (!fs.existsSync(bin)) {
|
|
12
|
+
console.error(
|
|
13
|
+
"manzanasd-client: manzanas binary missing; re-run `npm install` (postinstall downloads it)."
|
|
14
|
+
);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const res = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
|
|
19
|
+
process.exit(res.status === null ? 1 : res.status);
|
package/install.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Postinstall: download the matching manzanas release tarball and extract
|
|
4
|
+
// the `manzanas` binary into bin-native/ (esbuild-style native binary
|
|
5
|
+
// distribution). Set MANZANAS_BINARY_PATH to skip the download and use a
|
|
6
|
+
// locally-built binary instead. While the repo is private, set
|
|
7
|
+
// GITHUB_TOKEN (or GH_TOKEN) so the release download is authorized.
|
|
8
|
+
|
|
9
|
+
const fs = require("fs");
|
|
10
|
+
const path = require("path");
|
|
11
|
+
const zlib = require("zlib");
|
|
12
|
+
const crypto = require("crypto");
|
|
13
|
+
const { execFileSync } = require("child_process");
|
|
14
|
+
const {
|
|
15
|
+
VERSION,
|
|
16
|
+
REPO,
|
|
17
|
+
tarballURL,
|
|
18
|
+
checksumsURL,
|
|
19
|
+
binaryPath,
|
|
20
|
+
} = require("./lib/platform");
|
|
21
|
+
|
|
22
|
+
const TOKEN = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
|
23
|
+
|
|
24
|
+
async function download(url, headers) {
|
|
25
|
+
const res = await fetch(url, { redirect: "follow", headers: headers || {} });
|
|
26
|
+
if (!res.ok) {
|
|
27
|
+
let hint = "";
|
|
28
|
+
if (res.status === 404) {
|
|
29
|
+
hint = TOKEN
|
|
30
|
+
? " (no release matching this package version, or the token lacks access)"
|
|
31
|
+
: " (404 can mean the release tag does not exist yet, or the repo is" +
|
|
32
|
+
" private — set GITHUB_TOKEN/GH_TOKEN to authorize the download)";
|
|
33
|
+
}
|
|
34
|
+
throw new Error(
|
|
35
|
+
`download failed: ${res.status} ${res.statusText} for ${url}${hint}`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return Buffer.from(await res.arrayBuffer());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Private repos don't honor token auth on browser download URLs; resolve
|
|
42
|
+
// asset ids via the REST API and download through the assets endpoint.
|
|
43
|
+
async function downloadAsset(name) {
|
|
44
|
+
const relURL = `https://api.github.com/repos/${REPO}/releases/tags/v${VERSION}`;
|
|
45
|
+
const auth = { Authorization: `Bearer ${TOKEN}` };
|
|
46
|
+
const rel = JSON.parse(
|
|
47
|
+
(await download(relURL, { ...auth, Accept: "application/vnd.github+json" })).toString()
|
|
48
|
+
);
|
|
49
|
+
const asset = (rel.assets || []).find((a) => a.name === name);
|
|
50
|
+
if (!asset) throw new Error(`asset ${name} not found in release v${VERSION}`);
|
|
51
|
+
return download(asset.url, { ...auth, Accept: "application/octet-stream" });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function fetchReleaseFile(name, publicURL) {
|
|
55
|
+
return TOKEN ? downloadAsset(name) : download(publicURL);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function extractManzanas(tgz, dest) {
|
|
59
|
+
// Minimal tar reader: find the `manzanas` entry in the gunzipped stream.
|
|
60
|
+
const tar = zlib.gunzipSync(tgz);
|
|
61
|
+
let off = 0;
|
|
62
|
+
while (off + 512 <= tar.length) {
|
|
63
|
+
const name = tar.subarray(off, off + 100).toString().replace(/\0.*$/, "");
|
|
64
|
+
if (!name) break;
|
|
65
|
+
const size = parseInt(
|
|
66
|
+
tar.subarray(off + 124, off + 136).toString().replace(/\0.*$/, "").trim(),
|
|
67
|
+
8
|
|
68
|
+
);
|
|
69
|
+
const typeflag = tar[off + 156];
|
|
70
|
+
const isFile = typeflag === 0x30 || typeflag === 0; // '0' or NUL: regular file
|
|
71
|
+
const dataStart = off + 512;
|
|
72
|
+
if (isFile && (name === "manzanas" || name.endsWith("/manzanas"))) {
|
|
73
|
+
fs.writeFileSync(dest, tar.subarray(dataStart, dataStart + size), {
|
|
74
|
+
mode: 0o755,
|
|
75
|
+
});
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
off = dataStart + Math.ceil(size / 512) * 512;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function main() {
|
|
84
|
+
const dest = binaryPath();
|
|
85
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
86
|
+
|
|
87
|
+
const local = process.env.MANZANAS_BINARY_PATH;
|
|
88
|
+
if (local) {
|
|
89
|
+
fs.copyFileSync(local, dest);
|
|
90
|
+
fs.chmodSync(dest, 0o755);
|
|
91
|
+
console.log(`manzanasd-client: using local binary ${local}`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const url = tarballURL();
|
|
96
|
+
const name = url.split("/").pop();
|
|
97
|
+
console.log(`manzanasd-client: downloading ${url}`);
|
|
98
|
+
const tgz = await fetchReleaseFile(name, url);
|
|
99
|
+
|
|
100
|
+
// Verify against the release's published checksums.txt.
|
|
101
|
+
const checksums = (
|
|
102
|
+
await fetchReleaseFile("checksums.txt", checksumsURL())
|
|
103
|
+
).toString();
|
|
104
|
+
const line = checksums.split("\n").find((l) => l.trim().endsWith(` ${name}`) || l.trim().endsWith(` ${name}`));
|
|
105
|
+
if (!line) throw new Error(`no checksum for ${name} in checksums.txt`);
|
|
106
|
+
const expected = line.trim().split(/\s+/)[0];
|
|
107
|
+
const actual = crypto.createHash("sha256").update(tgz).digest("hex");
|
|
108
|
+
if (actual !== expected) {
|
|
109
|
+
throw new Error(`sha256 mismatch for ${name}: expected ${expected}, got ${actual}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!extractManzanas(tgz, dest)) {
|
|
113
|
+
throw new Error("manzanas binary not found in release tarball");
|
|
114
|
+
}
|
|
115
|
+
execFileSync(dest, ["--version"], { stdio: "inherit" });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
main().catch((err) => {
|
|
119
|
+
console.error(String(err.message || err));
|
|
120
|
+
console.error(
|
|
121
|
+
"manzanasd-client: install failed. Set MANZANAS_BINARY_PATH to a local manzanas binary to skip the download."
|
|
122
|
+
);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
});
|
package/lib/platform.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
// The npm package version tracks the manzanasd release version 1:1
|
|
6
|
+
// (esbuild-style): manzanasd-client@0.2.0 downloads manzanas v0.2.0.
|
|
7
|
+
const VERSION = require("../package.json").version;
|
|
8
|
+
const REPO = "BariBariGood/manzanas";
|
|
9
|
+
|
|
10
|
+
const OS_MAP = { darwin: "darwin", linux: "linux" };
|
|
11
|
+
const ARCH_MAP = { x64: "amd64", arm64: "arm64" };
|
|
12
|
+
|
|
13
|
+
function target() {
|
|
14
|
+
const os = OS_MAP[process.platform];
|
|
15
|
+
const arch = ARCH_MAP[process.arch];
|
|
16
|
+
if (!os || !arch) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`manzanasd-client: unsupported platform ${process.platform}/${process.arch}`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return { os, arch };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function tarballURL() {
|
|
25
|
+
const { os, arch } = target();
|
|
26
|
+
return (
|
|
27
|
+
`https://github.com/${REPO}/releases/download/` +
|
|
28
|
+
`v${VERSION}/manzanasd_${VERSION}_${os}_${arch}.tar.gz`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function checksumsURL() {
|
|
33
|
+
return (
|
|
34
|
+
`https://github.com/${REPO}/releases/download/v${VERSION}/checksums.txt`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function binaryPath() {
|
|
39
|
+
return path.join(__dirname, "..", "bin-native", "manzanas");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { VERSION, REPO, target, tarballURL, checksumsURL, binaryPath };
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "manzanasd-client",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Thin npm wrapper for the manzanas CLI: downloads the right release binary on install and proxies to it.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/BariBariGood/manzanas.git",
|
|
9
|
+
"directory": "clients/npm"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"manzanas": "bin/manzanas.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin/",
|
|
16
|
+
"lib/",
|
|
17
|
+
"install.js",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"postinstall": "node install.js"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"os": [
|
|
27
|
+
"darwin",
|
|
28
|
+
"linux"
|
|
29
|
+
],
|
|
30
|
+
"cpu": [
|
|
31
|
+
"x64",
|
|
32
|
+
"arm64"
|
|
33
|
+
]
|
|
34
|
+
}
|