core_wrapper.js

/**
 * @file src/core/wrapper.js
 * @copyright @spmhome @_2025
 * @author Scott Meesseman @spmeesseman
 *//** */

const os = require("os");
const WpwBase = require("./base");
const WpwBuild = require("./build");
const { join, sep } = require("path");
const WpwLogger = require("../log/log");
const WpwCache = require("../services/cache");
const { wpwPath, cleanupBuildDone } = require("../utils/utils");
const { printWpwProperties } = require("../utils/print");
const { applyVendorMods } = require("../utils/vendormod");
const { getRcConfig, SpmhSchema } = require("@spmhome/app-utils");
const { WpwBuildBaseConfigKeys, WpwSchemaKeys } = require("../types/constants");
const { getRandomColor, SpmhError, SpmhMessageUtils } = require("@spmhome/log-utils");
const { STATS_JSON_SLUG, CRYPTO_KEY_DEFAULT_128, CRYPTO_KEY_DEFAULT_256 } = require("../utils/constants");
const {
    findExPathSync, existsAsync, existsSync, createDirAsync, findFiles, findExPath, isAbsPath, joinPath,
    resolvePath, findDotJsonFileUpSync
} = require("@spmhome/cmn-utils");
const {
    apply, isArray, pick, merge, asArray, applyIf, pickNot, mergeStrong, mergeIf, isObject, uniq, isString,
    safeStringify, pushReturnOne, pluralize, create,
    isFunction
} = require("@spmhome/type-utils");
const webpackExports = require("../exports");


/**
 * @augments WpwBase
 * @implements {IWpwSchema}
 */
class WpwWrapper extends WpwBase
{
    /**
     * @type {boolean}
     */
    autoConfig;
    /**
     * @type {WebpackRuntimeArgs}
     */
    cliWp;
    /**
     * @type {WpwBuild[]}
     */
    builds = [];
    /**
     * @type {IWpwBuildConfig[]}
     */
    buildConfigs = [];
    /**
     * @private
     * @type {WpwBuildLibraryConfig}
     */
    _library;
    /**
     * @type {IWpwSchema}
     */
    _config;
    /**
     * @type {WpwCrypto}
     */
    crypto;
    /**
     * @private
     * @type {string}
     */
    _x0 = "878bcdd309ff6855";
    /**
     * @private
     * @type {string}
     */
    _x1 = "706d23bc2f154886";
    /**
     * @private
     * @type {string}
     */
    _x2 = "ae9c1cc6a2272923";
    /**
     * @type {WpwBuildEnvironmentConfig}
     */
    development;
    /**
     * @type {WpwLogOptions}
     */
    log;
    /**
     * @private
     * @type {boolean}
     */
    _disposed = false;
    /**
     * @private
     * @type {WpwLogger}
     */
    _logger;
    /**
     * @type {WpwWebpackMode}
     */
    mode;
    /**
     * @type {WpwBuildOptions}
     */
    options = {};
    /**
     * @type {WpwRcPaths}
     */
    paths;
    /**
     * @private
     * @type {WpwPackageJson}
     */
    _pkgJson;
    /**
     * @private
     * @type {string}
     */
    _pkgJsonDir;
    /**
     * @private
     * @type {string}
     */
    _pkgJsonPath;
    /**
     * @type {WpwBuildEnvironmentConfig}
     */
    production;
    /**
     * @type {string}
     */
    $schema;
    /**
     * @type {SpmhSchema}
     */
    _schema;
    /**
     * @type {VersionString}
     */
    _schemaVersion;
    /**
     * @type {IWpwSourceCodeConfig}
     */
    source;
    /**
     * @private
     * @type {WpwCache}
     */
    _stats;
    /**
     * @private
     * @type {string}
     */
    _statsfile;
    /**
     * @private
     * @type {WpwBuildTargetConfig}
     */
    _target;
    /**
     * @description Builds in 'test' mode, i.e. 'none'
     * @type {WpwBuildEnvironmentConfig}
     */
    test;
    /**
     * @private
     * @type {WpwBuildTypeConfig}
     */
    _type;
    /**
     * @type {WpwGlobalSettings}
     */
    settings;
    /**
     * @private
     * @type {WpwRcPaths}
     */
    _vpaths;
    /**
     * @private
     * @type {WebpackType}
     */
    _wp;


