Home Reference Source Test

src/Releaser/Releaser.js

import { execSync } from "child_process";
import chalk from "chalk";

import Logger from "../Logger";

/**
 * Bundles source files for release.
 *
 * @example <caption>Assume later uses of `config` use the following value.</caption>
 * const config = {
 *      vendor: "shinka",
 *      code: "cli",
 *      version: "0.0.1-a"
 * };
 *
 * @example
 * new Releaser({}, config);
 * // => shinka-news-0.0.1-a.zip
 *
 * @example
 * new Releaser({
 *      vendor: "shin",
 *      directory: "to/a/dir"
 * }, config);
 * // => to/a/dir/shin-news-0.0.1-a.zip
 */
export default class Releaser {
    /**
     * Command arguments.
     *
     * @type     {Cmd}
     * @property {?string} [cmd.output]    - Overrides default filename format.
     * @property {?string} [cmd.directory] - Prepended to filename.
     * @property {?string} [cmd.vendor]    - Vendor name, e.g. `shinka` in `shinka-cli`.
     * @property {?string} [cmd.code]      - Plugin code name, e.g. `cli` in `shinka-cli`.
     * @property {?string} [cmd.semver]    - Semantic plugin version
     */
    cmd;

    /**
     * For vendor, code, and version lookup.
     * @type {Config}
     */
    config = {};

    /**
     * @type {Error[]}
     */
    errorList = [];

    /**
     * Output target.
     * @type {string}
     */
    filename;

    /**
     * logger#log is used to output progress, e.g. {@link Logger#log} or {@link console#log}.
     * @type {LogObject}
     */
    logger;

    /**
     * @param {Cmd}       cmd                   - Command arguments
     * @param {Config}    [config={}]           - For vendor, code, and version lookup.
     * @param {LogObject} [logger=new Logger()] - logger#log is used to output progress
     */
    constructor(cmd, config = {}, logger = new Logger()) {
        this.cmd = cmd;
        this.config = config;
        this.logger = logger;
        this.filename = this.getFileName();
    }

    /**
     * Builds filename from command arguments and config.
     *
     * Format defaults to `vendor-code-semver.zip`.
     * `cmd.output` overwrites the default format.
     * `cmd.directory` is prepended to the filename
     *
     * @returns {string}
     *
     * @example
     * new Releaser({}, config).getFileName();
     * // => shinka-cli-0.0.1-a.zip
     *
     * @example
     * new Releaser({
     *      vendor: "shin",
     *      code: "news",
     *      version: "1.0.0"
     * }, config).getFileName();
     * // => shin-news-1.0.0.zip
     *
     * @example
     * new Releaser({
     *      directory: "to/a/dir"
     * }, config).getFileName();
     * // => to/a/dir/shinka-news-0.0.1-a.zip
     *
     * @example
     * new Releaser({
     *      directory: "to/a/dir",
     *      output: "release.zip"
     * }, config).getFileName();
     * // => to/a/dir/release.zip
     */
    getFileName() {
        const {
            output,
            directory,
            vendor = this.config.vendor,
            code = this.config.code,
            semver = this.config.version
        } = this.cmd;

        let filename = `${vendor}-${code}-${semver}.zip`;

        if (output) {
            filename = output;
        }

        if (directory) {
            filename = `${directory}/${filename}`;
        }

        return filename;
    }

    /**
     * Bundles source files for release with `git archive`.
     */
    release() {
        try {
            this.executeArchive();
        } catch (error) {
            this.errorList.push(error);
        }

        this.errorList.length ? this.outputErrors() : this.outputSuccess();
    }

    /**
     * Executes `git archive` to bundle release.
     */
    executeArchive() {
        execSync(`git archive HEAD:src --format zip -o "${this.filename}"`);
    }

    /**
     * Builds error messages.
     * @returns {string[]}
     */
    errors() {
        const { verbose } = this.cmd;

        let str = [chalk.red(`Failed to bundle ${this.filename}`)];

        if (verbose) {
            this.errorList.forEach(error => {
                str.push(`\n${error}`);
            });
        } else {
            str.push(chalk.gray("Rerun with -v for verbose error messages."));
        }

        return str;
    }

    /**
     * Builds success message.
     * @returns {string}
     */
    success() {
        return chalk.green(`Successfully bundled ${this.filename}`);
    }

    /**
     * Outputs error messages.
     */
    outputErrors() {
        const str = this.errors();
        str.forEach(err => this.logger.log(err));
    }

    /**
     * Outputs success message.
     */
    outputSuccess() {
        const str = this.success();
        this.logger.log(str);
    }
}