@fedify/cli
Version:
CLI toolchain for Fedify and debugging ActivityPub
180 lines (169 loc) • 4.73 kB
JavaScript
import { execFile } from "node:child_process";
import { createWriteStream } from "node:fs";
import {
access,
chmod,
constants,
copyFile,
mkdir,
mkdtemp,
readFile,
realpath,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import process from "node:process";
import { Readable } from "node:stream";
import { fileURLToPath } from "node:url";
const platforms = {
darwin: {
arm64: "macos-aarch64.tar.xz",
x64: "macos-x86_64.tar.xz",
},
linux: {
arm64: "linux-aarch64.tar.xz",
x64: "linux-x86_64.tar.xz",
},
win32: {
arm64: "windows-x86_64.zip",
x64: "windows-x86_64.zip",
},
};
export async function main(version) {
const filename = fileURLToPath(import.meta.url);
const dirName = dirname(filename);
const packageJson = await readFile(join(dirName, "package.json"), {
encoding: "utf8",
});
const pkg = JSON.parse(packageJson);
const binDir = join(dirName, "bin");
await mkdir(binDir, { recursive: true });
await install(version ?? pkg.version, binDir);
}
async function install(version, targetDir) {
const downloadUrl = getDownloadUrl(version);
const downloadPath = await download(downloadUrl);
let extractPath;
if (downloadPath.endsWith(".zip")) {
extractPath = await extractZip(downloadPath);
} else {
extractPath = await extractTar(downloadPath);
}
const exePath = join(extractPath, "fedify.exe");
if (await isFile(exePath)) {
const targetPath = join(targetDir, "fedify.exe");
await copyFile(exePath, targetPath);
return targetPath;
}
const binPath = join(extractPath, "fedify");
if (await isFile(binPath)) {
const targetPath = join(targetDir, "fedify");
await copyFile(binPath, targetPath);
await chmod(targetPath, 0o755);
return targetPath;
}
throw new Error("Executable not found in the archive");
}
function getDownloadUrl(version) {
const platform = platforms[process.platform];
if (!platform) {
console.error("Unsupported platform:", process.platform);
return;
}
const suffix = platform[process.arch];
if (!suffix) {
console.error("Unsupported architecture:", process.arch);
return;
}
const filename = `fedify-cli-${version}-${suffix}`;
const url =
`https://github.com/fedify-dev/fedify/releases/download/${version}/${filename}`;
return url;
}
async function download(url) {
const response = await fetch(url, { redirect: "follow" });
if (!response.ok) {
console.error("Download failed:", response.statusText);
return;
}
const tmpDir = await mkdtemp(join(tmpdir(), `fedify-`));
const filename = url.substring(url.lastIndexOf("/") + 1);
const downloadPath = join(tmpDir, filename);
const fileStream = createWriteStream(downloadPath);
const readable = Readable.fromWeb(response.body);
await new Promise((resolve, reject) => {
readable.pipe(fileStream);
readable.on("error", reject);
fileStream.on("finish", resolve);
});
return downloadPath;
}
async function extractZip(path) {
const dir = await mkdtemp(join(tmpdir(), "fedify-"));
await new Promise((resolve, reject) => {
execFile("powershell", [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-Command",
`Import-Module Microsoft.PowerShell.Archive;\
Expand-Archive -LiteralPath '${path}' -DestinationPath '${dir}'`,
], (error, _, stderr) => {
if (error) {
console.error("Extraction failed:", error);
reject(error);
return;
}
if (stderr) console.warn(stderr);
resolve();
});
});
return dir;
}
async function extractTar(path) {
const switches = {
".tar": "",
".tar.gz": "z",
".tgz": "z",
".tar.bz2": "j",
".tbz2": "j",
".tar.xz": "J",
".txz": "J",
};
let switch_ = "";
for (const ext in switches) {
if (path.endsWith(ext)) {
switch_ = switches[ext];
break;
}
}
path = await realpath(path);
const dir = await mkdtemp(join(tmpdir(), "fedify-"));
await execTar(`xvf${switch_}`, dir, path);
return dir;
}
function execTar(switch_, dir, path) {
return new Promise((resolve, reject) => {
execFile("tar", [switch_, path], { cwd: dir }, (error, _, stderr) => {
if (error) {
console.error("Extraction failed:", error);
reject(error);
return;
}
if (stderr) console.warn(stderr);
resolve();
});
});
}
export async function isFile(path) {
try {
await access(path, constants.R_OK);
return true;
} catch (error) {
if (error.code === "ENOENT") return false;
throw error;
}
}
if (fileURLToPath(import.meta.url) === process.argv[1]) {
await main(process.argv[2]);
}