    /**
     * @param {WpwCmdLineArgs} cli
     * @param {WebpackType} wp
     */
    constructor(cli, wp)
    {
        super();
        this._wp = wp;
        this._cli = merge({}, cli);
        this._schema = new SpmhSchema("WpwSchema", this._path_.resolve(wpwPath(), "schema/spmh.wpw.schema.json"));
        this._schemaVersion = this._schema.version();
    }


    get buildCount() { return this.builds.length; }
    get cacheDir() { return this.settings.cacheDir; }
    get cacheDirGlobal() { return this.global.cacheDir; }
    get cli() { return this._cli; }
    get isSingleBuild() { return this.builds.length === 1; }
    get library() { return this._library; }
    /** @private */set library(v) { this._library = this._obj_.merge({}, v); }
    get logger() { return this._logger; }
    get pkgJson() { return this._pkgJson; }
    get pkgJsonFilePath() { return this._pkgJsonPath; }
    get schema() { return this._schema; }
    get schemaVersion() { return this._schemaVersion; }
    get stats() { return this._stats.data; }
    get statsfile() { return this._statsfile; }
    get target() { return this._target; }
    /** @private */set target(v) { this._target = this._obj_.merge({}, v); }
    get type() { return this._type; }
    /** @private */set type(v) { this._type = this._obj_.merge({}, v); }
    get vpaths() { return this._vpaths; }
    /** @private */set vpaths(v) { this._vpaths = this._obj_.merge({}, v); }


    /**
     * Application entry point to a cli  / startup
     *
     * @param {WpwCmdLineArgs} cli
     * @param {WebpackType} wp
     */
    static async create(cli, wp)
    {
        const wpw = new WpwWrapper(cli, wp);
        await wpw.init.call(wpw);
        return wpw;
    }


    /**
     * @returns {Promise<WpwBuild[]>}
     */
    async createBuilds()
    {
        const l = this.logger,
              arg = this.cli.build?.split(",").map((b) => b.trim());

        l.write("prepare build run", 1);
        l.value("   cli --build argument", arg || "none [all]", 2);

        await this.createBuildConfigs();
        const activeConfigs = this.buildConfigs.filter((b) => !b.disabled && (!arg || arg.includes(b.name)));

        if (activeConfigs.length > 0)
        {
            const vMsg = `   ${activeConfigs.length} active ${pluralize("configuration", activeConfigs.length)}`;
            if (l.level >= 3) {
                l.value(vMsg, safeStringify(activeConfigs, null, 3), 3, "   ");
            }
            else {
                l.value(vMsg, safeStringify(activeConfigs.map((c) => ({ name: c.name }))), 1);
            }
            this.builds.push(...activeConfigs.map((cfg) => new WpwBuild(this, this._wp, cfg)));
            await Promise.all(this.builds.map((b) => b.init("      ")));

            if (activeConfigs.find((c) => !!c.options.wait?.enabled || (isArray(c) && !c.includes(c.entry)) ||
                isObject(c.entry) && !isArray(c.entry) && !!c.entry.dependOn))
            {
                const dependConfigs = await this.getDependencyBuilds("   ");
                if (dependConfigs.length > 0)
                {
                    const dBuilds = dependConfigs.map((cfg) => new WpwBuild(this, this._wp, cfg));
                    await Promise.all(dBuilds.map((b) => b.init("      ")));
                    this.builds.push(...dBuilds);
                }
                l.write(`   created ${this.builds.length} build instances [dependencies::${dependConfigs.length}]`, 1);
            }
            else {
                l.write(`   created ${this.builds.length} build instances`, 1);
            }

            let xPortCt = 0, pluginCt = 0;
            l.write("   configure webpack configuration exports for each active build", 1);
            this.builds.map((b) => webpackExports(b, "      ")).forEach((wpc) => {
                pluginCt += wpc.plugins.length; xPortCt += Object.keys(wpc).length;
            });
            l.values([
                [ "active builds", this.builds.length ], [ "webpack configs", xPortCt ], [ "plugin instances", pluginCt ]
            ], 1, "   ", false, "completed webpack exports configuration");

            l.success("initialization complete, ready to start webpack multi-compiler", 1);
        }
        else
        {   l.warning("   there are no active builds, dumping full configuration object:");
            l.write(safeStringify(this.buildConfigs, null, 3), 1, "   ", l.icons.color.warning);
            l.write("configuration dump complete");
        }

        return this.builds;
    }


