[Suggestion] Platform independent NodeJS-based installer
Considering that the plugin uses an executable that depends on NodeJS, a platform independent installer based on NodeJS could be considered.
Only a few extra dependencies would be added, namely:
node-fetch to get the latest release from GitHub API
node-downloader-helper to download the zip file
compressing to unzip the file
I made a draft script, which works correctly by getting the last available release of the executable, downloading and extracting it to the specified folder. I believe this is a useful and easy way to maintain the installation script, considering that there are currently scripts per platform.
install.js
// node-fetch
const fetch = (...args) => import("node-fetch").then(({ default: fetch }) => fetch(...args));
// node-downloader-helper
const { DownloaderHelper } = require("node-downloader-helper");
const { byteHelper } = require("node-downloader-helper/bin/helpers");
// path
const path = require("path");
// compressing lib
const compressing = require("compressing");
// fs
const fs = require("fs");
// Repository
const repo = "iamcco/markdown-preview.nvim";
// GitHub API
let url = `https://api.github.com/repos/${repo}/releases/latest`;
// Get current system
const _os = process.platform;
// Filename
const filePrefix = "markdown-preview-";
const filename = {
zip: {
win32: "win.zip",
linux: "linux.tar.gz",
darwin: "macos.tar.gz",
},
executable: {
win32: `${filePrefix}win.exe`,
linux: `${filePrefix}linux`,
darwin: `${filePrefix}macos`,
},
};
// Full zip filename
let zip_file = `${filePrefix}${filename.zip[_os]}`;
// Define bin path
const bin_path = path.join(__dirname, "bin");
// Filepath
const file_path = path.join(bin_path, zip_file);
// Executable path
const exec_path = path.join(bin_path, `${filePrefix}${filename.executable[_os]}`);
// Get latest release version
const getLatestRelease = async () => {
const res = await fetch(url);
const json = await res.json();
return json.tag_name;
};
// DownloaderHelper options
const options = {
method: "GET",
override: { skip: true, skipSmaller: false },
removeOnStop: true,
removeOnFail: true,
fileName: `${zip_file}`,
};
// Download latest release
const downloadRelease = async (latest) => {
try {
let startTime = new Date();
// URL for file download
let download = `https://github.com/${repo}/releases/download/${latest}/${zip_file}`;
const dl = new DownloaderHelper(`${download}`, bin_path, options);
dl.on("download", (downloadInfo) =>
console.log("Download Begins: ", {
name: downloadInfo.fileName,
total: downloadInfo.totalSize,
})
)
.on("end", (downloadInfo) => console.log("Download Completed: ", downloadInfo))
.on("skip", (skipInfo) => console.log("Download skipped. File already exists."))
.on("error", (err) => console.error("Something happened", err))
.on("retry", (attempt, opts, err) => {
console.log({
RetryAttempt: `${attempt}/${opts.maxRetries}`,
StartsOn: `${opts.delay / 1000} secs`,
Reason: err ? err.message : "unknown",
});
})
.on("progress", (stats) => {
const progress = stats.progress.toFixed(1);
const speed = byteHelper(stats.speed);
const downloaded = byteHelper(stats.downloaded);
const total = byteHelper(stats.total);
const currentTime = new Date();
const elaspsedTime = currentTime - startTime;
if (elaspsedTime > 1000) {
startTime = currentTime;
console.log(`${speed}/s - ${progress}% [${downloaded}/${total}]`);
}
});
console.log("Downloading: ", download);
dl.start().catch((err) => {
console.error(err);
});
} catch (error) {
throw error;
}
};
// Unzip file
const unzip = async () => {
const file_ext = zip_file.split(".").pop();
const unzipLib = {
zip: compressing.zip,
gz: compressing.tgz,
};
// Check if zip file is available
fs.access(file_path, fs.F_OK, () => {
try {
// Check if executable exists
fs.access(exec_path, fs.F_OK, () => {
try {
console.log("markdown-preview already installed.");
} catch (error) {
unzipLib[file_ext].uncompress(file_path, bin_path);
console.log("markdown-preview install completed.");
}
});
} catch (error) {
console.log("Zip file unavailable. Install cancelled!");
return;
}
});
};
// Run installation
const install = (async () => {
const latest = await getLatestRelease();
console.log(`Latest markdown-preview release: ${latest}`);
await downloadRelease(latest);
await unzip();
})();
Current installation script is to support for no nodejs environment, so that they can use the binary bundle build.