Added cloning and CLI downloading scripts.
This commit is contained in:
commit
0fe1873950
4 files changed
+151
No files matched your search
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env node
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { chmod, copyFile, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
||||
import { homedir, tmpdir } from "node:os";
|
||||
import { basename, dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { parseArgs } from "node:util";
|
||||
|
||||
const org = "storytold";
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const config = JSON.parse(await readFile(join(scriptDir, "config.json"), "utf8"));
|
||||
const installDir = resolve(scriptDir, (config.installDir ?? "bin").replaceAll("__HOME__", homedir()));
|
||||
|
||||
// Use the token of the logged in gh, if any.
|
||||
let token = null;
|
||||
try {
|
||||
token = execFileSync("gh", ["auth", "token"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
|
||||
} catch {}
|
||||
console.log(token ? "Using gh token" : "No gh token available, using anonymous access");
|
||||
|
||||
const headers = (accept) => ({
|
||||
Accept: accept,
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
...(token && { Authorization: `Bearer ${token}` }),
|
||||
});
|
||||
|
||||
const platforms = {
|
||||
macos: /-macos-universal\.zip$/,
|
||||
linux: /-linux-x86_64\.tar\.gz$/,
|
||||
windows: /-windows-x64-portable\.zip$/,
|
||||
};
|
||||
const hostPlatform = { darwin: "macos", linux: "linux", win32: "windows" }[process.platform];
|
||||
const { values: { os: targetOs = hostPlatform, "dry-run": dryRun } } = parseArgs({
|
||||
options: { os: { type: "string" }, "dry-run": { type: "boolean" } },
|
||||
});
|
||||
if (!platforms[targetOs]) {
|
||||
console.error(`Usage: get-clis.mjs [--os ${Object.keys(platforms).join("|")}] [--dry-run]`);
|
||||
process.exit(2);
|
||||
}
|
||||
const matchesPlatform = platforms[targetOs];
|
||||
const archiveRe = /\.(tar\.gz|tgz|tar|zip)$/i;
|
||||
|
||||
async function findFiles(dir) {
|
||||
const out = [];
|
||||
for (const e of await readdir(dir, { withFileTypes: true })) {
|
||||
const p = join(dir, e.name);
|
||||
if (e.isDirectory()) out.push(...(await findFiles(p)));
|
||||
else if (e.isFile()) out.push(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
async function install(name) {
|
||||
const res = await fetch(`https://api.github.com/repos/${org}/${name}/releases/latest`, {
|
||||
headers: headers("application/vnd.github+json"),
|
||||
});
|
||||
if (!res.ok) throw new Error(`release lookup failed: ${res.status} ${res.statusText}`);
|
||||
const release = await res.json();
|
||||
|
||||
const matching = release.assets.filter((a) => a.name.startsWith(`${name}-`) && matchesPlatform.test(a.name));
|
||||
const asset = matching.find((a) => a.name.startsWith(`${name}-cli-`)) ?? matching[0];
|
||||
if (!asset) throw new Error(`no ${targetOs} asset in release ${release.tag_name}`);
|
||||
|
||||
console.log(`${name}: ${release.tag_name} -> ${asset.name}`);
|
||||
if (dryRun) {
|
||||
console.log(`${name}: dry run, would download ${asset.size} bytes into ${installDir}`);
|
||||
return;
|
||||
}
|
||||
const dl = await fetch(asset.url, { headers: headers("application/octet-stream") });
|
||||
if (!dl.ok) throw new Error(`download failed: ${dl.status} ${dl.statusText}`);
|
||||
|
||||
const work = await mkdtemp(join(tmpdir(), `${name}-`));
|
||||
try {
|
||||
const file = join(work, asset.name);
|
||||
await writeFile(file, Buffer.from(await dl.arrayBuffer()));
|
||||
|
||||
let binary = file;
|
||||
if (archiveRe.test(asset.name)) {
|
||||
const out = join(work, "extracted");
|
||||
await mkdir(out);
|
||||
execFileSync("tar", ["-xf", file, "-C", out]);
|
||||
const files = await findFiles(out);
|
||||
binary = files.find((f) => [`${name}-cli`, `${name}-cli.exe`].includes(basename(f)));
|
||||
if (!binary) throw new Error(`no ${name}-cli executable in ${asset.name}`);
|
||||
}
|
||||
|
||||
await mkdir(installDir, { recursive: true });
|
||||
const target = join(installDir, basename(binary) === asset.name ? name : basename(binary));
|
||||
await copyFile(binary, target);
|
||||
await chmod(target, 0o755);
|
||||
console.log(`${name}: installed ${target}`);
|
||||
} finally {
|
||||
await rm(work, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
let failed = 0;
|
||||
for (const name of config.clis) {
|
||||
try {
|
||||
await install(name);
|
||||
} catch (err) {
|
||||
failed++;
|
||||
console.error(`${name}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
process.exit(failed ? 1 : 0);
|
||||
Reference in new issue
Block a user