	/**
	 * @private
	 */
    async createBuildConfigs()
    {
        /** @type {WpwBuildEnvironmentConfig} */
        const modeConfig = this[this.mode] || {},
              modeBuildConfigs = asArray(modeConfig.builds),
              baseBuildConfigs = asArray(this.builds).splice(0),
              emptyConfig = () => /** @type {IWpwBuildConfig} */({}),
              modeBaseConfig = this.getBasePropertyConfig(modeConfig),
              rootBaseConfig = this.getBasePropertyConfig(this._config),
              gDefaults = this._schema.defaults("WpwBuildOptionsGroup"),
              eDefaults = this._schema.defaults("WpwBuildOptionsExport"),
              pDefaults = this._schema.defaults("WpwBuildOptionsPlugin"),
              l = this.logger.write("generate all possible build configurations", 1);

        l.value("   has base build configuration(", !!this[this.mode] ,1);
        l.value("   has mode build configuration", !!modeConfig.builds , 1);
        l.write(`   merge ${baseBuildConfigs.length} base-level build configurations`, 2);

        for (const config of baseBuildConfigs)
        {
            l.value("   merge base & base-level build configurations", config.name, 3);
            if (config.type && this.type[config.type]) {
                merge(config, this.type[config.type]);
            }
            if (config.target) {
                this._arr_.asArray(config.target).filter((t) => !!this.target[t]).forEach((target) => {
                    merge(config, this.target[target]);
                });
            }
            if (config.library && this.library[config.library]) {
                merge(config, this.library[config.library]);
            }
            this.buildConfigs.push(merge(emptyConfig(), rootBaseConfig, config, modeBaseConfig));
        }   //
           // Process the current environment's confsig.  Add all builds defined in the env config that
          // aren't defined at root level, and apply the base config and mode config to each. If the
         // build "is" defined already at root level, merge in the environment config.
        //
        l.write(`   merge ${modeBuildConfigs.length} build-level mode configurations`, 2);
        for (const config of modeBuildConfigs)
        {
            let buildConfig = this.buildConfigs.find((bc) => bc.name === config.name);
            if (!buildConfig) {
                buildConfig = pushReturnOne(this.buildConfigs, merge(emptyConfig(), rootBaseConfig, modeBaseConfig));
            }
            if (buildConfig.type && config.type?.[buildConfig.type]) {
                merge(buildConfig, config.type[buildConfig.type]);
            }
            if (buildConfig.target && config.target) {
                this._arr_.asArray(config.target).filter((t) => !!config.target[t]).forEach((target) => {
                    merge(buildConfig, config.target[target]);
                });
            }
            if (buildConfig.library && config.library?.[buildConfig.library]) {
                merge(buildConfig, config.library[buildConfig.library]);
            }
            l.value("   merge mode-level build configuration", config.name, 3);
            merge(buildConfig, config);
        } //
         // resolve all configured paths to absolute and apply log colors and transformations
        //
        for (const config of this.buildConfigs)
        {
            applyIf(config, { mode: this.mode, filter: [], suppress: [] });
            config.log.color ||= getRandomColor("wpw");
            config.log.colors = applyIf(config.log.colors,
            {   buildBracket: config.log.color, buildText: "white", default: "default",
                infoIcon: config.log.color, tagBracket: config.log.color
            });
            mergeIf(config.options, gDefaults, eDefaults, pDefaults);
            await this.resolvePaths(this._pkgJsonDir, config.name, config);
        }

        this.logger.write("completesd generation of all possible build configurations", 1);
    }


