plugins_schema.js

/**
 * @file plugins/schema.js
 * @copyright @spmhome @_2025
 * @author Scott Meesseman @spmeesseman
 *//** */

const WpwTaskPlugin = require("./basetask");
const { getScriptBuildAssets, getScriptBuildDependencies } = require("../utils/utils");


/**
 * @augments WpwTaskPlugin
 * @example Replace 'NewPluginName' with a name that will match the rc setting, e.g. WpwSchemaPlugin would
 * have an options key of 'schema' in the rc config file.
 */
class WpwSchemaPlugin extends WpwTaskPlugin
{
    /**
     * @param {WpwPluginOptions} options
     */
    constructor(options)
    {
        super("schema", { taskHandler: "executeSchemaBuild", ...options });
        this.buildOptions = /** @type {WpwBuildOptionsPluginConfig<"schema">} */(this.buildOptions); // reset for typings
    }


    /**
     * @override
     */
    static create = WpwSchemaPlugin.wrap.bind(this);


    /**
     * @override
     * @returns {WpwPluginTapOptions<any, any, any>}
     */
    onApply()
    {
        return this._obj_.apply(this.buildOptions.upload ?
        {
            cleanTypesTemporaryFiles: {
                async: true,
                hook: "done",
                callback: this.uploadJsonSchema.bind(this)
            }
        } : {}, super.onApply());
    }


    /**
	 * @returns {Promise<WpwPluginTaskResult | SpmhError>}
     */
    async executeSchemaBuild()
    {
        try
        {   const bo = this.buildOptions,
                  scripts = bo.scripts,
                  assets = scripts.assets || { immutable: false, tag: this.optionsKey };
            await this.execScriptsAsync(scripts, "");
            return {
                tag: assets.tag,
                immutable: !!assets.immutable,
                glob: assets.glob || "*.json",
                paths: getScriptBuildAssets(bo.scripts, bo.jsonPath),
                srcpaths: getScriptBuildDependencies(this.build, bo.scripts, bo.jsonPath)
            };
        }
        catch (e)
        {   this.addMessage(
            {   exception: e,
                code: this.MsgCode.ERROR_PLUGIN_HOOK_FAILED,
                message: "failed to execute 'schema' type build"
            }, true);
        }
    }


    /**
     * @private
     * @param {string} version
     * @param {IWpwConfigApiEndpoint} apiEp
     * @returns {string}
     */
    getPlinkCommand(version, apiEp)
    {
        const plinkCmds = [
            `mkdir -p ${apiEp.path}`,
            `mkdir -p ${apiEp.path}/${version}`,
            `mkdir -p ${apiEp.path}/${version}/template`,
            `mkdir -p ${apiEp.path}/latest`,
            `mkdir -p ${apiEp.path}/latest/template`,
            `rm -f ${apiEp.path}/${version}/*.schema.json"`,
            `rm -f ${apiEp.path}/${version}/template/*.*"`,
            `rm -f ${apiEp.path}/latest/*.schema.json"`,
            `rm -f ${apiEp.path}/latest/template/*.*"`,
            `rm -fr ${apiEp.path}/${version}/template"`,
            `rm -fr ${apiEp.path}/latest/template"`
        ];
        return "plink " +  [
            "-ssh", "-batch", "-pw", apiEp.credential.key, `${apiEp.credential.user}@${apiEp.host}`, plinkCmds.join(";")
        ].join(" ");
    }

    /**
     * @private
     * @param {string} remoteFolder
     * @param {IWpwConfigApiEndpoint} apiEp
     * @returns {string}
     */
    getPscpCommand(remoteFolder, apiEp)
    {
        const path = this._path_.absPath(this.buildOptions.jsonPath);
        return "pscp " + [
            "-pw", apiEp.credential.key, "-q", this._path_.joinPath(path, "*.json"),
            `${apiEp.credential.user}@${apiEp.host}:"${apiEp.path}/${remoteFolder}"`
        ].join(" ");
    }


    /**
     * @returns {Promise<void>}
     */
    async uploadJsonSchema()
    {
        const b = this.build,
              logger = this.logger,
              lPad = "", logPad = "   ",
              bo = this.buildOptions, apiEp = bo.upload,
              version = `v${b.pkgJson.version}`,
              execOptions = { cwd: b.getContextPath() },
              projSlug = b.wrapper.settings.project.slug || b.pkgJson.scopedName.name;

        apiEp.path ||= `/var/www/spmhome.io/product/${projSlug}/schema`;

        logger.write("upload json schema assets", 1, lPad);
        logger.value("   host", apiEp.host, 2, lPad);
        logger.value("   method", apiEp.method, 2, lPad);
        logger.value("   protocol", apiEp.protocol, 2, lPad);
        logger.value("   local json schema directory", bo.jsonPath, 2, lPad);
        logger.value("   remote json schema base directory", apiEp.path, 2, lPad);

        if (bo.upload.protocol === "pscp")
        {
            const execOpts = { logPad, execOptions };
            try
            {   logger.write("   create / clean json schema remote directories", 1, lPad);
                let command = this.getPlinkCommand(version, apiEp);
                let result = await this.exec(command, "plink", true, execOpts);
                if (result.code !== 0) {
                    throw new Error("failed to prepre remote upload directory [plink]");
                }

                logger.write(`   upload json schema assets to remote directory '~/${version}'`, 1, lPad);
                command = this.getPscpCommand(version, apiEp)
                result = await this.exec(command, "pscp", true, execOpts);
                if (result.code !== 0) {
                    throw new Error(`failed to upload json schema assets to directory '.../v${version}'`);
                }

                logger.write("   upload json schema assets to remote directory '~/latest'", 1, lPad);
                command = this.getPscpCommand("latest", apiEp)
                result = await this.exec(command, "pscp", true, execOpts);
                if (result.code !== 0) {
                    throw new Error("failed to upload json schema assets to directory '/latest'");
                }

                logger.write("successfully uploaded json schema assets", 1, lPad);
            }
            catch (e)
            {   this.addMessage(
                {   exception: e, code: this.MsgCode.ERROR_UPLOAD_FAILED,
                    message: "pscp|plink execution returned failure status"
                });
            }
        }
        else
        {   this.addMessage(
            {   code: this.MsgCode.ERROR_NOT_IMPLEMENTED,
                message: "failed to upload json schema assets",
                detail: `specified protocol '${bo.upload.protocol}' not yet supported`
            }, true);
        }
    }

}


module.exports = WpwSchemaPlugin.create;