/**
* @file plugin/copy.js
* @copyright @spmhome @_2025
* @author Scott Meesseman @spmeesseman
*//** */
const WpwPlugin = require("./base");
const {
copyDirAsync, copyFileAsync, existsSync, isDirectory, findFiles, readFileAsync, relativePathEx, resolvePath, normalizePath
} = require("@spmhome/cmn-utils");
/**
* @augments WpwPlugin
*/
class WpwCopyPlugin extends WpwPlugin
{
/**
* @param {WpwPluginOptions} options Plugin options to be applied
*/
constructor(options)
{
super(options);
this.buildOptions = /** @type {WpwBuildOptionsPluginConfig<"copy">} */(this.buildOptions);
}
/**
* @override
*/
static create = WpwCopyPlugin.wrap.bind(this);
/**
* @override
* @param {WebpackCompiler} _compiler
* @returns {WpwPluginTapOptions<any, any, boolean> | undefined}
*/
onApply(_compiler)
{
return {
copyFileResourcesToOutput: {
async: true,
hook: "compilation",
stage: "ADDITIONAL",
statsProperty: "copy",
hookCompilation: "processAssets",
callback: this.copyFileResourcesToOutput.bind(this)
}
};
}
/**
* @private
* @param {WebpackCompilation} _compilation
* @returns {Promise<void | Error>}
*/
async copyFileResourcesToOutput(_compilation)
{
const b = this.build,
basePath = b.getBasePath(),
items = this.buildOptions.items || [];
this.hookstart();
try
{ for (const i of items) {
await this.execCopyTask(resolvePath(basePath, i.input), resolvePath(basePath, i.output), i, " ");
}
}
catch (e) {
return e;
}
finally {
this.hookdone();
}
}
/**
* @private
* @param {string} from
* @param {string} to
* @param {WpwPluginInputOutputPath} io
* @param {string | number} lPad
* @returns {Promise<void | Error>}
*/
async execCopyTask(from, to, io, lPad)
{
const l = this.build.logger, compilation = this.compilation, isDir = isDirectory(from),
outputPath = normalizePath(compilation.outputOptions.path || this.compiler.outputPath),
outputPathIsVDir = outputPath.startsWith(this.build.virtualEntry.dir);
l.write("execute copy task", 1, lPad);
l.value(" input path", from, 1, lPad);
l.value(" output path", to, 1, lPad);
if (!existsSync(from)) {
return l.warn(" copy source 'from' does not exist", lPad);
}
/**
* @param {string[]} files
* @returns {Promise<number>}
*/
const _emit = async (files) =>
{ for (const a of files.map((p) =>
({ path: p, name: relativePathEx(this.build.getBasePath(), p, { psx: true }), asset: compilation.getAsset(p) })
))
{ let source = a.asset?.source;
if (!source)
{ const content = await readFileAsync(a.path);
source = new this.build.wp.sources.RawSource(content);
}
l.write(` emit file asset italic(${a.name})`, 3, lPad);
compilation.emitAsset(a.name, source, { copy: true, immutable: !!io.assets?.immutable });
}
return files.length;
};
let tct = 0, fct = 0, act = 0, dct = 0;
try
{ let files;
if (io.assets)
{
l.write(" add input files as compilation file dependencies", 1, lPad);
files = isDir ? await findFiles(io.assets?.glob || "**/*", { cwd: from }) : [ from ];
dct = tct = files.length;
compilation.fileDependencies.addAll(files);
l.write(" emit destination files as compilation assets", 1, lPad);
act = await _emit(files);
}
if (!io.assets || outputPathIsVDir)
{
l.write(" static copy input directory to destination", 1, lPad);
if (isDir) {
fct = tct = await copyDirAsync(from, to);
}
else {
await copyFileAsync(from, to);
fct = tct = 1;
}
}
l.write(` ${tct} total files processed`, 1, lPad);
l.write(` ${fct} total files copied (static/raw)`, 1, lPad);
l.write(` ${act} total file assets emitted`, 1, lPad);
l.write(` ${dct} total file dependencies added`, 1, lPad);
l.write("copy task completed", 1, lPad);
}
catch (e) {
return this.addMessage({ exception: e, code: this.MsgCode.ERROR_COPY_FAILED, message: "copy task failed" });
}
}
}
module.exports = WpwCopyPlugin.create;