markdown-preview.nvim icon indicating copy to clipboard operation
markdown-preview.nvim copied to clipboard

[Suggestion] Platform independent NodeJS-based installer

Open AndreAugustoDev opened this issue 4 years ago • 1 comments

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();
})();

AndreAugustoDev avatar Nov 01 '21 02:11 AndreAugustoDev

Current installation script is to support for no nodejs environment, so that they can use the binary bundle build.

iamcco avatar May 13 '22 12:05 iamcco