@dinglebear/labby 1.8.6

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.
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const crypto = require("node:crypto");
5
+ const fs = require("node:fs");
6
+ const http = require("node:http");
7
+ const https = require("node:https");
8
+ const os = require("node:os");
9
+ const path = require("node:path");
10
+ const { spawnSync } = require("node:child_process");
11
+ const {
12
+ binaryPath,
13
+ downloadUrl,
14
+ installRoot,
15
+ releaseVersion,
16
+ targetFor,
17
+ } = require("../lib/platform");
18
+
19
+ function log(message) {
20
+ process.stderr.write(`labby: ${message}\n`);
21
+ }
22
+
23
+ function download(url, destination) {
24
+ return new Promise((resolve, reject) => {
25
+ const client = url.startsWith("http:") ? http : https;
26
+ const request = client.get(url, (response) => {
27
+ if ([301, 302, 303, 307, 308].includes(response.statusCode)) {
28
+ response.resume();
29
+ download(response.headers.location, destination).then(resolve, reject);
30
+ return;
31
+ }
32
+
33
+ if (response.statusCode !== 200) {
34
+ response.resume();
35
+ reject(new Error(`download failed (${response.statusCode}) from ${url}`));
36
+ return;
37
+ }
38
+
39
+ const file = fs.createWriteStream(destination, { mode: 0o600 });
40
+ response.pipe(file);
41
+ file.on("finish", () => file.close(resolve));
42
+ file.on("error", reject);
43
+ });
44
+
45
+ request.on("error", reject);
46
+ });
47
+ }
48
+
49
+ function sha256(file) {
50
+ const hash = crypto.createHash("sha256");
51
+ hash.update(fs.readFileSync(file));
52
+ return hash.digest("hex");
53
+ }
54
+
55
+ function checksumFromText(text, asset) {
56
+ const lines = text.trim().split(/\r?\n/).filter(Boolean);
57
+ for (const line of lines) {
58
+ const parts = line.trim().split(/\s+/);
59
+ const hash = parts[0] && parts[0].toLowerCase();
60
+ const name = parts.slice(1).join(" ").replace(/^\*/, "");
61
+ if (/^[a-f0-9]{64}$/.test(hash) && (!asset || !name || path.basename(name) === asset)) {
62
+ return hash;
63
+ }
64
+ }
65
+ throw new Error("checksum file does not contain a SHA-256 entry for " + asset);
66
+ }
67
+
68
+ async function verifyChecksum(url, archive) {
69
+ const asset = path.basename(archive);
70
+ const sidecarFile = archive + ".sha256";
71
+ let expected;
72
+
73
+ try {
74
+ await download(url + ".sha256", sidecarFile);
75
+ expected = checksumFromText(fs.readFileSync(sidecarFile, "utf8"), asset);
76
+ } catch (sidecarError) {
77
+ const manifestUrl = url.replace(/\/[^/]+$/, "/SHA256SUMS");
78
+ const manifestFile = archive + ".SHA256SUMS";
79
+ await download(manifestUrl, manifestFile);
80
+ expected = checksumFromText(fs.readFileSync(manifestFile, "utf8"), asset);
81
+ }
82
+
83
+ const actual = sha256(archive);
84
+ if (actual !== expected) {
85
+ throw new Error("checksum mismatch for " + asset + ": expected " + expected + ", got " + actual);
86
+ }
87
+
88
+ log("verified checksum for " + asset);
89
+ }
90
+
91
+ function run(command, args, failureMessage) {
92
+ const result = spawnSync(command, args, {
93
+ encoding: "utf8",
94
+ stdio: ["ignore", "pipe", "pipe"],
95
+ });
96
+
97
+ if (result.status !== 0) {
98
+ throw new Error((result.stderr || result.stdout || failureMessage).trim());
99
+ }
100
+ }
101
+
102
+ function extractTarGz(archive, destination) {
103
+ run("tar", ["-xzf", archive, "-C", destination], "tar extraction failed");
104
+ }
105
+
106
+ function powershellLiteral(value) {
107
+ return `'${String(value).replaceAll("'", "''")}'`;
108
+ }
109
+
110
+ function powershellExpandArchiveCommand(archive, destination) {
111
+ return `Expand-Archive -LiteralPath ${powershellLiteral(archive)} -DestinationPath ${powershellLiteral(destination)} -Force`;
112
+ }
113
+
114
+ function extractZip(archive, destination) {
115
+ if (process.platform === "win32") {
116
+ run(
117
+ "powershell.exe",
118
+ [
119
+ "-NoLogo",
120
+ "-NoProfile",
121
+ "-NonInteractive",
122
+ "-ExecutionPolicy",
123
+ "Bypass",
124
+ "-Command",
125
+ powershellExpandArchiveCommand(archive, destination),
126
+ ],
127
+ "zip extraction failed",
128
+ );
129
+ return;
130
+ }
131
+
132
+ run("unzip", ["-q", archive, "-d", destination], "zip extraction failed");
133
+ }
134
+
135
+ function extract(archive, destination, archiveType) {
136
+ fs.rmSync(destination, { recursive: true, force: true });
137
+ fs.mkdirSync(destination, { recursive: true });
138
+
139
+ if (archiveType === "tar.gz") {
140
+ extractTarGz(archive, destination);
141
+ return;
142
+ }
143
+
144
+ if (archiveType === "zip") {
145
+ extractZip(archive, destination);
146
+ return;
147
+ }
148
+
149
+ throw new Error(`unsupported archive type: ${archiveType}`);
150
+ }
151
+
152
+ async function main() {
153
+ if (process.env.LABBY_SKIP_DOWNLOAD === "1") {
154
+ log("skipping binary download because LABBY_SKIP_DOWNLOAD=1");
155
+ return;
156
+ }
157
+
158
+ const target = targetFor();
159
+ const destination = binaryPath();
160
+
161
+ if (fs.existsSync(destination)) {
162
+ log(`${path.basename(destination)} already installed for ${releaseVersion()}`);
163
+ return;
164
+ }
165
+
166
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "labby-mcp-install-"));
167
+ const archive = path.join(tempDir, target.asset);
168
+
169
+ try {
170
+ const url = downloadUrl(target);
171
+ log(`downloading ${url}`);
172
+ await download(url, archive);
173
+ await verifyChecksum(url, archive);
174
+ extract(archive, installRoot(), target.archiveType);
175
+ fs.chmodSync(destination, 0o755);
176
+ log(`installed ${destination}`);
177
+ } finally {
178
+ fs.rmSync(tempDir, { recursive: true, force: true });
179
+ }
180
+ }
181
+
182
+ if (require.main === module) {
183
+ main().catch((error) => {
184
+ log(error.message);
185
+ process.exitCode = 1;
186
+ });
187
+ }
188
+
189
+ module.exports = {
190
+ powershellExpandArchiveCommand,
191
+ powershellLiteral,
192
+ };
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+
6
+ const packageReadme = path.resolve(__dirname, "..", "README.md");
7
+ const repoReadme = path.resolve(__dirname, "..", "..", "..", "README.md");
8
+ const packageRoot = path.resolve(__dirname, "..");
9
+ const repoRoot = path.resolve(__dirname, "..", "..", "..");
10
+
11
+ if (fs.existsSync(packageReadme) && fs.lstatSync(packageReadme).isSymbolicLink()) {
12
+ fs.unlinkSync(packageReadme);
13
+ }
14
+
15
+ fs.copyFileSync(repoReadme, packageReadme);
16
+
17
+ for (const entry of fs.readdirSync(repoRoot)) {
18
+ const source = path.join(repoRoot, entry);
19
+ if (/^licen[cs]e/i.test(entry) && fs.statSync(source).isFile()) {
20
+ const destination = path.join(packageRoot, entry);
21
+ if (fs.existsSync(destination) && fs.lstatSync(destination).isSymbolicLink()) {
22
+ fs.unlinkSync(destination);
23
+ }
24
+ fs.copyFileSync(source, destination);
25
+ }
26
+ }