@xberg-io/xberg-cli 1.1.0 → 1.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/install.js +100 -134
- package/package.json +2 -5
package/install.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {execFileSync, spawnSync} from "node:child_process";
|
|
1
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import https from "node:https";
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import {fileURLToPath, pathToFileURL} from "node:url";
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
8
|
|
|
9
9
|
const REPO = "xberg-io/xberg";
|
|
10
10
|
const BIN_NAME = "xberg";
|
|
@@ -17,22 +17,17 @@ function targetTriple() {
|
|
|
17
17
|
const arch = os.arch();
|
|
18
18
|
|
|
19
19
|
if (type === "Windows_NT") {
|
|
20
|
-
if (arch === "x64")
|
|
21
|
-
return "x86_64-pc-windows-msvc";
|
|
20
|
+
if (arch === "x64") return "x86_64-pc-windows-msvc";
|
|
22
21
|
throw new Error(`unsupported Windows arch: ${arch}`);
|
|
23
22
|
}
|
|
24
23
|
if (type === "Linux") {
|
|
25
|
-
if (arch === "x64")
|
|
26
|
-
|
|
27
|
-
if (arch === "arm64")
|
|
28
|
-
return "aarch64-unknown-linux-gnu";
|
|
24
|
+
if (arch === "x64") return "x86_64-unknown-linux-gnu";
|
|
25
|
+
if (arch === "arm64") return "aarch64-unknown-linux-gnu";
|
|
29
26
|
throw new Error(`unsupported Linux arch: ${arch}`);
|
|
30
27
|
}
|
|
31
28
|
if (type === "Darwin") {
|
|
32
|
-
if (arch === "arm64")
|
|
33
|
-
|
|
34
|
-
if (arch === "x64")
|
|
35
|
-
return "x86_64-apple-darwin";
|
|
29
|
+
if (arch === "arm64") return "aarch64-apple-darwin";
|
|
30
|
+
if (arch === "x64") return "x86_64-apple-darwin";
|
|
36
31
|
throw new Error(`unsupported macOS arch: ${arch}`);
|
|
37
32
|
}
|
|
38
33
|
throw new Error(`unsupported platform: ${type} ${arch}`);
|
|
@@ -42,35 +37,34 @@ function binaryName() {
|
|
|
42
37
|
return os.type() === "Windows_NT" ? `${BIN_NAME}.exe` : BIN_NAME;
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
function httpGetBuffer(url, {headers = {}} = {}, maxRedirects = 5) {
|
|
40
|
+
function httpGetBuffer(url, { headers = {} } = {}, maxRedirects = 5) {
|
|
46
41
|
return new Promise((resolve, reject) => {
|
|
47
|
-
if (maxRedirects < 0)
|
|
48
|
-
|
|
42
|
+
if (maxRedirects < 0) {
|
|
43
|
+
reject(new Error("too many redirects"));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
49
46
|
if (!/^https:\/\//i.test(url)) {
|
|
50
|
-
|
|
47
|
+
reject(new Error(`refusing non-https URL: ${url}`));
|
|
48
|
+
return;
|
|
51
49
|
}
|
|
52
|
-
const req = https.get(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
res.on("data", (c) => chunks.push(c));
|
|
71
|
-
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
72
|
-
res.on("error", reject);
|
|
73
|
-
});
|
|
50
|
+
const req = https.get(url, { headers: { "User-Agent": USER_AGENT, ...headers } }, (res) => {
|
|
51
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
52
|
+
res.resume();
|
|
53
|
+
const next = res.headers.location;
|
|
54
|
+
if (!/^https:\/\//i.test(next)) {
|
|
55
|
+
return reject(new Error(`refusing non-https redirect to: ${next}`));
|
|
56
|
+
}
|
|
57
|
+
return httpGetBuffer(next, { headers }, maxRedirects - 1).then(resolve, reject);
|
|
58
|
+
}
|
|
59
|
+
if (res.statusCode !== 200) {
|
|
60
|
+
res.resume();
|
|
61
|
+
return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
62
|
+
}
|
|
63
|
+
const chunks = [];
|
|
64
|
+
res.on("data", (c) => chunks.push(c));
|
|
65
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
66
|
+
res.on("error", reject);
|
|
67
|
+
});
|
|
74
68
|
req.on("error", reject);
|
|
75
69
|
req.setTimeout(60000, () => {
|
|
76
70
|
req.destroy();
|
|
@@ -80,8 +74,7 @@ function httpGetBuffer(url, {headers = {}} = {}, maxRedirects = 5) {
|
|
|
80
74
|
}
|
|
81
75
|
|
|
82
76
|
async function httpGetJson(url) {
|
|
83
|
-
const buf = await httpGetBuffer(
|
|
84
|
-
url, {headers : {Accept : "application/vnd.github+json"}});
|
|
77
|
+
const buf = await httpGetBuffer(url, { headers: { Accept: "application/vnd.github+json" } });
|
|
85
78
|
return JSON.parse(buf.toString("utf8"));
|
|
86
79
|
}
|
|
87
80
|
|
|
@@ -117,24 +110,19 @@ export function isNonCliArtifact(name) {
|
|
|
117
110
|
export function assetScore(name) {
|
|
118
111
|
const n = (name || "").toLowerCase();
|
|
119
112
|
let score = 0;
|
|
120
|
-
if (n.includes("cli"))
|
|
121
|
-
|
|
122
|
-
if (n.includes(BIN_NAME.toLowerCase()))
|
|
123
|
-
score += 1;
|
|
113
|
+
if (n.includes("cli")) score += 2;
|
|
114
|
+
if (n.includes(BIN_NAME.toLowerCase())) score += 1;
|
|
124
115
|
return score;
|
|
125
116
|
}
|
|
126
117
|
|
|
127
118
|
export function selectArchiveName(names, triple) {
|
|
128
119
|
const survivors = (names || []).filter((name) => {
|
|
129
120
|
const n = (name || "").toLowerCase();
|
|
130
|
-
if (!n.includes(triple))
|
|
131
|
-
|
|
132
|
-
if (!(n.endsWith(".tar.gz") || n.endsWith(".zip")))
|
|
133
|
-
return false;
|
|
121
|
+
if (!n.includes(triple)) return false;
|
|
122
|
+
if (!(n.endsWith(".tar.gz") || n.endsWith(".zip"))) return false;
|
|
134
123
|
return !isNonCliArtifact(n);
|
|
135
124
|
});
|
|
136
|
-
if (survivors.length === 0)
|
|
137
|
-
return null;
|
|
125
|
+
if (survivors.length === 0) return null;
|
|
138
126
|
survivors.sort((a, b) => assetScore(b) - assetScore(a));
|
|
139
127
|
return survivors[0];
|
|
140
128
|
}
|
|
@@ -143,16 +131,15 @@ async function resolveRelease() {
|
|
|
143
131
|
const triple = targetTriple();
|
|
144
132
|
const pinned = process.env[VERSION_ENV];
|
|
145
133
|
const apiUrl = pinned
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
: `https://api.github.com/repos/${REPO}/releases/latest`;
|
|
134
|
+
? `https://api.github.com/repos/${REPO}/releases/tags/${encodeURIComponent(pinned)}`
|
|
135
|
+
: `https://api.github.com/repos/${REPO}/releases/latest`;
|
|
149
136
|
|
|
150
137
|
let release;
|
|
151
138
|
try {
|
|
152
139
|
release = await httpGetJson(apiUrl);
|
|
153
140
|
} catch (err) {
|
|
154
141
|
if (pinned && /HTTP 404/.test(err.message)) {
|
|
155
|
-
throw new Error(`release tag '${pinned}' not found`, {cause
|
|
142
|
+
throw new Error(`release tag '${pinned}' not found`, { cause: err });
|
|
156
143
|
}
|
|
157
144
|
throw err;
|
|
158
145
|
}
|
|
@@ -160,18 +147,16 @@ async function resolveRelease() {
|
|
|
160
147
|
const tag = release.tag_name || pinned || "latest";
|
|
161
148
|
|
|
162
149
|
const chosenName = selectArchiveName(
|
|
163
|
-
|
|
164
|
-
|
|
150
|
+
assets.map((a) => a.name),
|
|
151
|
+
triple,
|
|
165
152
|
);
|
|
166
153
|
if (!chosenName) {
|
|
167
|
-
throw new CliUnavailableError(`no standalone CLI asset for target triple "${
|
|
168
|
-
triple}" in ${REPO} release ${tag}`);
|
|
154
|
+
throw new CliUnavailableError(`no standalone CLI asset for target triple "${triple}" in ${REPO} release ${tag}`);
|
|
169
155
|
}
|
|
170
156
|
const archive = assets.find((a) => a.name === chosenName);
|
|
171
|
-
const checksums =
|
|
172
|
-
assets.find((a) => (a.name || "").toUpperCase().includes("SHA256SUMS"));
|
|
157
|
+
const checksums = assets.find((a) => (a.name || "").toUpperCase().includes("SHA256SUMS"));
|
|
173
158
|
|
|
174
|
-
return {tag, triple, archive, checksums};
|
|
159
|
+
return { tag, triple, archive, checksums };
|
|
175
160
|
}
|
|
176
161
|
|
|
177
162
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -180,14 +165,11 @@ const BIN_DIR = path.join(__dirname, "bin");
|
|
|
180
165
|
function expectedDigest(text, assetName) {
|
|
181
166
|
for (const raw of text.split(/\r?\n/)) {
|
|
182
167
|
const line = raw.trim();
|
|
183
|
-
if (!line)
|
|
184
|
-
continue;
|
|
168
|
+
if (!line) continue;
|
|
185
169
|
const parts = line.split(/\s+/);
|
|
186
|
-
if (parts.length < 2)
|
|
187
|
-
continue;
|
|
170
|
+
if (parts.length < 2) continue;
|
|
188
171
|
const name = parts[parts.length - 1].replace(/^\*/, "");
|
|
189
|
-
if (name === assetName)
|
|
190
|
-
return parts[0].toLowerCase();
|
|
172
|
+
if (name === assetName) return parts[0].toLowerCase();
|
|
191
173
|
}
|
|
192
174
|
return null;
|
|
193
175
|
}
|
|
@@ -195,58 +177,49 @@ function expectedDigest(text, assetName) {
|
|
|
195
177
|
async function verifyOrWarn(archiveBuf, archiveName, checksums) {
|
|
196
178
|
if (!checksums) {
|
|
197
179
|
process.stderr.write(
|
|
198
|
-
|
|
199
|
-
|
|
180
|
+
`WARNING: no SHA256SUMS asset found for ${archiveName}; ` +
|
|
181
|
+
`installing over HTTPS without checksum verification.\n`,
|
|
200
182
|
);
|
|
201
183
|
return;
|
|
202
184
|
}
|
|
203
|
-
const sumsText =
|
|
204
|
-
(await httpGetBuffer(checksums.browser_download_url)).toString("utf8");
|
|
185
|
+
const sumsText = (await httpGetBuffer(checksums.browser_download_url)).toString("utf8");
|
|
205
186
|
const expected = expectedDigest(sumsText, archiveName);
|
|
206
187
|
if (!expected) {
|
|
207
188
|
throw new Error(
|
|
208
|
-
|
|
209
|
-
checksums.name} — refusing to install unverified binary`,
|
|
189
|
+
`no checksum entry for ${archiveName} in ${checksums.name} — refusing to install unverified binary`,
|
|
210
190
|
);
|
|
211
191
|
}
|
|
212
|
-
const actual = crypto.createHash("sha256")
|
|
213
|
-
.update(archiveBuf)
|
|
214
|
-
.digest("hex")
|
|
215
|
-
.toLowerCase();
|
|
192
|
+
const actual = crypto.createHash("sha256").update(archiveBuf).digest("hex").toLowerCase();
|
|
216
193
|
if (actual !== expected) {
|
|
217
|
-
throw new Error(`checksum mismatch for ${archiveName} (expected ${
|
|
218
|
-
expected}, got ${actual})`);
|
|
194
|
+
throw new Error(`checksum mismatch for ${archiveName} (expected ${expected}, got ${actual})`);
|
|
219
195
|
}
|
|
220
196
|
process.stderr.write(`Checksum verified for ${archiveName}.\n`);
|
|
221
197
|
}
|
|
222
198
|
|
|
223
199
|
function isUnsafeEntry(name) {
|
|
224
200
|
const entry = String(name).replace(/\\/g, "/").trim();
|
|
225
|
-
if (!entry)
|
|
226
|
-
|
|
227
|
-
if (entry
|
|
228
|
-
|
|
229
|
-
if (/^[a-zA-Z]:/.test(entry))
|
|
230
|
-
return true;
|
|
231
|
-
if (entry.startsWith("//"))
|
|
232
|
-
return true;
|
|
201
|
+
if (!entry) return false;
|
|
202
|
+
if (entry.startsWith("/")) return true;
|
|
203
|
+
if (/^[a-zA-Z]:/.test(entry)) return true;
|
|
204
|
+
if (entry.startsWith("//")) return true;
|
|
233
205
|
return entry.split("/").some((part) => part === "..");
|
|
234
206
|
}
|
|
235
207
|
|
|
236
208
|
function listTarEntries(archivePath) {
|
|
237
|
-
const result = spawnSync("tar", [
|
|
209
|
+
const result = spawnSync("tar", ["-tzf", archivePath]);
|
|
238
210
|
if (result.status !== 0) {
|
|
239
211
|
const stderr = result.stderr ? result.stderr.toString() : "";
|
|
240
212
|
throw new Error(`tar listing failed: ${stderr || result.error}`);
|
|
241
213
|
}
|
|
242
|
-
return result.stdout
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
214
|
+
return result.stdout
|
|
215
|
+
.toString()
|
|
216
|
+
.split(/\r?\n/)
|
|
217
|
+
.map((s) => s.trim())
|
|
218
|
+
.filter(Boolean);
|
|
246
219
|
}
|
|
247
220
|
|
|
248
221
|
function extractTarGz(archivePath, destDir) {
|
|
249
|
-
const result = spawnSync("tar", [
|
|
222
|
+
const result = spawnSync("tar", ["-xzf", archivePath, "-C", destDir]);
|
|
250
223
|
if (result.status !== 0) {
|
|
251
224
|
const stderr = result.stderr ? result.stderr.toString() : "";
|
|
252
225
|
throw new Error(`tar extraction failed: ${stderr || result.error}`);
|
|
@@ -256,26 +229,28 @@ function extractTarGz(archivePath, destDir) {
|
|
|
256
229
|
function listZipEntries(archivePath) {
|
|
257
230
|
if (os.type() === "Windows_NT") {
|
|
258
231
|
const script =
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const out = execFileSync(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
232
|
+
"$ErrorActionPreference='Stop';" +
|
|
233
|
+
"Add-Type -AssemblyName System.IO.Compression.FileSystem;" +
|
|
234
|
+
"[System.IO.Compression.ZipFile]::OpenRead($args[0]).Entries |" +
|
|
235
|
+
" ForEach-Object { $_.FullName }";
|
|
236
|
+
const out = execFileSync("powershell", ["-NoProfile", "-NonInteractive", "-Command", script, archivePath], {
|
|
237
|
+
encoding: "utf8",
|
|
238
|
+
});
|
|
239
|
+
return out
|
|
240
|
+
.split(/\r?\n/)
|
|
241
|
+
.map((s) => s.trim())
|
|
242
|
+
.filter(Boolean);
|
|
269
243
|
}
|
|
270
|
-
const result = spawnSync("unzip", [
|
|
244
|
+
const result = spawnSync("unzip", ["-Z1", archivePath]);
|
|
271
245
|
if (result.status !== 0) {
|
|
272
246
|
const stderr = result.stderr ? result.stderr.toString() : "";
|
|
273
247
|
throw new Error(`zip listing failed: ${stderr || result.error}`);
|
|
274
248
|
}
|
|
275
|
-
return result.stdout
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
249
|
+
return result.stdout
|
|
250
|
+
.toString()
|
|
251
|
+
.split(/\r?\n/)
|
|
252
|
+
.map((s) => s.trim())
|
|
253
|
+
.filter(Boolean);
|
|
279
254
|
}
|
|
280
255
|
|
|
281
256
|
function extractZip(archivePath, destDir) {
|
|
@@ -297,7 +272,7 @@ function extractZip(archivePath, destDir) {
|
|
|
297
272
|
}
|
|
298
273
|
return;
|
|
299
274
|
}
|
|
300
|
-
const result = spawnSync("unzip", [
|
|
275
|
+
const result = spawnSync("unzip", ["-o", archivePath, "-d", destDir]);
|
|
301
276
|
if (result.status !== 0) {
|
|
302
277
|
const stderr = result.stderr ? result.stderr.toString() : "";
|
|
303
278
|
throw new Error(`zip extraction failed: ${stderr || result.error}`);
|
|
@@ -305,12 +280,11 @@ function extractZip(archivePath, destDir) {
|
|
|
305
280
|
}
|
|
306
281
|
|
|
307
282
|
function findBinary(dir, name) {
|
|
308
|
-
for (const entry of fs.readdirSync(dir, {withFileTypes
|
|
283
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
309
284
|
const full = path.join(dir, entry.name);
|
|
310
285
|
if (entry.isDirectory()) {
|
|
311
286
|
const found = findBinary(full, name);
|
|
312
|
-
if (found)
|
|
313
|
-
return found;
|
|
287
|
+
if (found) return found;
|
|
314
288
|
} else if (entry.name === name) {
|
|
315
289
|
return full;
|
|
316
290
|
}
|
|
@@ -319,22 +293,18 @@ function findBinary(dir, name) {
|
|
|
319
293
|
}
|
|
320
294
|
|
|
321
295
|
function findDir(dir, name) {
|
|
322
|
-
for (const entry of fs.readdirSync(dir, {withFileTypes
|
|
323
|
-
if (!entry.isDirectory())
|
|
324
|
-
|
|
325
|
-
if (entry.name === name)
|
|
326
|
-
return path.join(dir, entry.name);
|
|
296
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
297
|
+
if (!entry.isDirectory()) continue;
|
|
298
|
+
if (entry.name === name) return path.join(dir, entry.name);
|
|
327
299
|
const found = findDir(path.join(dir, entry.name), name);
|
|
328
|
-
if (found)
|
|
329
|
-
return found;
|
|
300
|
+
if (found) return found;
|
|
330
301
|
}
|
|
331
302
|
return null;
|
|
332
303
|
}
|
|
333
304
|
|
|
334
305
|
function safeExtract(archivePath, archiveName, dest) {
|
|
335
306
|
const isZip = archiveName.toLowerCase().endsWith(".zip");
|
|
336
|
-
const entries =
|
|
337
|
-
isZip ? listZipEntries(archivePath) : listTarEntries(archivePath);
|
|
307
|
+
const entries = isZip ? listZipEntries(archivePath) : listTarEntries(archivePath);
|
|
338
308
|
for (const entry of entries) {
|
|
339
309
|
if (isUnsafeEntry(entry)) {
|
|
340
310
|
throw new Error(`refusing unsafe archive entry: ${entry}`);
|
|
@@ -352,19 +322,18 @@ function safeExtract(archivePath, archiveName, dest) {
|
|
|
352
322
|
const binName = binaryName();
|
|
353
323
|
const extractedBin = findBinary(tmpDir, binName);
|
|
354
324
|
if (!extractedBin) {
|
|
355
|
-
throw new CliUnavailableError(`archive ${
|
|
356
|
-
archiveName} did not contain expected CLI binary ${binName}`);
|
|
325
|
+
throw new CliUnavailableError(`archive ${archiveName} did not contain expected CLI binary ${binName}`);
|
|
357
326
|
}
|
|
358
327
|
const finalBin = path.join(dest, binName);
|
|
359
328
|
fs.copyFileSync(extractedBin, finalBin);
|
|
360
329
|
|
|
361
330
|
const libDir = findDir(tmpDir, "lib");
|
|
362
331
|
if (libDir) {
|
|
363
|
-
fs.cpSync(libDir, path.join(dest, "lib"), {recursive
|
|
332
|
+
fs.cpSync(libDir, path.join(dest, "lib"), { recursive: true });
|
|
364
333
|
}
|
|
365
334
|
return finalBin;
|
|
366
335
|
} finally {
|
|
367
|
-
fs.rmSync(tmpDir, {recursive
|
|
336
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
368
337
|
}
|
|
369
338
|
}
|
|
370
339
|
|
|
@@ -376,17 +345,14 @@ export async function main() {
|
|
|
376
345
|
const stat = fs.statSync(finalPath);
|
|
377
346
|
const sizeOk = stat.size > 0;
|
|
378
347
|
const execOk = os.type() === "Windows_NT" || (stat.mode & 0o111) !== 0;
|
|
379
|
-
if (sizeOk && execOk)
|
|
380
|
-
|
|
381
|
-
} catch {
|
|
382
|
-
}
|
|
348
|
+
if (sizeOk && execOk) return;
|
|
349
|
+
} catch {}
|
|
383
350
|
}
|
|
384
351
|
|
|
385
|
-
fs.mkdirSync(BIN_DIR, {recursive
|
|
352
|
+
fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
386
353
|
|
|
387
|
-
const {tag, archive, checksums} = await resolveRelease();
|
|
388
|
-
process.stderr.write(
|
|
389
|
-
`Downloading ${BIN_NAME} ${tag} asset ${archive.name}...\n`);
|
|
354
|
+
const { tag, archive, checksums } = await resolveRelease();
|
|
355
|
+
process.stderr.write(`Downloading ${BIN_NAME} ${tag} asset ${archive.name}...\n`);
|
|
390
356
|
|
|
391
357
|
const archiveBuf = await httpGetBuffer(archive.browser_download_url);
|
|
392
358
|
await verifyOrWarn(archiveBuf, archive.name, checksums);
|
|
@@ -397,7 +363,7 @@ export async function main() {
|
|
|
397
363
|
fs.writeFileSync(archivePath, archiveBuf);
|
|
398
364
|
safeExtract(archivePath, archive.name, BIN_DIR);
|
|
399
365
|
} finally {
|
|
400
|
-
fs.rmSync(stageDir, {recursive
|
|
366
|
+
fs.rmSync(stageDir, { recursive: true, force: true });
|
|
401
367
|
}
|
|
402
368
|
|
|
403
369
|
if (os.type() !== "Windows_NT") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xberg-io/xberg-cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "CLI proxy for xberg — downloads and runs the native xberg binary from GitHub releases.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Na'aman Hirschfeld",
|
|
@@ -12,10 +12,7 @@
|
|
|
12
12
|
"bin": {
|
|
13
13
|
"xberg": "bin/xberg.js"
|
|
14
14
|
},
|
|
15
|
-
"files": [
|
|
16
|
-
"bin/",
|
|
17
|
-
"install.js"
|
|
18
|
-
],
|
|
15
|
+
"files": ["bin/", "install.js"],
|
|
19
16
|
"type": "module",
|
|
20
17
|
"publishConfig": {
|
|
21
18
|
"access": "public"
|