	/**
	 * @private
     * @param {string | number} lPad
	 */
    async getDependencyBuilds(lPad)
    {
        let cnt = 0;
        const /** @type {WpwBuildConfig[]} */depBuilds = [],
              l = this._logger.write("check dependency builds for inclusion in compilation", 1, lPad);

        const _addBuild = (/** @type {WpwBuildConfig} */cfg) =>
        {   l.write(`   add dependency build '${cfg.name}' to compilation`, 1, lPad);
            const depBuild = depBuilds[depBuilds.push(cfg) - 1];
            if (++cnt && !depBuild.options.wait?.enabled) {
                depBuild.options.wait = apply(depBuild.options.wait, { enabled: true });
            }
        };

        const _addOutputPathMsg = (/** @type {SpmhMessage[]} */ msgs, depBuildCfg) =>
        {   msgs.push(new SpmhError({
                code: SpmhError.Code.INFO_CONFIG_SUGGESTION,
                message: "possible issue with build configuration",
                suggest: `add rc 'assets/output' config for '${depBuildCfg.name}' for determining dependency build status`
        }));};

        const _isBuilt = (/** @type {string} */ entry, /** @type {string} */distPath, /** @type {string[]} */ ...ext) =>
        {   // return Promise.any([
            //    existsAsync(resolvePath(distPath, `${f}.${cfgExt}`)), existsAsync(resolvePath(distPath, `${f}.js`)),
            //     existsAsync(resolvePath(distPath, `${f}.mjs`)), existsAsync(resolvePath(distPath, `${f}.cjs`))
            // ]);
            return /** @type {Promise<string | null | undefined>}*/(new Promise((ok, fail) =>
            {
                const f = this._path_.basename(entry).replace(/\.[a-zA-Z0-9]{1,8}$/, ""),
                      files = uniq([ ...ext.map((e) => `${f}.${e}`), `${f}.js`, `${f}.cjs`, `${f}.mjs` ]);
                findExPath(files, distPath).then(ok).catch(fail);
            }));
        };

        //
        // process the 'entry.dependOn' config of each active build, & add any applicable dependent
        // builds to the 'wait' option items array if not already configured
        //
        for (const build of this.builds.filter((b) => isObject(b.entry)))
        {   for (const entry of Object.entries(build.entry))
            {   if (isObject(entry[1]) && entry[1].dependOn)
                {
                    build.options.wait = mergeStrong(build.options.wait || {}, { enabled: true, items: [] });
                    this._arr_.pushUniq(build.options.wait.items, asArray(entry[1].dependOn)
                              .map((d) => ({ mode: "event", name: d })));
                }
            }
        }

        //
        // process the 'wait' option configured for this build, adding any dependent builds to the
        // current build process if the dependent build output doesn't already exist
        //
        for (const build of this.builds.filter((b) => !!b.options.wait?.enabled))
        {
            if (!asArray(build.options.wait?.items))
            {
                l.write(`   build '${build.name}' has 0 dependencies`, 2, lPad);
                continue;
            }
            l.write(`build '${build.name}' has ${build.options.wait.items.length} dependencies`, 2, lPad);
            for (const wCfg of build.options.wait.items)
            {
                l.write(`   check status of dependency build '${wCfg.name}'`, 2, lPad);
                const exBuild = this.builds.find((b) => b.name === wCfg.name);
                if (exBuild)
                {
                    if (!exBuild.options.wait?.enabled) {
                        exBuild.options.wait = { enabled: true };
                    }
                    l.write("      already in build queue, set wait flag", 2, lPad);
                    continue;
                }

                const dCfg = this.buildConfigs.find((b) => b.name === wCfg.name);
                if (!dCfg)
                {
                    throw new SpmhError({
                        code: SpmhError.Code.ERROR_CONFIG_INVALID,
                        message: `specified build '${wCfg.name}' in 'wait' plugin config does not exist`
                    });
                }

                let isBuilt = false;
                const depDistPath = dCfg.paths.dist,
                      depDistIsDup = !!this.buildConfigs.find((b) => b.name !== wCfg.name && b.paths.dist === depDistPath);

                l.write("      check dist directory for existing output assets", 2, lPad);
                if (!existsSync(depDistPath) || this._cli.clean)
                {
                    l.value("      dist directory of dependency doesn't exist", dCfg.paths.dist, 2, lPad);
                    _addBuild(dCfg);
                    continue;
                }

                if (dCfg.type === "types")
                {
                    const bco =  build.tsc.compilerOptions;
                    l.write("      check 'types' build output", 2, lPad);
                    if (bco.outFile)
                    {
                        isBuilt = await existsAsync(resolvePath(build.paths.base, bco.outFile));
                    }
                    if (!isBuilt && bco.declarationDir && bco.declarationDir !== build.paths.dist)
                    {
                        isBuilt = await existsAsync(resolvePath(build.paths.base, bco.declarationDir));
                    }
                    if (!isBuilt && build.pkgJson.types)
                    {
                        isBuilt = await existsAsync(resolvePath(depDistPath, build.pkgJson.types));
                    }
                    if (!isBuilt && dCfg.options.types.bundle?.enabled)
                    {
                        const outName = dCfg.options.output?.name || dCfg.name;
                        isBuilt = await Promise.any([
                            existsAsync(join(depDistPath, outName.replace(/\.d\.ts$/, "") + ".d.ts")),
                            existsAsync(join(depDistPath, "types.d.ts")),
                            existsAsync(join(depDistPath, "index.d.ts"))
                        ]);
                    }
                    if (!isBuilt)
                    {
                        const srcPattern = "**/*.{js,ts,cjs,mjs,cts,mts,jsx,tsx}",
                              srcCnt = (await findFiles(srcPattern, { cwd: dCfg.paths.src })).length;
                        isBuilt = [ srcCnt, 1 ].includes((await findFiles("**/*.d.ts", { cwd: depDistPath })).length);
                    }
                }
                else if (dCfg.type === "script")
                {
                    l.write("      check 'script' build output", 2, lPad);
                    let addSuggest = false;
                    const opts = dCfg.options.script,
                          paths = asArray(opts.assets?.paths);
                    isBuilt = (paths.length === 0 || paths.every((p) => findExPathSync([
                        resolvePath(dCfg.paths.dist, p), resolvePath(dCfg.paths.base, p), resolvePath(build.getRootDistPath(), p)
                    ])));
                    if (isBuilt)
                    {
                        asArray(opts?.items).map((s) => asArray(s.paths?.output)).forEach((paths) =>
                        {
                            isBuilt = (paths.length === 0 || paths.every((p) => findExPathSync([
                             resolvePath(depDistPath, p), resolvePath(dCfg.paths.base, p), resolvePath(build.getRootDistPath(), p)
                            ])));
                            addSuggest &&= (paths.length === 0);
                            if (!isBuilt) { return; }
                        });
                    }
                    if (depDistIsDup && addSuggest) { _addOutputPathMsg(build.info, dCfg); }
                }
                else if (dCfg.type === "schema")
                {
                    l.write("      check 'schema' build output", 2, lPad);
                    let addSuggest = false;
                    const opts = dCfg.options.schema,
                          paths = [ dCfg.options.schema.jsonPath, ...asArray(opts.scripts.assets?.paths) ];
                    isBuilt = (paths.length === 0 || paths.every((p) => findExPathSync([
                        resolvePath(depDistPath, p), resolvePath(dCfg.paths.base, p), resolvePath(build.getRootDistPath(), p)
                    ])));
                    if (opts.scripts && isBuilt)
                    {
                        asArray(opts.scripts.items).map((s) => asArray(s.paths?.output)).forEach((paths) =>
                        {   isBuilt = (paths.length === 0 || paths.every((p) => findExPathSync([
                             resolvePath(depDistPath, p), resolvePath(dCfg.paths.base, p), resolvePath(build.getRootDistPath(), p)
                            ])));
                            addSuggest &&= (paths.length === 0);
                            if (!isBuilt) { return; }
                        });
                    }
                    if (depDistIsDup && addSuggest) { _addOutputPathMsg(build.info, dCfg); }
                }
                else if (dCfg.type === "resource")
                {
                    l.write("      check resource type output", 2, lPad);
                    const path = dCfg.options.resource.output,
                          paths = asArray(dCfg.options.resource.assets?.paths);
                    isBuilt = (paths.length === 0 || paths.every((p) => findExPathSync([
                        resolvePath(depDistPath, p), resolvePath(build.getRootDistPath(), p)
                    ]))) && !!findExPathSync([ resolvePath(dCfg.paths.dist, path), resolvePath(build.getRootDistPath(), path) ]);
                }

                if (!isBuilt && dCfg.entry)
                {
                    l.write("      check existing entry configuration", 2, lPad);
                    const cfgExt = dCfg.options.output.ext?.replace(/^\./, "") || "js";
                    if (isString(dCfg.entry))
                    {
                        isBuilt = !!await _isBuilt(dCfg.options.output?.name || dCfg.entry, depDistPath, cfgExt);
                    }
                    else if (isObject(dCfg.entry))
                    {
                        const eKeys = Object.keys(dCfg.entry);
                        isBuilt = (await Promise.all(eKeys.map((e) => _isBuilt(e, depDistPath, cfgExt)))).every((r) => !!r);
                    }
                }

                if (!isBuilt)
                {
                    if (!depDistIsDup) {
                        isBuilt = existsSync(depDistPath);
                    }
                    else { _addOutputPathMsg(build.info, dCfg); }
                }

                build.options.wait.enabled = !isBuilt;
                if (!isBuilt) {
                    l.write("      all checks complete, dependency build required for this compilation", 2, lPad);
                    _addBuild(dCfg);
                }
            }
        }

        if (cnt > 0) {
            l.write(`      added ${cnt} required dependency builds to compilation`, 1, lPad);
        }

        return depBuilds;
    }


