wsh-packager.js

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.bundleWshFiles = exports._setWsfPath = exports.bundleWsfJob = exports.surroundWithPackageJob = exports.surroundWshCode = exports.bundleVBScriptSrcs = exports.bundleJScriptSrcs = exports.minifyVbsCode = exports.minifyJsCode = void 0;
const tslib_1 = require("tslib");
const fsh = tslib_1.__importStar(require("@tuckn/fs-hospitality"));
const fs = tslib_1.__importStar(require("fs"));
const _ = tslib_1.__importStar(require("lodash"));
const path = tslib_1.__importStar(require("path"));
const UglifyJS = tslib_1.__importStar(require("uglify-js"));
const xmlJs = tslib_1.__importStar(require("xml-js"));
/** @namespace API */
/** @private */
const ARG_ERR = 'TypeError [ERR_INVALID_ARG_VALUE]: ';
/**
 * @private
 * @param {Function} fn - Function where the Error occurred
 * @returns {string} - Returns "  at ${Function.name} (${__filename})"
 */
const _errLoc = (fn) => `\n    at ${fn.name} (${__filename})`;
const WSH_EOL = '\r\n';
/**
 * Minify JScript code
 *
 * @memberof API
 * @param {string} code - A code of JScript
 * @returns {string} - A minified JScript code
 */
function minifyJsCode(code) {
    const minifyingJscriptOptions = {
        ie8: true,
        mangle: false,
    };
    const minifiedObj = UglifyJS.minify(code, minifyingJscriptOptions);
    if (minifiedObj.error)
        throw minifiedObj.error;
    if (minifiedObj.warnings)
        console.warn(minifiedObj.warnings);
    return minifiedObj.code;
}
exports.minifyJsCode = minifyJsCode;
/**
 * Minify VBScript code
 *
 * @memberof API
 * @param {string} code - A code of VBScript
 * @returns {string} - A minified VBScript code
 */
