/**
* @file src/exports/entry.js
* @copyright @spmhome @_2025
* @author Scott Meesseman @spmeesseman
* @description @see {@link https://webpack.js.org/configuration/entry-context webpack.js.org/context}
*//** */
const WpwWebpackExport = require("./base");
const { createEntryObjFromDir } = require("../utils/utils");
const { apply, isString, isObject, pluralize, asArray } = require("@spmhome/type-utils");
const {
existsSync, findFilesSync, findExPathSync, isAbsPath, isDirectory, normalizePath, relativePath, resolvePath
} = require("@spmhome/cmn-utils");
/**
* @augments WpwWebpackExport
*/
class WpwEntryExport extends WpwWebpackExport
{
/**
* @private
* @type {string}
*/
globTestSuiteFiles= "**/*.{test,tests,spec,specs}";
/**
* @param {WpwExportOptions} options Plugin options to be applied
*/
constructor(options)
{
super(options);
this.buildOptions = /** @type {WpwBuildOptionsExportConfig<"entry">} */(this.buildOptions);
}
/**
* @override
*/
static create = WpwEntryExport.wrap.bind(this);
/**
* @override
* @param {WpwBuild} build
*/
app(build)
{
const outputCfg = this.build.options.output || { name: undefined, ext: undefined },
entryName = outputCfg.name || build.name,
l = build.logger.write("construct entry export configuration", 1);
if (build.entry)
{
l.write(" found rc-configured entry point configuration", 1);
if (isObject(build.entry))
{
const entries = Object.entries(build.entry),
entryCtxTxt = `${pluralize("entry", entries.length)}`;
l.write(` found entry point 'object' type config with ${entries.length} ${entryCtxTxt}`, 1);
entries.forEach(([ entryName, cfg ]) =>
{
if (isObject(cfg))
{
const ext = outputCfg.ext || (build.isModule ? ".mjs" : ".cjs"),
name = cfg.filename || "[name]";
l.write(" found entry point 'descriptor' type value");
apply(cfg, {
filename: `${name}${build.options.hash?.enabled ? ".[contenthash]" : ""}${ext}`
});
this._obj_.merge(build.wpc.entry, { [entryName]: cfg });
l.write(" " + JSON.stringify(cfg, null, l.level >= 3 ? 3 : undefined), 2);
}
else if (isString(cfg, true))
{
l.write(" found entry point 'object path' type config", 1);
l.value(" configured entry path", cfg, 2);
build.wpc.entry[entryName] = cfg;
}
else
{ return build.addMessage({
code: this.MsgCode.ERROR_CONFIG_PROPERTY,
message: `could not determine ${build.type} entry point`,
detail: "entry point contains empty or invalid rc-configuration"
});
}
});
}
else
{ l.write(" found entry point 'string/path' type config", 1);
apply(build.wpc.entry,
{
[entryName]: {
import: build.entry,
layer: build.isApp && build.debug ? "release" : undefined
}
});
}
}
else
{
l.write(" entry point not configured", 1);
const entryPath = this.determineEntryPath(build);
if (entryPath)
{
l.write(" auto-create entry point 'object import' type value", 1);
apply(build.wpc.entry, {
[entryName]: {
import: entryPath,
layer: build.debug ? "release" : undefined
}
});
if (build.debug)
{
l.write(" auto-create layer.debug entry point 'object import' type value", 1);
apply(build.wpc.entry,
{
[`${entryName}.debug`]: {
import: entryPath,
layer: "debug"
}
});
}
}
}
}
/**
* @override
* @protected
* @param {WpwBuild} build
*/
baseDone(build)
{
if (isString(build.entry))
{
build.entry = `./${this._path_.fwdSlash(build.entry).replace(/^\.\//, "")}`;
}
else if (isObject(build.entry))
{
const entry = this.build.entry;
Object.keys(entry).forEach((name) =>
{
if (isString(entry[name]))
{
entry[name] = `./${this._path_.fwdSlash(entry[name]).replace(/^\.\//, "")}`;
}
else
{ const imp = entry[name].import;
if (this._types_.isArray(imp))
{
for (let i = 0; i < imp.length; i++) {
entry[name].import[i] = `./${this._path_.fwdSlash(imp[i]).replace(/^\.\//, "")}`;
}
}
else {
entry[name].import = `./${this._path_.fwdSlash(imp).replace(/^\.\//, "")}`;
}
}
});
}
}
/**
* @private
* @param {WpwBuild} build
* @returns {string | string[] | undefined}
*/
determineEntryPath(build)
{
const entry = build.entry;
let entryPath, entryPathAbs;
build.logger.write(" determine unknown entry point", 2);
if (isString(entry))
{
entryPathAbs = entry;
if (!isAbsPath(entryPathAbs)) {
entryPathAbs = resolvePath(build.getRootCtxPath(), entryPathAbs);
}
// entryPathAbs = normalize(`${entryPathAbs.replace(/\.(?:j|t)s$/, "")}.${build.source.ext}`);
if (!existsSync(entryPathAbs)) {
entryPathAbs = undefined;
}
}
if (this._types_.isArray(entry))
{
entryPathAbs = entry.map((_e) => this._path_.absPath(entryPathAbs, true, build.getContextPath()));
if (entryPathAbs.length === 0) {
entryPathAbs = undefined;
}
}
if (!entryPathAbs)
{
const rootLikeFilenames = [
`index.${build.source.ext}`, `main.${build.source.ext}`, `${build.name}.${build.source.ext}`,
`${build.type}.${build.source.ext}`, `${this._arr_.asArray(build.target).join("-")}.${build.source.ext}`
];
build.logger.write(" no entry point defined in wpwrc for this build", 3);
build.logger.write(" attempt to auto-find entry point", 3);
if (build.type !== "app")
{
const mainBuildConfig = build.getBuildConfigMain();
if (mainBuildConfig)
{
build.logger.write(" get configured entry point from 'app' build config", 2);
if (mainBuildConfig.entry)
{
if (isString(mainBuildConfig.entry)) {
entryPathAbs = mainBuildConfig.entry;
}
else if (isObject(mainBuildConfig.entry))
{
const eValues = Object.values(mainBuildConfig.entry);
entryPathAbs = eValues.map((e) => isObject(e) ? e.import : e);
}
}
entryPathAbs = asArray(entryPathAbs).map(this._path_.absPath.bind(this)).filter((e) => existsSync(e));
if (!entryPathAbs.length)
{
entryPathAbs.push(normalizePath(
`${mainBuildConfig.paths.src}/${mainBuildConfig.name}.${build.source.ext}`
));
build.logger.write(" search entry point by build name for 'app' build", 2);
build.logger.value(" constructed search value", entryPathAbs, 2);
if (!existsSync(entryPathAbs[0]))
{
entryPathAbs = findFilesSync(
`**/${mainBuildConfig.name}.${build.source.ext}`,
{ cwd: mainBuildConfig.paths.src, absolute: true, maxDepth: 3 }
);
if (!entryPathAbs.length) {
entryPathAbs = normalizePath(`${build.paths.src}/index.${build.source.ext}`);
build.logger.write(" check entry point by name 'index'", 2);
build.logger.value(" value", entryPathAbs, 2);
}
}
}
}
}
else
{
entryPathAbs = normalizePath(`${build.paths.src}/${build.name}.${build.source.ext}`);
build.logger.write(" search entry point by build name", 2);
build.logger.value(" constructed search value", entryPathAbs, 2);
if (!existsSync(entryPathAbs))
{
entryPathAbs = findFilesSync(
`**/${build.name}.${build.source.ext}`,
{ cwd: build.paths.src, absolute: true, maxDepth: 3 }
)[0];
if (!entryPathAbs)
{
entryPathAbs = normalizePath(`${build.paths.src}/index.${build.source.ext}`);
build.logger.write(" check entry point by name 'index'", 2);
build.logger.value(" value", entryPathAbs, 2);
}
}
}
if (!entryPathAbs.length)
{
const srcPath = build.getRootSrcPath(),
file = findExPathSync(rootLikeFilenames, srcPath, true);
if (file)
{
build.logger.write(" get configured entry point from 'root' paths config", 2);
entryPathAbs = resolvePath(srcPath, file);
}
}
if (!entryPathAbs.length)
{
const srcPath = build.tsc.compilerOptions.rootDir,
file = findExPathSync(rootLikeFilenames, srcPath, true);
if (file)
{
build.logger.write(" get configured entry point from 'tsconfig.json' parsed config", 2);
entryPathAbs = resolvePath(srcPath, file);
}
}
if (!entryPathAbs.length)
{
const srcPath = build.tsc.compilerOptions.sourceRoot,
file = findExPathSync(rootLikeFilenames, srcPath, true);
if (file)
{
build.logger.write(" get configured entry point from 'root' paths config", 2);
entryPathAbs = resolvePath(srcPath, file);
}
}
if (!entryPathAbs.length && ((build.pkgJson.main && !build.isModule) || (build.pkgJson.module && build.isModule)))
{
entryPathAbs = build.pkgJson.main;
build.logger.write(" check configured 'main' property in package.json", 2);
build.logger.value(" property value", entryPathAbs, 2);
if (!isAbsPath(entryPathAbs)) {
entryPathAbs = resolvePath(build.getBuildConfigMain().paths.base, entryPathAbs);
}
entryPathAbs = normalizePath(`${entryPathAbs.replace(/\.(?:j|t)s$/, "")}.${build.source.ext}`);
if (!existsSync(entryPathAbs)) {
entryPathAbs = undefined;
}
}
}
if (!entryPathAbs.length)
{
this.addMessage({
lPad: " ",
code: this.MsgCode.ERROR_CONFIG_PROPERTY,
message: "could not determine entry point",
detail: `build details: [wpc.entry: name=${build.name}] [type=${build.type}] [mode=${build.mode}]`,
suggest: [
"name the build using the same name as that of the entry file",
"set 'root.build.entry' in the wpwrc file ('(web)app', 'lib', and 'test' type builds only)",
`create a root level index.${build.source.ext} that exports the current unknown entry file`
]
});
}
else
{
entryPath = relativePath(build.getContextPath(), asArray(entryPathAbs)[0]);
entryPath = `./${this._path_.fwdSlash(entryPath.replace(/^\.[/\\]/, "")
.replace(/\.(?:j|t)s$/, ""))}.${build.source.ext}`;
if (entryPathAbs.includes(build.getDistPath()))
{
entryPath = undefined;
this.addMessage({
code: this.MsgCode.ERROR_CONFIG_PROPERTY,
message: "entry point cannot be a descendent of 'dist' path, output will overwrite input",
suggest: "set a valid 'entry' property in the wpw build configuration ot js/tsconfig file",
detail: `details: [entry=${entryPath}][name=${build.name}][type=${build.type}][mode=${build.mode}]`
});
}
}
if (entryPath) {
build.logger.value(" using entry point", entryPath, 2);
}
else {
build.logger.error(" unable to determine entry point");
}
return entryPath;
}
/**
* @override
* @param {WpwBuild} build
*/
doxygen(build) { this.task(build, build.options.doc.doxygen); }
/**
* @override
* @param {WpwBuild} build
*/
extjsdoc(build) { this.task(build, build.options.doc.extjsdoc); }
/**
* @override
* @param {WpwBuild} build
*/
jsdoc(build) { this.task(build, build.options.doc.jsdoc); }
/**
* @override
* @param {WpwBuild} build
*/
lib(build)
{
this.app(build);
// const build = this.build,
// entryPath = this.appEntryPath();
// if (entryPath && !build.hasError)
// {
// apply(build.wpc.entry,
// {
// [build.name]: {
// import: entryPath,
// filename: `${build.name}.js`
// // library: {
// // type: "commonjs2"
// // }
// }
// });
// let binPath = resolvePath(build.getSrcPath(), `bin/${build.name}.js`);
// if (!existsSync(binPath)) {
// binPath = resolvePath(build.getSrcPath(), `cli/${build.name}.js`);
// }
// if (existsSync(binPath))
// {
// const binName = dirname(binPath);
// apply(build.wpc.entry,
// {
// [`${build.name}-${binName}`]: {
// import: `./${binName}/${build.name}.js`,
// filename: `${build.name}-${binName}.js`,
// dependOn: build.name
// }
// });
//
// }
// }
}
/**
* @override
* @param {WpwBuild} build
*/
plugin(build) { this.app(build); }
/**
* @override
* @param {WpwBuild} build
*/
schema(build) { this.task(build, build.options.schema.scripts, true); }
/**
* @override
* @param {WpwBuild} build
*/
script(build) { this.task(build, build.options.script, true); }
/**
* @override
* @param {WpwBuild} build
*/
resource(build) { this.task(build, build.options.resource, true); }
/**
* @private
* @param {WpwBuild} build
* @param {WpwBuildOptionsConfig<any> | WpwBuildOptionsPluginConfig<any>} [_config]
* @param {boolean | undefined} [_noEntry]
*/
task(build, _config, _noEntry)
{
// if (!noEntry)
// {
// const entry = build.entry || this.determineEntryPath();
// if (isObject(entry))
// {
// let entryNum = 0;
// build.logger.write(" apply dependency entry point path for file dependencies", 3);
// apply(build.wpc.entry, entry);
// Object.values(build.entry).forEach((value) => {
// apply(build.wpc.entry, { [`entry${++entryNum}`]: value });
// });
// }
// else if (isString(entry)) {
// apply(build.wpc.entry, { entry1: `./${forwardSlash(entry.replace(/^\.\//, ""))}` });
// }
// }
// else {
apply(
build.wpc.entry,
{ [build.virtualEntry.file]: `./${this._path_.fwdSlash(build.virtualEntry.filePathRel)}${build.source.dotext}`}
);
// }
}
/**
* @override
* @param {WpwBuild} build
*/
tests(build)
{
const dotext = this.build.source.dotext,
testsPath = this.build.getSrcPath();
apply(build.wpc.entry, {
...createEntryObjFromDir(testsPath, dotext, true, [ this.globTestSuiteFiles + dotext ]),
...createEntryObjFromDir(testsPath, dotext, true)
});
}
/**
* @override
* @param {WpwBuild} build
*/
types(build)
{
if (build.options.types?.mode === "plugin") { this.task(build, build.options.types); }
}
/**
* @override
* @param {WpwBuild} build
*/
webapp(build)
{
if (build.entry)
{
this.app(build);
}
else
{
if (isDirectory(build.getSrcPath()))
{
const appPath = this._path_.resolve(build.getContextPath(), build.getSrcPath({ rel: true }));
apply(build.wpc.entry, createEntryObjFromDir(appPath, build.source.dotext));
}
}
}
}
module.exports = WpwEntryExport.create;