    /**
     * @override
     */
    dispose()
    {
        if (!this._disposed)
        {   this.builds.splice(0).forEach((b) => b.dispose());
            this.disposables.splice(0).forEach((d) => d.dispose());
            this._disposed = true;
        }
    }


    /**
     * @private
     * @param {WpwBuildEnvironmentConfig} config
     * @returns {IWpwBuildBaseConfig}
     */
    getBasePropertyConfig(config) { return config ? pick(config, ...WpwBuildBaseConfigKeys) : {}; }


    /**
     * @param {string} nameOrType
     * @param {boolean} [isName]
     * @returns {WpwBuild | undefined}
     */
    getBuild(nameOrType, isName)
    {
        return this.builds.find((b) => b.name === nameOrType || (!isName && b.type === nameOrType));
    }


    /**
     * @param {string} nameOrType
     * @param {boolean} [isName]
     * @returns {IWpwBuildConfig | undefined}
     */
    getBuildConfig(nameOrType, isName)
    {
        return this.buildConfigs.find((b) => b.name === nameOrType || (!isName && b.type === nameOrType));
    }


    /**
     * @param {(function(IWpwBuildConfig): boolean)} cb
     * @param {any} thisArg
     */
    getBuildConfigBy(cb, thisArg) { return this.buildConfigs.find(cb.bind(thisArg || cb)) || {}; }


