exports_index.js

/**
 * @file exports/index.js
 * @copyright @spmhome @_2025
 * @author Scott Meesseman @spmeesseman
 *//** */

const cache = require("./cache");
const entry = require("./entry");
const rules = require("./rules");
const stats = require("./stats");
const watch = require("./watch");
const output = require("./output");
const devtool = require("./devtool");
const plugins = require("./plugins");
const resolve = require("./resolve");
const externals = require("./externals");
const ignoreWarnings = require("./ignore");
const experiments = require("./experiments");
const optimization = require("./optimization");
const infrastructureLogging = require("./infrastructure");
const { printBuildProperties } = require("../utils/print");
const { existsSync, resolvePath } = require("@spmhome/cmn-utils");
const { isEmpty, asArray, isString } = require("@spmhome/type-utils");
const { SpmhMessageUtils, withColor } = require("@spmhome/log-utils");


// /**
//  * @type {Record<WpwBuildOptionsExportKey, typeof WpwWebpackExport>}
//  */
const webpackExportModules = {
    experiments, cache, entry, externals, optimization, output, resolve, devtool, rules, watch,
    ignoreWarnings, infrastructureLogging, stats, plugins
};


/**
 * @param {WpwBuild} build
 * @returns {WpwWebpackConfig}
 */
const webpackDefaultExports = (build) =>
{
    const b = build, p = build.pkgJson;
          // recordsDir = join(b.virtualEntry.dir, "records");
    // if (!existsSync(recordsDir)) {
    //     createDirSync(recordsDir);
    // }
    /** @type {WpwWebpackConfig} */
    const wpCfg = {
        entry: {},
        stats: {},
        plugins: [],
        resolve: {},
        snapshot: {},
        mode: b.mode,
        watch: false,
        externals: [],
        experiments: {},
        optimization: {},
        target: b.target,
        resolveLoader: {},
        devtool: undefined,
        ignoreWarnings: [],
        module: { rules: [] },
        infrastructureLogging: {},
        context: b.getContextPath(),
        profile: b.logger.level >= 4,
        parallelism: 8 * b.buildCount,
        // recordsPath: join(recordsDir, "index.json"),
        // recordsInputPath: join(recordsDir, "input.json"),
        // recordsOutputPath: join(recordsDir, "output.json"),
        cache: { type: "filesystem", buildDependencies: {} },
        output: { path: b.getDistPath({ rel: true }) }, // eslint-disable-next-line stylistic/max-len
        name: `${p.scopedName.name} v${p.version} [build::${b.name}] [type::${b.type}] [mode::${b.mode}] [tgt::${asArray(b.target).join("|")}]`
    };
    return wpCfg;
};


/**
 * @param {string} name
 * @param {function(WpwBuild): WpwWebpackExport} createFn
 * @param {WpwBuild} build
 * @param {WpwWebpackConfig} wpc
 * @returns {any}
 */
const exportConfig = (name, createFn, build, wpc) =>
{
    const xTags = [ "exports", name ],
          initializationMsg = `create '${name.toLowerCase()}' configuration [${build.type}][${asArray(build.target).join("|")}]`;

    build.wpc = wpc;
    if (!build.hasError)
    {
        const cStaticPad = build.logger.staticPad;
        build.logger.start(initializationMsg, 1);
        build.logger.staticPad += "   ";
        try {
            createFn(build);
        }
        catch(e)
        {   build.addMessage({
                exception: e,
                code: SpmhMessageUtils.Code.ERROR_EXPORT_FAILED,
                message: `failed to ${initializationMsg}`
            }, true);
        }
        finally {
            build.logger.staticPad = cStaticPad;
        }
        if (!build.hasError) {
            build.logger.success(initializationMsg, 1, "   ", !build.hasError, xTags);
        }
        else {
            build.logger.fail(initializationMsg, 1, "   ", !build.hasError, [ "invalid", ...xTags ]);
        }
    }
    else
    {   build.logger.success(
            initializationMsg, 1, "   ", false,  [ "skipped", ...xTags ], build.hasError
        );
    }
};


/**
 * @param {WpwBuild} build
 * @param {string} lPad
 * @returns {WpwWebpackConfig}
 * @throws {Error}
 */
const webpackExports = (build, lPad) =>
{
    const l = build.logger.write(
        `create webpack configuration for ${build.name}::${asArray(build.target).join("|")}`, 1, lPad
    );
    try
    {   let isRebuild = build.isRebuild;
        const wpc = webpackDefaultExports(build);
        if (build.errors.length > 0)
        {   l.write("unable to configure webpack exports:", undefined, "", l.icons.color.error);
            build.errors.splice(0).forEach(e => { l.sep(undefined, l.icons.color.error); l.error(e); });
            l.sep(undefined, l.icons.color.error);
            throw new Error("failed to configure webpack exports [details in log output]");
        }
        if (!build.isRebuild && isString(wpc.output.filename, true) && isString(wpc.output.path, true))
        {   let name = wpc.output.filename;
            if (name === "[name].js") {
                name = `${build.name}.js`;
            }
            if (name) {
                isRebuild = !existsSync(resolvePath(wpc.output.path, name));
            }
        }
        if (isRebuild) {
            wpc.name = wpc.name.replace("build::", "rebuild::");
        }
        this._state = "running";
        l.blank(1);
        Object.entries(webpackExportModules).forEach(([ n, m ]) => { exportConfig(n, m, build, wpc); });
        Object.keys(build.wpc).forEach((k) => { if (isEmpty(build.wpc[k])) { delete build.wpc[k]; }});
        printBuildProperties(build, undefined, "   ");
        l.success(`successfully configured webpack exports for build '${this.name}'`, 1, lPad);
        l.blank(1);
        return wpc;
    }
    catch (e)
    {   l.error("dump invalid configuration:");
        printBuildProperties(build, withColor(l.icons.error, l.color), "");
        l.blank(1, withColor(l.icons.error, l.color));
        throw e;
    }
}


module.exports = webpackExports;
module.exports.webpackExportModules = webpackExportModules;