mdsf-cli
Version:
Format, and lint, markdown code snippets using your favorite tools
213 lines (192 loc) • 6.56 kB
JavaScript
const { createWriteStream, existsSync, mkdirSync, mkdtemp } = require("fs");
const { join, sep } = require("path");
const { spawnSync } = require("child_process");
const { tmpdir } = require("os");
const axios = require("axios");
const rimraf = require("rimraf");
const tmpDir = tmpdir();
const error = (msg) => {
console.error(msg);
process.exit(1);
};
class Package {
constructor(platform, name, url, filename, zipExt, binaries) {
let errors = [];
if (typeof url !== "string") {
errors.push("url must be a string");
} else {
try {
new URL(url);
} catch (e) {
errors.push(e);
}
}
if (name && typeof name !== "string") {
errors.push("package name must be a string");
}
if (!name) {
errors.push("You must specify the name of your package");
}
if (binaries && typeof binaries !== "object") {
errors.push("binaries must be a string => string map");
}
if (!binaries) {
errors.push("You must specify the binaries in the package");
}
if (errors.length > 0) {
let errorMsg =
"One or more of the parameters you passed to the Binary constructor are invalid:\n";
errors.forEach((error) => {
errorMsg += error;
});
errorMsg +=
'\n\nCorrect usage: new Package("my-binary", "https://example.com/binary/download.tar.gz", {"my-binary": "my-binary"})';
error(errorMsg);
}
this.platform = platform;
this.url = url;
this.name = name;
this.filename = filename;
this.zipExt = zipExt;
this.installDirectory = join(__dirname, "node_modules", ".bin_real");
this.binaries = binaries;
if (!existsSync(this.installDirectory)) {
mkdirSync(this.installDirectory, { recursive: true });
}
}
exists() {
for (const binaryName in this.binaries) {
const binRelPath = this.binaries[binaryName];
const binPath = join(this.installDirectory, binRelPath);
if (!existsSync(binPath)) {
return false;
}
}
return true;
}
install(fetchOptions, suppressLogs = false) {
if (this.exists()) {
if (!suppressLogs) {
console.error(
`${this.name} is already installed, skipping installation.`,
);
}
return Promise.resolve();
}
if (existsSync(this.installDirectory)) {
rimraf.sync(this.installDirectory);
}
mkdirSync(this.installDirectory, { recursive: true });
if (!suppressLogs) {
console.error(`Downloading release from ${this.url}`);
}
return axios({ ...fetchOptions, url: this.url, responseType: "stream" })
.then((res) => {
return new Promise((resolve, reject) => {
mkdtemp(`${tmpDir}${sep}`, (err, directory) => {
let tempFile = join(directory, this.filename);
const sink = res.data.pipe(createWriteStream(tempFile));
sink.on("error", (err) => reject(err));
sink.on("close", () => {
if (/\.tar\.*/.test(this.zipExt)) {
const result = spawnSync("tar", [
"xf",
tempFile,
// The tarballs are stored with a leading directory
// component; we strip one component in the
// shell installers too.
"--strip-components",
"1",
"-C",
this.installDirectory,
]);
if (result.status == 0) {
resolve();
} else if (result.error) {
reject(result.error);
} else {
reject(
new Error(
`An error occurred untarring the artifact: stdout: ${result.stdout}; stderr: ${result.stderr}`,
),
);
}
} else if (this.zipExt == ".zip") {
let result;
if (this.platform.artifactName.includes("windows")) {
// Windows does not have "unzip" by default on many installations, instead
// we use Expand-Archive from powershell
result = spawnSync("powershell.exe", [
"-NoProfile",
"-NonInteractive",
"-Command",
`& {
param([string]$LiteralPath, [string]$DestinationPath)
Expand-Archive -LiteralPath $LiteralPath -DestinationPath $DestinationPath -Force
}`,
tempFile,
this.installDirectory,
]);
} else {
result = spawnSync("unzip", [
"-q",
tempFile,
"-d",
this.installDirectory,
]);
}
if (result.status == 0) {
resolve();
} else if (result.error) {
reject(result.error);
} else {
reject(
new Error(
`An error occurred unzipping the artifact: stdout: ${result.stdout}; stderr: ${result.stderr}`,
),
);
}
} else {
reject(
new Error(`Unrecognized file extension: ${this.zipExt}`),
);
}
});
});
});
})
.then(() => {
if (!suppressLogs) {
console.error(`${this.name} has been installed!`);
}
})
.catch((e) => {
error(`Error fetching release: ${e.message}`);
});
}
run(binaryName, fetchOptions) {
const promise = !this.exists()
? this.install(fetchOptions, true)
: Promise.resolve();
promise
.then(() => {
const [, , ...args] = process.argv;
const options = { cwd: process.cwd(), stdio: "inherit" };
const binRelPath = this.binaries[binaryName];
if (!binRelPath) {
error(`${binaryName} is not a known binary in ${this.name}`);
}
const binPath = join(this.installDirectory, binRelPath);
const result = spawnSync(binPath, args, options);
if (result.error) {
error(result.error);
}
process.exit(result.status);
})
.catch((e) => {
error(e.message);
process.exit(1);
});
}
}
module.exports.Package = Package;