function minifyVbsCode(code) {
    const replaced = code
        .replace(/(^|\r?\n)([^'"]*)(\s*'[^\n]*)/g, '$1$2')
        .replace(/(\r?\n)\s+/g, '$1') // Trim lines
        .replace(/\s+(\r?\n)/g, '$1')
        .replace(/(\r?\n)\r?\n/g, '$1') // Removes blank lines
        .replace(/\s+_\r?\n\s*/g, ' '); // Removes VB CR _
    return replaced;
}
exports.minifyVbsCode = minifyVbsCode;
/**
 * Bundles JScripts
 *
 * @memberof API
 * @param {string[]} scriptPaths - JScript src paths
 * @param {object} options - Optional parameters
 * @param {string} [options.baseDir='.']
 * @param {boolean} [options.minifies=true]
 * @param {string|RegExp} [options.ignoreSrc]
 * @returns {string} - A bundled JScript code
 */
function bundleJScriptSrcs(scriptPaths, options) {
    if (_.isEmpty(scriptPaths)) {
        throw new Error(`${ARG_ERR}scriptPaths is empty.${_errLoc(Function)}`);
    }
    const baseDir = _.get(options, 'baseDir', '.');
    const minifies = _.get(options, 'minifies', true);
    const ignoreSrc = _.get(options, 'ignoreSrc', '');
    let bundledCode = '';
    scriptPaths.forEach((srcPath) => {
        const wshPath = path.resolve(baseDir, srcPath);
        if (ignoreSrc) {
            let ignoreRegExp;
            if (_.isRegExp(ignoreSrc)) {
                ignoreRegExp = ignoreSrc;
            }
            else {
                ignoreRegExp = new RegExp(ignoreSrc, 'i');
            }
            if (ignoreRegExp.test(wshPath))
                return;
        }
        const wshCode = fsh.readAsTextSync(wshPath);
        if (minifies) {
            bundledCode += `${minifyJsCode(wshCode)}${WSH_EOL}`;
        }
        else {
            bundledCode += `${wshCode}${WSH_EOL}`;
        }
    });
    return bundledCode;
}
exports.bundleJScriptSrcs = bundleJScriptSrcs;
/**
 * Bundles VBScripts
 *
 * @memberof API
 * @param {string[]} scriptPaths - VBScript src paths
 * @param {object} options - Optional parameters
 * @param {string} [options.baseDir='.']
 * @param {boolean} [options.minifies=true]
 * @param {string|RegExp} [options.ignoreSrc]
 * @returns {string} - A bundled JScript code
 */
function bundleVBScriptSrcs(scriptPaths, options) {
    if (_.isEmpty(scriptPaths)) {
        throw new Error(`${ARG_ERR}scriptPaths is empty.${_errLoc(Function)}`);
    }
    const baseDir = _.get(options, 'baseDir', '.');
    const minifies = _.get(options, 'minifies', true);
    const ignoreSrc = _.get(options, 'ignoreSrc', '');
    let bundledCode = '';
    scriptPaths.forEach((srcPath) => {
        const wshPath = path.resolve(baseDir, srcPath);
        if (ignoreSrc) {
            let ignoreRegExp;
            if (_.isRegExp(ignoreSrc)) {
                ignoreRegExp = ignoreSrc;
            }
            else {
                ignoreRegExp = new RegExp(ignoreSrc, 'i');
            }
            if (ignoreRegExp.test(wshPath))
                return;
        }
        const wshCode = fsh.readAsTextSync(wshPath);
        if (minifies) {
            bundledCode += `${minifyVbsCode(wshCode)}${WSH_EOL}`;
        }
        else {
            bundledCode += `${wshCode}${WSH_EOL}`;
        }
    });
    // Remove the duplicating "Option Explicit" lines
    if (/Option Explicit/i.test(bundledCode)) {
        bundledCode = bundledCode.replace(/Option Explicit\r?\n/gi, ''); // Remove all
        bundledCode = `Option Explicit${WSH_EOL}${bundledCode}`;
    }
    return bundledCode;
}
exports.bundleVBScriptSrcs = bundleVBScriptSrcs;
/**
 * Surrounds A WSH code with script tag
 *
 * @memberof API
 * @param {string} wshCode - WSH code string
 * @param {string} lang - A value of language attribute. JScript of VBScript
 * @returns {string} - A code surrounded
 */
function surroundWshCode(wshCode, lang) {
    if (!wshCode) {
        throw new Error(`${ARG_ERR}wshCode is empty.${_errLoc(Function)}`);
    }
    if (!lang) {
        throw new Error(`${ARG_ERR}lang is empty.${_errLoc(Function)}`);
    }
    return `<script language="${lang}">${WSH_EOL}${wshCode}${WSH_EOL}</script>${WSH_EOL}`;
}
exports.surroundWshCode = surroundWshCode;
/**
 * Surrounds WSH contents with package and job tags for .wsf file. The "job id" will be "run".
 *
 * @memberof API
 * @param {string} content
 * @returns {string} - A code surrounded
 */
function surroundWithPackageJob(content) {
    if (!content) {
        throw new Error(`${ARG_ERR}content is empty.${_errLoc(Function)}`);
    }
    return `<package>${WSH_EOL}<job id = run>${WSH_EOL}${content}${WSH_EOL}</job>${WSH_EOL}</package>${WSH_EOL}`;
}
exports.surroundWithPackageJob = surroundWithPackageJob;
/**
 * Bundles the job of .wsf into a .wsf file
 *
 * @memberof API
 * @param {object[]} wshObjs
 * @param {object} options - Optional parameters
 * @param {string} [options.baseDir='.']
 * @param {boolean} [options.minifies=true]
 * @param {string|RegExp} [options.ignoreSrc]
 * @returns {string} - A code bundled
 */
function bundleWsfJob(wshObjs, options) {
    if (_.isEmpty(wshObjs)) {
        throw new Error(`${ARG_ERR}wshObjs is empty.${_errLoc(Function)}`);
    }
    const baseDir = _.get(options, 'baseDir', '.');
    const minifies = _.get(options, 'minifies', true);
    const ignoreSrc = _.get(options, 'ignoreSrc', '');
    let bundledCode = '';
    wshObjs.forEach((wshObj) => {
        const wshPath = path.resolve(baseDir, wshObj.attributes.src);
        if (ignoreSrc) {
            let ignoreRegExp;
            if (_.isRegExp(ignoreSrc)) {
                ignoreRegExp = ignoreSrc;
            }
            else {
                ignoreRegExp = new RegExp(ignoreSrc, 'i');
            }
            if (ignoreRegExp.test(wshPath))
                return;
        }
        const wshCode = fsh.readAsTextSync(wshPath);
        const lang = wshObj.attributes.language;
        if (/^vbscript$/i.test(lang)) {
            if (minifies) {
                bundledCode += surroundWshCode(minifyVbsCode(wshCode), 'VBScript');
            }
            else {
                bundledCode += surroundWshCode(wshCode, 'VBScript');
            }
        }
        else if (/^jscript$/i.test(lang)) {
            if (minifies) {
                bundledCode += surroundWshCode(minifyJsCode(wshCode), 'JScript');
            }
            else {
                bundledCode += surroundWshCode(wshCode, 'JScript');
            }
        }
    });
    return surroundWithPackageJob(bundledCode);
}
exports.bundleWsfJob = bundleWsfJob;
/**
 * @private
 * @param {string} source - A directory path or .wsf path
 * @returns {string} - .wsf path
 */
function _setWsfPath(source) {
    if (!source) {
        throw new Error(`${ARG_ERR}source is empty.${_errLoc(Function)}`);
    }
    const absPath = path.resolve(source);
    const srcStat = fs.statSync(absPath);
    if (srcStat.isDirectory())
        return path.join(absPath, 'Package.wsf');
    if (srcStat.isFile())
        return absPath;
    throw new Error(`${ARG_ERR}Invalid source "${source}".${_errLoc(Function)}`);
}
exports._setWsfPath = _setWsfPath;
/**
 * Bundles .wsf jobs defined in Package.wsf
 *
 * @memberof API
 * @param {string} dirPath - A directory path where Package.wsf is located or the full-path of your .wsf file.
 * @param {object} [options] - Optional parameters
 * @param {string} [options.jobId] - A job id to be bundled (Default: "\\.(js|vbs|wsf)$").
 * @param {string} [options.baseDir] - Default is the `dirPath`
 * @param {string|RegExp} [options.ignoreSrc] - Ex. "main\\.js$"
 * @returns {void}
 * @example
const { bundleWshFiles } = require('@tuckn/wsh-packager');

// Ex1.
// [File structure]
// D:\MyWshFolder\
// ├─ Package.wsf
// └─ src\
//     ├─ Function.js
//     ├─ Object.js
//     └─ JSON.js

// [The content of Package.wsf]
// <package>
//   <job id = "./dist/JSON.min.js">
//     <script language="JScript" src="./src/Function.js"></script>
//     <script language="JScript" src="./src/Object.js"></script>
//     <script language="JScript" src="./src/JSON.js"></script>
//   </job>
// </package>

bundleWshFiles('D:\\MyWshFolder');
// [The result]
// D:\MyWshFolder\
// ├─ Package.wsf
// ├─ dist\
// │  └─ JSON.min.js
// └─ src\
//     ├─ Function.js
//     ├─ Object.js
//     └─ JSON.js

// Ex2.
// [File structure]
// D:\MyWshFolder\
// ├─ Package.wsf
// └─ src\
//     ├─ CLI.js
//     ├─ Excel.vbs
//     ├─ Function.js
//     ├─ Object.js
//     ├─ JSON.js
//     └─ Util.vbs

// [The content of Package.wsf]
// <package>
//   <job id = "./dist/MyModule.vbs">
//     <script language="VBScript" src="./src/Util.vbs"></script>
//     <script language="VBScript" src="./src/Excel.vbs"></script>
//   </job>
//   <job id = "./dist/JSON.min.js">
//     <script language="JScript" src="./src/Function.js"></script>
//     <script language="JScript" src="./src/Object.js"></script>
//     <script language="JScript" src="./src/JSON.js"></script>
//   </job>
//   <job id = "./dist/Run.wsf">
//     <script language="JScript" src="./src/Function.js"></script>
//     <script language="VBScript" src="./src/Excel.vbs"></script>
//     <script language="JScript" src="./src/CLI.js"></script>
//   </job>
// </package>

bundleWshFiles('D:\\MyWshFolder');
// [The result]
// D:\MyWshFolder\
// ├─ Package.wsf
// ├─ dist\
// │  ├─ JSON.min.js
// │  ├─ MyModule.vbs
// │  └─ Run.wsf
// └─ src\
//     ├─ CLI.js
//     ├─ Excel.vbs
//     ├─ Function.js
//     ├─ Object.js
//     ├─ JSON.js
//     └─ Util.vbs
 */
function bundleWshFiles(dirPath, options) {
    if (!dirPath) {
        throw new Error(`${ARG_ERR}dirPath is empty.${_errLoc(Function)}`);
    }
    const srcWsfPath = _setWsfPath(dirPath);
    const srcWsfCode = fsh.readAsTextSync(srcWsfPath);
    // Parsing
    const xml2jsOptions = {
        alwaysArray: true,
        attributesKey: 'attributes',
        compact: true,
        ignoreDeclaration: true,
        ignoreCdata: true,
        ignoreComment: true,
        ignoreDoctype: true,
    };
    const wsfObj = xmlJs.xml2js(srcWsfCode, xml2jsOptions);
    const packageObj = _.get(wsfObj, 'package', []);
    if (_.isEmpty(packageObj)) {
        throw new Error(`[ContentError] The package tag is not defined in "${srcWsfPath}".${_errLoc(Function)}`);
    }
    const jobObjs = _.get(packageObj[0], 'job', []);
    if (_.isEmpty(jobObjs)) {
        throw new Error(`[ContentError] The job tag is not defined in "${srcWsfPath}".${_errLoc(Function)}`);
    }
    // Bundling
    const baseDir = _.get(options, 'destDir', path.dirname(srcWsfPath));
    const bundlingJobId = _.get(options, 'jobId', '\\.(js|vbs|wsf)$');
    const bundlingRegExp = _.isRegExp(bundlingJobId)
        ? bundlingJobId
        : new RegExp(bundlingJobId, 'i');
    const ignoreSrc = _.get(options, 'ignoreSrc', '');
    const writingOptions = {
        trim: 'end',
        eol: WSH_EOL,
        bom: true,
        encoding: 'utf8',
    };
    jobObjs.forEach(async (jobObj) => {
        let bundledCode = '';
        const jobId = jobObj.attributes.id;
        // Filtering
        if (!bundlingRegExp.test(jobId))
            return;
        const destWshPath = path.resolve(baseDir, jobId);
        const extName = path.extname(destWshPath);
        const scriptObjs = jobObj.script;
        if (/\.wsf$/i.test(extName)) {
            bundledCode = bundleWsfJob(scriptObjs, { baseDir, ignoreSrc });
        }
        else {
            const scriptPaths = scriptObjs.map((o) => o.attributes.src);
            if (/\.js$/i.test(extName)) {
                bundledCode = bundleJScriptSrcs(scriptPaths, { baseDir, ignoreSrc });
            }
            else if (/\.vbs$/i.test(extName)) {
                bundledCode = bundleVBScriptSrcs(scriptPaths, { baseDir, ignoreSrc });
            }
            else {
                return;
            }
        }
        fsh.writeAsTextSync(destWshPath, bundledCode, writingOptions);
    });
}
exports.bundleWshFiles = bundleWshFiles;
//# sourceMappingURL=wsh-packager.js.map