/**
* @file src/core/build.js
* @copyright @spmhome @_2025
* @author Scott Meesseman @spmeesseman
* @description main class representing a `build` that produces output assets
* .-. .-. .;;;;. .-. .-.
* .;. .-. (_) )-. .;. .-. (_) )-. .;. .-. ' .;' ` ;' (_) (_) )-.
* `; ;' .: \ `; ;' .: __) ;; ; .;' .:' .: \
* ;; ; .:' ) ;; ; .:' `. ;; : .;' .:' .:' \
* ;; ; ;; .-:. `--' ;; ; ;; :' ) ;; ; .;' .-:. .-. .-:. )
* `;.' `.;' (_/ `;.' `.;' (_/ `----' `;.__.: .;;;;;;;;;' (_/ `;._. (_/ `----'
*/
const WpwBase = require("./base");
const WpwLogger = require("../log/log");
// const WpwTscService = require("../services/tsc");
const WpwSourceCodeService = require("./source");
const { withColor } = require("@spmhome/log-utils");
const WpwBuildOptionsFinalizer = require("./options");
const WpwSnapshotService = require("../services/snapshot");
const { printBuildProperties, printBuildMessages } = require("../utils/print");
const { SpmhWarning, SpmhInfo, SpmhError, SpmhMessageUtils } = require("@spmhome/log-utils");
const { isWpwBuildType, isWebpackTarget, WpwBuildConfigKeys } = require("../types/constants");
const { getUniqBuildKey, isWpwBuildOptionsKey, wpwNodeModulesPath, wpwPath } = require("../utils/utils");
const {
createDirSync, existsSync, relativePath, resolvePath, nodejsModulesGlobalPath, relativePathEx
} = require("@spmhome/cmn-utils");
/**
* @augments WpwBase
* @implements {IWpwBuildConfig}
*/
class WpwBuild extends WpwBase
{
/**
* @private
* @type {SpmhError[]}
*/
static _errorsGlobal = [];
/**
* @private
* @type {IWpwRuntimeExternal[]}
*/
static _nodeModules = [];
/**
* @private
* @type {WebpackCompilation}
*/
_compilation;
/**
* @private
* @type {WebpackCompilation}
*/
_compilationThis;
/**
* @private
* @type {WebpackCompiler}
*/
_compiler;
/**
* @private
* @type {IWpwBuildConfig}
*/
_config;
/**
* @private
* @type {boolean}
*/
_autoConfig;
/**
* @type {WpwCrypto}
*/
crypto;
/**
* @type {boolean}
*/
debug;
/**
* @type {WpwWebpackEntry}
*/
entry;
/**
* @private
* @type {boolean}
*/
_disposed;
/**
* @private
* @type {SpmhError[]}
*/
_errors;
/**
* @private
* @type {RegExp}
*/
_externalsRgx;
/**
* @private
* @type {SpmhMessage[]}
*/
_info;
/**
* @type {WebpackLibrary | undefined}
*/
library;
/**
* @type {WpwWebpackLoaderType | undefined}
*/
loader;
/**
* @type {WpwLogOptions}
*/
log;
/**
* @type {WpwLogger}
*/
logger;
/**
* @type {boolean | undefined}
*/
main;
/**
* @type {WpwWebpackMode}
*/
mode;
/**
* @private
* @type {IWpwRuntimeExternal[]}
*/
_nodeModules;
/**
* @private
* @type {WpwNodeModulesPaths}
*/
_nodeModulesPaths;
/**
* @type {WpwBuildOptions}
*/
options = {};
/**
* @type {WpwRcPaths}
*/
paths;
/**
* @private
* @type {WpwRcVirtualPaths}
*/
_vpaths;
/**
* @private
* @type {boolean}
*/
_rebuild;
/**
* @type {WpwStaticResources}
*/
resources;
/**
* @private
* @type {WpwSourceCode}
*/
_scm;
/**
* @type {WpwSnapshotService}
*/
_ssCache;
/**
* @private
* @type {"idle" | "initializing" | "waiting" | "running" | "done" | "failed" | "disposed"}
*/
_state;
/**
* @type {WebpackTarget | WebpackTarget[]}
*/
target;
/**
* @type {WpwBuildType}
*/
type;
/**
* @type {WpwVirtualEntry}
*/
virtualEntry;
/**
* @private
* @type {number}
*/
_elapsed = 0;
/**
* @private
* @type {{ msg: string, ct: number, tId: number | NodeJS.Timeout, fn: "info" | "error" | "warn"}}
*/
_lastMsgTag = { msg: "", ct: 0, tId: 0, fn: "error" };
/**
* @private
* @type {SpmhWarning[]}
*/
_warnings;
/**
* @private
* @type {WebpackType}
*/
_wp;
/**
* @private
* @type {WpwWebpackConfig}
*/
_wpc;
/**
* @readonly
* @private
* @type {WpwWrapper}
*/
_wrapper;
/**
* @param {WpwWrapper} wpw
* @param {WebpackType} wp
* @param {IWpwBuildConfig} config
*/
constructor(wpw, wp, config)
{
super(config);
this._wp = wp;
this.name = "";
this._info = [];
this._errors = [];
this._warnings = [];
this._nodeModules = [];
this._wrapper = wpw;
this._rebuild = false;
this._disposed = false;
this._state = "initializing";
}
get autoConfig() { return this._autoConfig !== false && this.wrapper.autoConfig !== false; }
/** @private */set autoConfig(v) { this._autoConfig = v; }
get autoConfigTs() { return this._scm.autoConfig === true; }
get builds() { return this._wrapper.builds; }
get buildConfigs() { return this._wrapper.buildConfigs; }
get buildCount() { return this._wrapper.buildCount; }
get cacheDir() { return this.virtualEntry.dirStore; }
get cli() { return this._wrapper.cli; }
get compilation() { return this._compilation; }
set compilation(c) { this._compilation = c; this.ssCache?.updateRuntimeInstances(this._compiler, c); }
get compilationThis() { return this._compilationThis; }
set compilationThis(c) { this._compilationThis = c; }
get compiler() { return this._compiler; }
set compiler(c) { this._compiler = c; }
get config() { return this._config; }
get elapsed() { return this._elapsed; }
set elapsed(v) { this._elapsed = v; }
get errorCount() { return this._errors.length; }
get errors() { return this._errors; }
get errorsGlobal() { return WpwBuild._errorsGlobal; }
get externalsRgx() { return this._externalsRgx; }
set externalsRgx(r) { this._externalsRgx = r; }
get info() { return this._info; }
get infoCount() { return this._info.length; }
get hasError() { return this._errors.length > 0; }
get hasErrorOrWarning() { return this.hasError || this.hasWarning; }
get hasErrorOrWarningOrInfo() { return this.hasError || this.hasInfo || this.hasWarning; }
get hasInfo() { return this._info.length > 0; }
get hasGlobalError() { return WpwBuild._errorsGlobal.length > 0; }
get hasWarning() { return this._warnings.length > 0; }
get isAnyApp() { return this.isApp || this.isWebApp; }
get isAnyLib() { return this.isLib || this.isWebLib; }
get isAnyAppOrLib() { return this.isAppOrLib || this.isWebAppOrLib; }
get isApp() { return this.type === "app"; }
get isAppOrLib() { return this.isApp || this.isLib || this.isWebApp; }
get isDevMode() { return this.mode === "development"; }
get isDocs() { return [ "docs", "doxygen", "extjsdoc", "jsdoc" ].includes(this.type); }
get isExpo() { return this.source.info.language.startsWith("expo."); }
get isJs() { return this._scm.isJs; }
get isJsTs() { return this._scm.isJsTs; }
get isLib() { return this.type === "lib"; }
get isLogDisabled() { return this.logger.isDisabled; }
get isLogEnabled() { return this.logger.isEnabled; }
get isMain() {
return !!this.main || (this.isAnyAppOrLib &&
((this.buildConfigs.find((c) => !c.disabled && (c.type === "app")) &&
!this.options.output?.name || this.options.output.name === this.name) ||
(this.buildConfigs.find((c) => !c.disabled && (c.type === "lib")) &&
!this.options.output?.name || this.options.output.name === this.name)));
}
get isModule() { return this.library === "module" || this.library === "modern-module"; }
get isMultiTarget() {
return (this.options.npmiso && this.options.npmiso.dist !== this.getDistPath()) ||
(this.options.vsceiso && this.options.vsceiso.dist !== this.getDistPath()) ||
this.isAnyAppOrLib && this.buildConfigs.find((b) =>
!b.disabled && b.name !== this.name && b.target !== this.target && b.type === this.type);
}
get isNodeApp() { return !this.isWeb && this.isApp; }
get isOnlyBuild() { return this._wrapper.isSingleBuild; }
get isProdMode() { return this.mode === "production"; }
get isRebuild() { return this._rebuild; }
set isRebuild(v) { this._rebuild = v; }
get isReact() { return this.source.info.language.startsWith("react."); }
get isReactNative() { return this.source.info.language === "reactnative"; }
get isResource() { return this.type === "resource"; }
get isSchema() { return this.type === "schema"; }
get isScript() { return this.type === "script"; }
get isWeb() { return this.target.includes("web") || this.target.includes("webworker"); }
get isTests() { return this.type === "tests"; }
get isTs() { return this._scm.isTs; }
get isTsJs() { return this._scm.isTsJs; }
get isTypes() { return this.type === "types"; }
get isTranspiled() { return this.isAppOrLib || this.isTypes || this.isWebAppOrLib; }
get isWebApp() { return this.isWeb && this.type === "webapp"; }
get isWebAppOrLib() { return this.isWebApp || this.isWebLib; }
get isWebLib() { return this.type === "lib" && this.isWeb; }
get isWebWorker() { return this.target.includes("webworker"); }
get lastError() { return this._errors[this._errors.length - 1]; }
get nameAlias() { return this.options.output?.name || this.name; }
get nodeModules() { return this._nodeModules; }
get nodeModulesGlobal() { return WpwBuild._nodeModules; }
get nodeModulesPath() { return this._nodeModulesPaths.base; }
get nodeModulesPathCtx() { return this._nodeModulesPaths.ctx; }
get nodeModulesPathGbl() { return this._nodeModulesPaths.global; }
get nodeModulesPathWpw() { return this._nodeModulesPaths.wpw; }
get nodeModulesPaths() { return this._nodeModulesPaths; }
get pkgJson() { return this._wrapper.pkgJson; }
get pkgJsonFilePath() { return this._wrapper.pkgJsonFilePath; }
get project() { return this.wrapper.settings.project; }
get schema() { return this.wrapper.schema; }
get source() { return this._scm; }
get sourceInfo() { return this._scm.info; }
get ssCache() { return this._ssCache; }
get state() { return this._state; }
set state(s) { this._state = s; }
get stats() { return this._wrapper.stats; }
get statsfile() { return this._wrapper.statsfile; }
get tsc() { return this._scm.tsc; }
get tsCompilerOptions() { return this.tsc?.compilerOptions || this._obj_.create(); }
get tsconfig() { return this._scm.tsc.tsconfig; }
get vpaths() { return this._vpaths; }
/** @private */set vpaths(v) { this._obj_.apply(this._vpaths, v); }
get warnings() { return this._warnings; }
get warningCount() { return this._warnings.length; }
get wp() { return this._wp; }
get wpc() { return this._wpc; }
set wpc(v) { this._wpc = v; }
get wpw() { return this._wrapper; }
get wpwPath() { return wpwPath(); }
get wrapper() { return this._wrapper; }
/**
* @param {SpmhMessageInfo | Error} info
* @param {boolean} [ifFirst]
* @returns {SpmhError}
*/
addMessage(info, ifFirst)
{
let e;
const isJsErr = this._types_.isError(info) && !SpmhMessageUtils.isSpmh(info);
if (SpmhMessageUtils.isMessageInfo(info))
{
this._obj_.applyIf(info, { build: this, capture: this.addMessage });
if (SpmhMessageUtils.isInfoCode(info.code))
{
e = this.pushMessageQ(this._info, info, ifFirst, "info", "spmh_blue");
}
else if (SpmhMessageUtils.isWarningCode(info.code))
{
e = this.pushMessageQ(this._warnings, info, ifFirst, "warn", "lightyellow");
}
else {
e = this.pushMessageQ(this._errors, info, ifFirst, "error", "lightpink");
}
}
else if (isJsErr)
{
info = {
build: this,
message: info.message,
capture: this.addMessage,
exception: this._obj_.apply({}, info),
code: SpmhMessageUtils.Code.ERROR_GENERAL
};
e = this.pushMessageQ(this._errors, info, ifFirst, "error", "spmh_blue");
}
else if (SpmhMessageUtils.isInfo(info))
{
e = this.pushMessageQ(this._info, info, ifFirst, "info", "lightblue");
}
else if (SpmhMessageUtils.isWarning(info))
{
e = this.pushMessageQ(this._warnings, info, ifFirst, "warn", "warning");
}
else if (SpmhMessageUtils.isError(info))
{
while (SpmhMessageUtils.isError(info.exception)) {
info.exception = info.exception.exception;
}
e = this.pushMessageQ(this._errors, info, ifFirst, "error", "error");
}
return e;
}
/**
* @param {SpmhSuggestionInfo} info
* @param {boolean} [ifFirst]
* @returns {SpmhMessage}
*/
addSuggestion(info, ifFirst)
{
return this.addMessage(this._obj_.apply({ message: "general suggestion" }, info), ifFirst);
}
createVirtualBuildPaths()
{
if (!existsSync(this.virtualEntry.dir)) {
createDirSync(this.virtualEntry.dir);
}
if (!existsSync(this.virtualEntry.dirDist)) {
createDirSync(this.virtualEntry.dirDist);
}
if (!existsSync(this.virtualEntry.dirBuild)) {
createDirSync(this.virtualEntry.dirBuild);
}
if (!existsSync(this.virtualEntry.dirStore)) {
createDirSync(this.virtualEntry.dirStore);
}
}
/**
* @override
*/
dispose() { super.dispose(); this._state = "disposed"; }
/**
* @template {WpwGetPathOptions | undefined} P
* @param {P} [options]
* @returns {WpwGetBuildPathResult<P>}
*/
getBasePath = (options) => (!options || !options.ctx ? this.getRcPath("base", options) : this.getRcPath("ctx", options));
/**
* @param {string} nameOrType
* @param {boolean} [isName]
* @returns {WpwBuild | undefined}
*/
getBuild = (nameOrType, isName) => this.wrapper.getBuild(nameOrType, isName);
/**
* @param {string} nameOrType
* @param {boolean} [isName]
* @returns {IWpwBuildConfig | undefined}
*/
getBuildConfig = (nameOrType, isName) => this.wrapper.getBuildConfig(nameOrType, isName);
/**
* @param {function(IWpwBuildConfig): boolean} cb
* @param {any} thisArg
*/
getBuildConfigBy = (cb, thisArg) => this.wrapper.getBuildConfigBy(cb, thisArg);
getBuildConfigMain = (web) => this.getBuildConfig(!web ? "app" : "webapp") ||
this.getBuildConfig("lib") || this.getBuildConfig("jsdoc");
/**
* @param {WebpackSource} source
* @returns {string} string
*/
getContentHash(source)
{
return this._ssCache.getContentHash(source.buffer());
}
/**
* @template {WpwGetPathOptions | undefined} P
* @param {P} [options]
* @returns {WpwGetBuildPathResult<P>}
*/
getContextPath = (options) => this.getRcPath("ctx", options);
/**
* @template {WpwGetPathOptions | undefined} P
* @param {P} [options]
* @returns {WpwGetBuildPathResult<P>}
*/
getDistPath = (options) =>this.getRcPath("dist", options);
/**
* @param {string} path path to file
* @param {string} [basePath] path base relative from
* @returns {string} relative path to file, root @ context directtory
*/
getEmitRelPath(path, basePath)
{
let filePathRel = path;
if (!this._path_.isAbsPath(path))
{ if (!basePath)
{ path = resolvePath(this.virtualEntry.dirDist, path);
if (!existsSync(path)) {
path = resolvePath(this.virtualEntry.dirBuild, path);
if (!existsSync(path)) {
path = resolvePath(this.getSrcPath(), path);
if (!existsSync(path)) {
path = resolvePath(this.getContextPath(), path);
if (!existsSync(path)) {
path = resolvePath(this.getBasePath(), path);
} } } } }
else { path = resolvePath(basePath, path); }
}
if (basePath) {
filePathRel = relativePathEx(basePath, path, { psx: true });
}
else if (path.startsWith(this.virtualEntry.dirDist)) {
filePathRel = relativePathEx(this.virtualEntry.dirDist, path, { psx: true });
}
else if (path.startsWith(this.virtualEntry.dirBuild)) {
filePathRel = relativePathEx(this.virtualEntry.dirBuild, path, { psx: true });
}
else if (path.startsWith(this.getSrcPath())) {
filePathRel = relativePathEx(this.getSrcPath(), path, { psx: true });
}
else if (path.startsWith(this.getContextPath())) {
filePathRel = relativePathEx(this.getContextPath(), path, { psx: true });
}
else { filePathRel = relativePathEx(this.getBasePath(), path, { psx: true }); }
return filePathRel;
}
/**
* @private
* @template {WpwGetPathOptions | undefined} P
* @param {WpwRcPathsKey} pathKey
* @param {P} [options]
* @returns {WpwGetBuildPathResult<P>}
*/
getRcPath(pathKey, options)
{
const opts = options || /** @type {WpwGetPathOptions} */({}),
basePath = opts.ctx ? this.paths.ctx : this.paths.base,
build = opts.build ? this.getBuildConfig(opts.build) : undefined;
const _getPath = /** @param {string | undefined} path */(path) =>
{
const oPath = path;
if (path)
{ if (opts.rel || opts.relBase)
{ if (this._path_.isAbsPath(path))
{ let absPath = path;
if (opts.path) {
absPath = this._path_.isAbsPath(opts.path) ? opts.path : this._path_.resolvePath(path, opts.path);
}
if (opts.stat && !this._fs_.existsSync(absPath)) {
path = undefined;
}
else if (absPath === basePath) {
path = ".";
}
else
{ path = this._path_.relativePath(!opts.relBase ? basePath : oPath, absPath);
if (opts.dot) {
path = `.${this._path_.pathSep}${path}`;
}
}
}
else
{ let absPath = this._path_.resolvePath(basePath, path);
if (opts.path)
{ if (this._path_.isAbsPath(opts.path)) {
opts.path = this._path_.relativePath(absPath, opts.path);
}
absPath = this._path_.resolvePath(absPath, opts.path);
}
if (opts.stat && !this._fs_.existsSync(absPath)) {
path = undefined;
}
else if (absPath === basePath) {
path = ".";
}
else
{ path = this._path_.relativePath(!opts.relBase ? basePath : oPath, absPath);
if (opts.dot) {
path = `.${this._path_.pathSep}${path}`;
}
} } }
else
{ if (!this._path_.isAbsPath(path)) {
path = this._path_.resolvePath(basePath, path);
}
if (opts.path) {
if (this._path_.isAbsPath(opts.path)) {
opts.path = this._path_.relativePath(path, opts.path);
}
path = this._path_.resolvePath(path, opts.path);
}
if (opts.stat && !this._fs_.existsSync(path)) {
path = undefined;
}
}
}
else if (opts.fallback)
{
path = _getPath(this.getBuildConfig("app")?.paths[pathKey]) || _getPath(basePath);
}
return path ? !opts.psx ? path.replace(/[\\/]/g, this._path_.pathSep) : this._path_.fwdSlash(path) : path;
};
return !build ? _getPath(this.paths[pathKey]) : _getPath(build.paths[pathKey]);
}
/**
* @private
* @returns {IWpwRcPaths}
*/
getRootPathsConfig = () => this.wrapper.getRootPathsConfig(this.isWebApp);
/**
* @returns {string}
*/
getRootBasePath = () => this.getRootPathsConfig().base;
/**
* @returns {string}
*/
getRootCtxPath = () => this.getRootPathsConfig().ctx;
/**
* @returns {string}
*/
getRootDistPath = () => this.getRootPathsConfig().dist;
/**
* @returns {string}
*/
getRootSrcPath = () => this.getRootPathsConfig().src;
/**
* @template {WpwGetPathOptions | undefined} P
* @param {P} [options]
* @returns {WpwGetBuildPathResult<P>}
*/
getSrcPath = (options) => this.getRcPath("src", options);
/**
* @returns {RegExp}
*/
getSrcPathRegExp = () =>
{
const srcPath = this.getSrcPath(),
escapeRegExp = (/** @type {string} */txt) => txt.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") || "";
// eslint-disable-next-line stylistic/max-len
if (/^(?:\.{1,2}|(?:\.[\\/])?[a-zA-Z0-9_][~@!:\\*a-zA-Z0-9_.-\\/:]+ *[,|;]) *(?:\.{1,2}|(?:\.[\\/])?[a-zA-Z0-9_][~@!:*a-zA-Z0-9_.-\\/:]+ *[|,;]?)$/.test(srcPath)) {
return new RegExp(srcPath.split(/;|,|\|/).map((p) => escapeRegExp(p.trim())).join("|")); // delimited by , or ; or |
} return new RegExp(escapeRegExp(srcPath)); // path string or regex string
};
/**
* @private
* @param {IWpwBuildConfig} config
* @returns {WebpackTarget | WebpackTarget[]}
*/
getTarget(config)
{
/** @type {WebpackTarget[]} */
const target = !config.target ? [] : this._arr_.popBy(this._arr_.asArray(config.target), (t) => !isWebpackTarget(t));
if (target.length === 0)
{ if (config.type === "webapp" || (/^(?:web|browser)/).test(config.name)) {
target.push("web");
}
else if ((/^(?:web(?:worker|view))/).test(config.name)) {
target.push("webworker");
}
else { target.push("node"); }
}
return target.length === 1 ? target[0] : target;
}
/**
* @template {WpwGetPathOptions | undefined} P
* @param {P} [options]
* @returns {WpwGetBuildPathResult<P>}
*/
getTempPath = (options) => this.getRcPath("temp", options);
/**
* @private
* @param {IWpwBuildConfig} config
* @returns {WpwBuildType}
*/
getType(config)
{
/** @type {WpwBuildType | undefined} */
let type;
if (isWpwBuildType(config.type)) { type = config.type; return type; }
if (isWpwBuildType(config.name)) { type = config.name; }
else if ((/\btyp(?:es|ings)/).test(config.name)) { type = "types"; }
else if ((/\btests?(?:-?suite)?/).test(config.name)) { type = "tests"; }
else if ((/\blib.+$|^\w+-utils(?:-.+?)?$/).test(config.name)) { type = "lib"; }
else if ((/\b(?:web(?:worker|app|views|)|www[.-_].+)$/).test(config.name)) { type = "webapp"; }
else if ((/\b(?:js)?docs?|documentation|doxy?(?:gen)?|help|man|support/).test(config.name)) { type = "doc"; }
if (!type)
{
type = "app";
this.addSuggestion({
code: SpmhMessageUtils.InfoCode.INFO_CONFIG_SUGGESTION,
message: "the build type was not specified in the rc configuration and has been auto-set to 'app'",
suggest: "ensure that each merged build configuration is set to the desired " +
"type to avoid possibly un-noticed issues in the build output"
});
}
return type;
}
/**
* @returns {Promise<WpwBuild>}
* @param {string} lPad
*/
async init(lPad)
{
try
{ const lPadIn = lPad + " ";
//
// finalize instance configuration
//
this.initConfig();
this._rebuild = !existsSync(this.paths.dist);
this._wpc = /** @type {WpwWebpackConfig} */({});
//
// initialize logger
//
this.disposables.push(
this.logger = /** @type {WpwLogger} */(WpwLogger.getLoggerInst(this.log))
);
this.logger.write(
`configure build wrapper for '${this.name}::${this._arr_.asArray(this.target).join("|")}`, 1, lPad
);
//
// prepare cache and compilation build directories
//
this.logger.write(" resolve virtual build output paths", 1, lPad);
await this.resolveVirtualPaths();
//
// initialize build options utility, set initial target lib (subject to change)
//
const boFinalizer = new WpwBuildOptionsFinalizer(this, lPadIn);
this.library = boFinalizer.getLibraryType(this);
//
// initialize ts sourcecode service
//
// this.disposables.push(this._tsc = new WpwTscService({ build: this }));
this.disposables.push(this._scm = new WpwSourceCodeService(this._config.source, this));
await this._scm.init(lPadIn);
this._config.source = this._obj_.merge({}, this._scm.config);
//
// finalize build options
//
this.logger.write(" finalize build configuration", 1, lPad);
boFinalizer.configure();
const bCfg = this._obj_.pickBy(this.options, (k) => isWpwBuildOptionsKey(null, k));
this.logger.value(
" finalized build", this._json_.safeStringify(bCfg, null, this.logger.level >= 3 ? 3 : undefined), 2, lPad
);
//
// validate rc configuration using buildconfig json schema
//
this.logger.write(" validate build configurations schema", 1, lPad);
this.schema.validate(this, "WpwBuildConfig", this.logger, lPadIn, WpwBuildConfigKeys);
//
// initialize compilation filesystem snapshot cache
//
this.logger.write(" initialize snapshot cache service", 1, lPad);
this.disposables.push(this._ssCache = new WpwSnapshotService({ build: this }));
this.logger.success(`successfully configured execution wrapper for '${this.name}' build`, 1, lPad);
return this;
}
catch(e)
{ if (!this.logger) {
console.error(e);
}
else
{ this.logger.error("dump invalid configuration:");
printBuildProperties(this, withColor(this.logger.icons.error, this.logger.color), "");
this.logger.blank(1, withColor(this.logger.icons.error, this.logger.color));
}
throw e;
}
}
/**
* @private
*/
initConfig()
{
const targets = this._arr_.asArray(this.target),
config = this._obj_.merge({}, this.initialConfig);
config.target ||= this.getTarget(config);
config.type = this.getType(config);
// this._obj_.mergeIf(config, this.wrapper.targetConfigs[config.target] || {})
config.target = this._arr_.asArray(config.target);
config.vpaths ||= { base: config.paths.dist || "" };
this.validateConfig(config);
this._obj_.apply(this, this._obj_.pickNot(config, "source"));
this._obj_.apply(this.log, { envTag1: this.name, envTag2: targets.join("|") });
this._obj_.apply(config.log, { envTag1: this.name, envTag2: targets.join("|") });
this._obj_.apply(this, { _config: config });
}
/**
* @param {IWpwRuntimeExternal} info
* @returns {IWpwRuntimeExternal[]}
*/
pushImportedModule(info)
{
let alreadyAdded = !!this._nodeModules.find((e) => e.name === info.name);
if (!alreadyAdded)
{ this._nodeModules.push(info);
alreadyAdded = !!WpwBuild._nodeModules.find((e) => e.name === info.name);
if (!alreadyAdded) {
WpwBuild._nodeModules.push(this._obj_.apply({}, info));
}
}
return this._nodeModules;
}
/**
* @private
*/
resolveVirtualPaths()
{
return /** @type {Promise<void>} */(new Promise((ok, _fail) =>
{
const dirCtx = this.getContextPath(),
filenameTl = `v_${this.nameAlias}`,
uniqKey = getUniqBuildKey(this, true),
uniqKey2 = getUniqBuildKey(this, false),
vBaseDir = this._path_.resolvePath(this.wrapper.cacheDir, this.nameAlias, uniqKey2),
vDistDir = this._path_.resolvePath(vBaseDir, "dist"),
vBuildDir = this._path_.resolvePath(vBaseDir, "build");
this.virtualEntry = {
uniqKey,
dir: vBaseDir,
file: filenameTl,
chunk: filenameTl,
dirDist: vDistDir,
dirBuild: vBuildDir,
dirWpCache: vBaseDir,
dirDistRelToProj: relativePath(dirCtx, vDistDir),
dirBuildRelToProj: relativePath(dirCtx, vBuildDir),
dirBuildRelToDist: relativePath(vDistDir, vBuildDir),
dirDistRelToBuild: relativePath(vBuildDir, vDistDir),
dirStore: this._path_.resolvePath(vBaseDir, "store"),
filePathAbs: this._path_.resolvePath(vBaseDir, filenameTl),
filePathRel: relativePath(dirCtx, this._path_.joinPath(vBaseDir, filenameTl))
};
this.createVirtualBuildPaths();
this._nodeModulesPaths = {
all: [],
wpw: wpwNodeModulesPath(),
global: nodejsModulesGlobalPath(),
base: this._path_.joinPath(this.getBasePath(), "node_modules"),
ctx: this._path_.joinPath(this.getContextPath(), "node_modules")
};
if (!this._fs_.existsSync(this._nodeModulesPaths.ctx)) {
this._nodeModulesPaths.ctx = this._nodeModulesPaths.base;
}
this._arr_.pushUniq2(
this._nodeModulesPaths.all, this.global.isWin32, this._nodeModulesPaths.ctx,
this._nodeModulesPaths.base, this._nodeModulesPaths.wpw, this._nodeModulesPaths.global
);
ok();
}));
}
/**
* @param {boolean} [failed]
*/
printMessages(failed) { printBuildMessages(this, !!failed, this.log.suppress); }
/**
* @param {string | null} lPad
* @param {WpwLogColor | SpmhLogColorValue} [seeDetailsClr]
* @param {"error" | "info" | "warn" } [newFn]
* @param {string} [newMsg]
*/
printMessageQ(lPad, seeDetailsClr, newFn, newMsg)
{
if (this._lastMsgTag.tId) {
clearTimeout(this._lastMsgTag.tId);
}
if (lPad !== null && this._lastMsgTag.ct > 0)
{ const l = this.logger,
lFn = this._lastMsgTag.fn ? l[this._lastMsgTag.fn] : l[newFn],
tag = l.tag(`repeated ${this._lastMsgTag.ct} times`, seeDetailsClr, "default");
// lFn(lPad + this._lastMsgTag.msg.replace(/ {3,}/g, "").replace(":", `${tag} :`));
lFn(lPad + this._lastMsgTag.msg.replace(":", `${tag} :`));
}
this._lastMsgTag.ct = 0;
this._lastMsgTag.tId = 0;
this._lastMsgTag.fn = newFn;
this._lastMsgTag.msg = newMsg;
}
/**
* @private
* @param {SpmhMessage[]} msgQ
* @param {SpmhMessageInfo | SpmhSuggestionInfo | SpmhMessage} info
* @param {boolean} ifFirst
* @param {"error" | "info" | "warn"} fn
* @param {WpwLogColor | SpmhLogColorValue} seeDetailsTagClr
* @returns {SpmhMessage}
*/
pushMessageQ = (msgQ, info, ifFirst, fn, seeDetailsTagClr) =>
{
const qIsEmpty = this._types_.isEmpty(msgQ),
isInfo = SpmhMessageUtils.isMessageInfo(info),
msg = !isInfo ? info : SpmhMessageUtils.isErrorCode(info.code) ?
new SpmhError(info) : (SpmhMessageUtils.isWarningCode(info.code) ?
new SpmhWarning(info) : new SpmhInfo(info));
if (qIsEmpty || (!ifFirst && msgQ.every((m) => !msg.isEqual(m))))
{ const l = this.logger, lPad = isInfo ? info.lPad || l.lastPad : l.lastPad,
msgFmt = msg.xMessage.toString().split("\n")[0].replace(/ {2,}/g, "").trim() +
` ${ l.tag(` ${msg.loc.link} `, seeDetailsTagClr, "default")}`,
logIt = !isInfo || (info.lSkip !== true && this.logger.isLevelLogged(info.lLvl));
if (msgFmt !== this._lastMsgTag.msg)
{ if (this._lastMsgTag.tId) {
this.printMessageQ(logIt ? lPad : null, seeDetailsTagClr, fn, msgFmt);
}
if (logIt) {
l[fn](msgFmt, lPad);
}
}
else if (logIt)
{ ++this._lastMsgTag.ct;
this._lastMsgTag.tId = setTimeout(this.printMessageQ, 125, lPad);
}
if (fn === "error") {
WpwBuild._errorsGlobal.push(msg);
}
msgQ.push(msg);
}
return msg;
};
/**
* @private
* @param {IWpwBuildConfig} config
*/
validateConfig(config)
{
const _err = (/** @type {string} */ p) => new SpmhError(
{ code: SpmhMessageUtils.Code.ERROR_RESOURCE_MISSING,
message: `config validation failed for build ${this.name}: property ${p}`
});
if (!config.name) { throw _err("config.name"); }
if (!config.type) { throw _err("config.type"); }
if (!config.mode) { throw _err("config.mode"); }
if (!config.target) { throw _err("config.target"); }
}
}
module.exports = WpwBuild;