    /**
     * @private
     * @returns {WebpackMode}
     */
    getMode()
    {
        return this.cli.mode || this.cliWp?.mode || "production";
    }


    /**
     * @param {boolean} [web]
     * @returns {IWpwRcPaths}
     */
    getRootPathsConfig(web) { return this.getBuildConfig(!web ? "app" : "webapp")?.paths || this._config.paths; }


    /**
     * @param {boolean} [web]
     * @returns {string}
     */
    getRootSrcPath(web) { return this.getRootPathsConfig(web).src || this.buildConfigs[0].paths.src || process.cwd(); }


    /**
     * @param {Error} e
     */
    handleInitFailure(e)
    {
        let m, p = "";
        const l = this.logger,
              n = l.tag("1", l.color, "white"),
              separator = l.sep(1, null, "error", true),
              hasMessages = this.builds?.filter((b) => b.hasErrorOrWarning);

        l.sep(1, l.icons.color.error, "error", false, true);
        l.fail("failed to start", 1, "", true);
        l.sep(1, l.icons.color.error, "error", false, true);

        if (SpmhMessageUtils.isSpmh(e))
        {   const xMsg = e.xMessage.toString();
            if ((m = xMsg.match(/^( +)/)) !== null) { p = m[1]; }
            e.xMessage.setXMessage(`white(ERROR # ${n} of ${n}:)\n${p}${separator}\n${xMsg}\n${p}${separator}`, true);
            l.write(e, undefined, "", l.icons.color.error);
        }
        else
        {   const stack = e.stack.replace(/\n/g, "\n".padEnd(l.preMsgTagLen));
            // const stack = e.stack.replace(/\n/g, "\n".padEnd(l.messagePrefix(null, false, true).length));
            l.write(e.message, undefined, "", l.icons.color.error);
            l.write(stack, undefined, "", l.icons.color.error);
        }

        l.sep(1, l.icons.color.error, "error", false, true);
        if (hasMessages.length > 0)
        {   hasMessages.forEach((b) => b.printMessages(true));
            l.sep(1, l.icons.color.error, "error", false, true);
        }
        for (const b of this.builds.filter((b) => isFunction(b.dispose)))
        {   l.value(`${b.name} rc configuration dump`, b.config);
            try {
                cleanupBuildDone(b, l);
            } catch {}
        }

        l.fail("done", 1, "", true);
        l.sep(1, l.icons.color.error, "error", false, true);
        console.log("", "internal");
    }


