/**
* @file utils/vendormod.js
* @copyright @spmhome @_2025
* @author Scott Meesseman @spmeesseman
*/
const {
absPath, existsSync, readFileSync, writeFileSync, readFileAsync, resolve, writeFileAsync
} = require("@spmhome/cmn-utils");
const nodeModulesDir = absPath("node_modules");
/**
* @param {any} opts
* @param {WpwLogger} logger
* @returns {boolean}
*/
const angular = (opts, logger) =>
{
if (!opts.all && !opts.srcmap) { return false; }
const ngWpCfg = resolve(nodeModulesDir, "@angular-devkit/build-angular/src/tools/webpack/configs/common.js");
if (existsSync(ngWpCfg))
{
let content = readFileSync(ngWpCfg);
const rgx = /topLevelAwait: *false/;
if (rgx.test(content))
{
logger.write("apply angular webpack.config top-level await modification", 1);
logger.write(" " + ngWpCfg, 1);
content = content.replace(rgx, "topLevelAwait: true");
writeFileSync(ngWpCfg, content);
return true;
}
}
return false;
}
/**
* @param {any} opts
* @param {WpwLogger} logger
* @returns {Promise<boolean>}
*/
const jsdoc = async(opts, logger) =>
{ //
// JSDOC/PUBLISH.JS
// file:///D:\Projects\ci\webpack-wrap\node_modules\clean-jsdoc-theme\publish.js ~ line 764
// file:///D:\Projects\ci\webpack-wrap\node_modules\jsdoc\templates\default\publish.js ~ line 493
//
// replace some dumb shit in some of the templates with the implementation used in docdash
//
if (!opts.all && !opts.jsdoc) { return false; }
const rplRgx = new RegExp(" +if \\( *packageInfo +&& +packageInfo\\.name *\\)\\s+{\\s+outdir = path\\.join\\" +
"( *outdir, packageInfo\\.name, \\(? *packageInfo\\.version \\|\\| '' *\\)? *\\);\\s+\\}");
const rplTxt = ` if (packageInfo) {
const subdirs = [outdir];
if (packageInfo.name) {
const packageName = packageInfo.name.split('/');
if (packageName.length > 1 && conf.scopeInOutputPath !== false) {
subdirs.push(packageName[0]);
}
if (conf.nameInOutputPath !== false) {
subdirs.push((packageName.length > 1 ? packageName[1] : packageName[0]));
}
if (packageInfo.version && conf.versionInOutputPath !== false) {
subdirs.push(packageInfo.version);
}
if (subdirs.length > 1) {
outdir = path.join.apply(null, subdirs);
}
}
}`;
const _ = async(/** @type {string} */ path) =>
{
const jsdoc = resolve(nodeModulesDir, path);
if (existsSync(jsdoc))
{ const content = await readFileAsync(jsdoc);
if (!content.includes("packageName = packageInfo.name.split"))
{ logger.write("apply jsdoc template modification", 1);
logger.write(" " + jsdoc, 1);
await writeFileAsync(jsdoc, content.replace(rplRgx, rplTxt));
return true;
}
}
return false;
};
const didApply = (await _("clean-jsdoc-theme/publish.js")) || (await _("jsdoc/templates/default/publish.js"));
return didApply;
};
/**
* @param {any} opts
* @param {WpwLogger} logger
* @returns {boolean}
*/
const nodefetch = (opts, logger) =>
{ //
// Remove the async import and replace with top level import
//
if (!opts.all && !opts.nodefetch) { return false; }
const imports = [],
nodefetch = resolve(nodeModulesDir, "node-fetch/src/body.js");
if (existsSync(nodefetch))
{
let content = readFileSync(nodefetch);
const rgx = /const +\{ *(.+?) *\} += +await +import\(["'](.+?)["']\) *;/gm;
if (rgx.test(content))
{
logger.write("apply node-fetch async import modification", 1);
logger.write(" " + nodefetch, 1);
content = content.replace(rgx, (m, g1, g2) => { imports.push(`import { ${g1} } from "${g2}";`); return `// ${m}`; });
if (imports.length) {
writeFileSync(nodefetch, imports.join("\n") + "\n" + content);
return true;
}
}
}
return false;
};
/**
* @param {any} opts
* @param {WpwLogger} logger
* @returns {boolean}
*/
const nyc = (opts, logger) =>
{ //
// Remove the referened non-existent required internal module nyc
//
if (!opts.all && !opts.nyc) { return false; }
const nyc = resolve(nodeModulesDir, "nyc/index.js");
if (existsSync(nyc))
{
let content = readFileSync(nyc);
const rgx = /require\.resolve\("nyc\//gm;
if (rgx.test(content))
{
logger.write("apply nyc se;f referenced coverage file modification", 1);
logger.write(" " + nyc, 1);
content = content.replace("require(mod)", "____require____(mod)")
.replace(rgx, "____require.resolve____(\"nyc/")
.replace("selfCoverageHelper = require('../self-coverage-helper')", "selfCoverageHelper = { onExit () {} }");
writeFileSync(nyc, content);
return true;
}
}
return false;
};
/**
* @param {any} opts
* @param {WpwLogger} logger
* @returns {boolean}
*/
const sourcemapPlugin= (opts, logger) =>
{
// if (!(compilation instanceof Compilation)) {
// throw new TypeError(
// "The 'compilation' argument must be an instance of Compilation"
// );
// }
//
// WEBPACK.SOURCEMAPPLUGIN
// file:///d:\Projects\ci\webpack-wrap\node_modules\webpack\lib\javascript\JavascriptModulesPlugin.js
//
// A hack to remove a check added in Webpack 5 using 'instanceof' to check the compilation parameter.
// If multiple webpack installs are present, the following error occurs, regardless if Wp versions are the same:
//
// TypeError: The 'compilation' argument must be an instance of Compilation
// at Function.getCompilationHooks (...node_modules\webpack\lib\javascript\JavascriptModulesPlugin.js:164:10)
// at SourceMapDevToolModuleOptionsPlugin.apply (...\node_modules\webpack\lib\So...onsPlugin.js:54:27)
// at d:\Projects\ci\webpack-wrap\node_modules\webpack\lib\SourceMapDevToolPlugin.js:184:53
// at Hook.eval [as call] (eval at create (...\node_modules\tapable\lib\HookCodeFactory.js:19:10), <anon>:106:1)
// at Hook.CALL_DELEGATE [as _call] (d:\Projects\ci\webpack-wrap\node_modules\tapable\lib\Hook.js:14:14)
// ....
//
// Seeing it's a module resolution issue, this was patched for this plugin using 'require.resolve'
// in the cwd when importing this plugin in plugins/sourcemaps.js.
//
// This is a "in the worst case" fix, where we can "kind if" safely remove this check, and
// consider it patched if redundant testing yields no side effects,
//
if (!opts.all && !opts.srcmap) { return false; }
const sourceMapPlugin = resolve(nodeModulesDir, "webpack/lib/javascript/JavascriptModulesPlugin.js");
if (existsSync(sourceMapPlugin))
{
let content = readFileSync(sourceMapPlugin);
const rgx = /if \(!\(compilation instanceof Compilation\)\)/;
if (rgx.test(content))
{
logger.write("apply sourcemap-plugin compilation type comparison modification", 1);
logger.write(" " + sourceMapPlugin, 1);
content = content.replace(rgx, "if (false)");
writeFileSync(sourceMapPlugin, content);
return true;
}
}
return false;
};
/**
* @param {any} opts
* @param {WpwLogger} logger
* @returns {boolean}
*/
const tsLoader = (opts, logger) =>
{ //
// TS-LOADER
// file:///d:\Projects\ci\webpack-wrap\node_modules\ts-loader\dist\index.js
//
// A hck to allow just a straight up types 'declarations only' build.
//
if (!opts.all && !opts.tsloader) { return false; }
const tsLoader = resolve(nodeModulesDir, "ts-loader/dist/index.js");
if (existsSync(tsLoader))
{
let content = readFileSync(tsLoader);
const rgx = /if \(!\(compilation instanceof Compilation\)\)/;
if (rgx.test(content))
{
logger.write("apply ts-loader extended options check modification", 1);
logger.write(" " + tsLoader, 1);
content = readFileSync(tsLoader)
.replace(
/if \(outputText === null \|\| outputText === undefined\)/,
"if ((outputText === null || outputText === undefined) && (!instance.loaderOptions.compilerOptions " +
"|| !instance.loaderOptions.compilerOptions.emitDeclarationsOnly))"
).replace(
"callback(null, output, sourceMap);",
"callback(null, (!instance.loaderOptions.compilerOptions || " +
"!instance.loaderOptions.compilerOptions.emitDeclarationsOnly ? output : \"\"), sourceMap);"
);
writeFileSync(tsLoader, content);
return true;
}
}
return false;
};
/**
* @param {any} opts
* @param {WpwLogger} logger
* @returns {Promise<boolean>}
*/
async function applyVendorMods(opts, logger)
{
opts.all ||= !!Object.keys(opts).every((o) => !o);
if (!opts.quiet) {
logger.write("check vendor node_module modification status for known required changes", 1);
}
let didRpl = await jsdoc(opts, logger);
didRpl ||= angular(opts, logger);
didRpl ||= nodefetch(opts, logger);
didRpl ||= nyc(opts, logger);
didRpl ||= sourcemapPlugin(opts, logger);
didRpl ||= tsLoader(opts, logger);
if (didRpl)
{
logger.sep();
logger.start(" applied required modifications, a manual build restart is required");
logger.sep();
}
else if (!opts.quiet && !didRpl) {
logger.write(" all modifications have already been applied", 1);
}
return didRpl;
}
module.exports = { applyVendorMods, jsdoc, nodefetch, nyc, sourcemapPlugin, tsLoader };