    /**
     * @private
     */
    async init()
    {
        try
        {   const p = this.initPackageJson(),
                  n = p.scopedName;

            this.mode = this.getMode();
            this._config = await getRcConfig("wpwrc", "AP_", /** @type {IWpwSchema} */({}));
            this._config.settings ||= {};
            this._config.settings.project ||= {};
            this._config.crypto ||= { type: "none" };
            mergeIf(this._config.settings.project, { name: p.name, slug: n.name || p.name, scope: n.scope || "@", group: "" });
            mergeIf(this._config, this._schema.defaults("WpwSchema"));
            // this._obj_.cleanPrototype(this._config, ...Object.keys(this._config.library));
            this.initialConfig = this._obj_.clone(this._config);

            this.initLoggerConfig();
            await this.initCacheConfig();
            await this.resolvePaths(
                this._pkgJsonDir, this._config.settings.project?.name || p.scopedName.name || "wpw", this._config
            );

            this._config.type ||= create();
            this._config.target ||= create();
            this._config.library ||= create();
            this._config.vpaths ||= create({ base: this._config.paths.dist || "" });
            this._arr_.popBy(this._config.builds, (bc) => bc.disabled === true);
            this._arr_.popBy(this._config.test?.builds , (bc) => bc.disabled === true);
            this._arr_.popBy(this._config.production?.builds , (bc) => bc.disabled === true);
            this._arr_.popBy(this._config.development?.builds , (bc) => bc.disabled === true);

            this._logger = WpwLogger.getLoggerInst(this._config.log, this._schema.defaults("WpwLogOptions"));

            this._logger.start("initialize new build pipeline", 1);

            if (this._config.crypto.type !== "none")
            {
                try
                {   if (this._config.crypto.key === "default")
                    {
                        if (this._config.crypto.strength === "128-bit")
                        {
                            this._config.crypto.key = CRYPTO_KEY_DEFAULT_128.substring(0, 16) + this._x0;
                        }
                        else {
                            this._config.crypto.key = `${CRYPTO_KEY_DEFAULT_256.substring(0, 16)}${this._x1}` +
                                              `${this.crypto.key.substring(32, 48)}${this._x2}`;
                        }
                    }
                    this._stats = new WpwCache({
                        dir: this.global.cacheDir, slug: STATS_JSON_SLUG, logger: this._logger, crypto: this.crypto
                    });
                    if (!this._stats.data.applyVendorMods)
                    {
                        this._stats.set({ applyVendorMods: true });
                        if (await applyVendorMods({ all: true }, this._logger)) {
                            throw new Error("l2_handled");
                        }
                    }
                } finally { }// store?.dispose(); }
            }

            merge(this, this._config);

            if (this.cli.help || this.cli.version) {
               return this;
            }

            this._schema.validate(this._config, "WpwSchema", this._logger, "", WpwSchemaKeys);
            printWpwProperties(this, undefined, "   ");

            // this.disposables.push(this._tsc = new WpwTscService({ owner: this }));
            await this.createBuilds();
            this.disposables.push(this.global.globalEvent, ...this.builds, this._logger);

            this._logger.success("completed build pipeline initialization", 1);
            return this;
        }
        catch(e)
        {   if (e && !/l[1-9]_handled/i.test(e.message || ""))
            {   if (this._logger)
                {   this._logger.blank(1,  this._logger.icons.color.error);
                    this.handleInitFailure(e);
                } else { console.error(e); }
            } throw new Error("l3_handled");
        }
    }


    /**
     * @private
     */
    async initCacheConfig()
    {
        this.global.cacheDir = !this._config.settings.cacheDir ?
                               resolvePath(this._pkgJsonDir, "node_modules/.cache") :
                               (!isAbsPath(this._config.settings.cacheDir) ?
                               resolvePath(this._config.settings.cacheDir) : this._config.settings.cacheDir);
        this._config.settings.cacheDir = joinPath(this.global.cacheDir, "wpw");
        if (!existsSync(this._config.settings.cacheDir)) {
            await createDirAsync(this._config.settings.cacheDir);
        }
        this._statsfile = joinPath(this.global.cacheDir, `.${STATS_JSON_SLUG}`);
    }


    /**
     * @private
     */
    initLoggerConfig()
    {
        mergeIf(this._config.log,
        {
            envTag1: "spmh", envTag2: "wpw", level: 2, color: "spmh_blue",
            prefix: merge(this._config.log.prefix, { tag1: "spmh", tag2: "wpw", tag3: "wrapper" })
        });
        if (WpwLogger.isSpmhLogLevel(this.cli.loglevel)) {
            this._config.log.level = WpwLogger.toWpwLogLevel(this.cli.loglevel);
        }
    }


    /**
     * @private
     */
    initPackageJson()
    {
        const pkgJsonFile = findDotJsonFileUpSync("package.json");
        this._pkgJsonDir = pkgJsonFile.dir;
        this._pkgJsonPath = pkgJsonFile.path;
        this._pkgJson = merge({}, pkgJsonFile.data);
        return this._pkgJson;
    }


    /**
     * @private
     * @param {string} base
     * @param {string} name
     * @param {Partial<IWpwSchema | IWpwBuildConfig>} config
     */
    async resolvePaths(base, name, config)
    {
        let paths = config.paths; // @ts-ignore
        const osTmp = os.tmpdir ? os.tmpdir() : os.tmpDir(),
              defTmpDir = join(this._config.settings.cacheDir, name || "default", "temp"),
              tempDir = osTmp ? `${osTmp}${sep}@spmhome${sep}${name}`.replace(`@spmhome${sep}@spmhome`, "@spmhome") : defTmpDir;

        if (!paths) {
            config.paths = apply(paths, {}); paths = config.paths;
        }

        paths.base = base;
        paths.dist = resolvePath(base, paths.dist || "dist");
        const ctx = paths.ctx ? resolvePath(base, paths.ctx) : base;
        // if(!paths.ctx&&!ctx.endsWith("src")&&(await existsAsync(joinPath(ctx,"src")))){paths.ctx=joinPath(ctx,"src");}
        paths.src = resolvePath(paths.ctx && !paths.src ? ctx : (paths.base, paths.src || (!paths.ctx ? "src" : ".")));
        paths.ctx = ctx;
        paths.temp = paths.temp && paths.temp !== defTmpDir ? paths.temp : tempDir;

        if (!(await existsAsync(paths.temp))) { await createDirAsync(paths.temp); }

        Object.entries(pickNot(paths, "dist", "temp")).forEach((e) =>
        {
            if (e[0] !== "src")
            {   if (!existsSync(e[1]))
                {   throw new SpmhError({
                        code: SpmhError.Code.ERROR_SCHEMA, message: `build path '${e[0]}'/'${e[1]}' does not exist`
                    });
                }
            }
            else
            {   e[1].split(/;|,|\|/).forEach((p) =>
                {   if (!existsSync(p))
                    {   throw new SpmhError({
                            code: SpmhError.Code.ERROR_SCHEMA, message: `build path '${e[0]}'/'${p}' does not exist`
                        });
                    }
        }); }   });

        return config;
    };


    /**
     * @param {boolean} [keepMaxLine]
     * @param {boolean} [toInstance]
     * @returns {WpwLogger}
     */
    syncLogConfigs(keepMaxLine, toInstance)
    {
        return /** @type {WpwLogger} */(WpwLogger.syncConfigs(this.logger, keepMaxLine, toInstance));
    }
}


module.exports = WpwWrapper;