plugins_cleanup_clean.js

/**
 * @file plugins/clean.js
 * @copyright @spmhome @_2025
 * @author Scott Meesseman @spmeesseman
 *//** */

const { join } = require("path");
const WpwPlugin = require("../base");
const { readdir } = require("fs/promises");
const { existsSync, findFiles, rmDirIfEmpty, deleteFile, resolvePath } = require("@spmhome/cmn-utils");


/**
 * @augments WpwPlugin
 */
class WpwCleanPlugin extends WpwPlugin
{
    /**
     * @param {WpwPluginOptions} options
     */
	constructor(options)
	{
		super(options);
        this.buildOptions = /** @type {WpwBuildOptionsPluginConfig<"clean">} */(this.buildOptions); // reset for typings
	}


    /**
     * @override
     * @static
     */
	static create = WpwCleanPlugin.wrap.bind(this);


    /**
     * @override
     * @returns {WpwPluginTapOptions<any, any, boolean> | undefined}
     */
    onApply()
    {
		return { cleanBuildOutput: { async: true, hook: "beforeRun", callback: this.cleanBuild.bind(this) }};
	}


	/**
	 * @private
     * @param {WebpackCompilation} _compilation
	 * @returns {Promise<void>}
     */
	async cleanBuild(_compilation)
	{
		this.hookstart();
		try
		{   await this.cleanBuildAssets();
			await this.cleanBuildCaches();
			await rmDirIfEmpty(this.build.virtualEntry.dir);
			this.build.createVirtualBuildPaths();
			await this.cleanWebpackCache();
		}
		catch (e) {
			this.addMessage({ message: "failed to execute all 'clean' tasks", exception: e }, true);
		}
		finally {
			this.hookdone();
		}
	}


	/**
	 * @private
	 * @returns {Promise<void>}
     */
	async cleanBuildAssets()
	{
		const distPath = this.compiler.outputPath || this.compilation.outputOptions.path || this.build.getDistPath(),
			  compilerOptions = this.build.tsc.compilerOptions;
		this.logger.write("clean build assets", 1);
		await this.deleteDir("distribution", distPath, "   ");
		await this.deleteDir("v-dir build", this.build.virtualEntry.dirBuild, "   ");
		await this.deleteDir("v-dir distribution", this.build.virtualEntry.dirDist, "   ");
		if (!!compilerOptions.outDir && compilerOptions.outDir !== distPath)
		{
			await this.deleteDir("ts.outdir", resolvePath(this.build.source.info.dir, compilerOptions.outDir), "   ");
		}
	}


	/**
	 * @private
	 * @returns {Promise<void>}
     */
	async cleanBuildCaches()
	{
		this.logger.write("clean wpw build cache", 1);
		this.logger.write("   check for tsbuildinfo incremental file", 3);
		const buildInfoFile = await findFiles("**/*.tsbuildinfo", { cwd: this.build.virtualEntry.dir })[0];
		if (buildInfoFile)
		{
			this.logger.value("   delete tsbuildinfo file", buildInfoFile, 2);
			await deleteFile(buildInfoFile);
		}
		if (existsSync(this.build.virtualEntry.dirStore))
		{
			await this.deleteDir("v-dir cache", this.build.virtualEntry.dirStore, "   ");
		}
	}


	/**
	 * @private
	 * @returns {Promise<void>}
     */
	async cleanWebpackCache()
	{
		this.logger.write("clean webpack cache", 1);
		const wpCacheDir = this.wpc.cache.type === "filesystem" ? this.wpc.cache.cacheDirectory : null;
		if (wpCacheDir)
		{
			await this.deleteDir("webpack cache", wpCacheDir, "   ");
		}
		else {
			this.logger.write("   webpack cache is configured as 'memory' type, skip task", 1);
		}
	}


	/**
	 * @private
	 * @param {string} dirType
	 * @param {string} dir
	 * @param {string} lPad
	 */
	async deleteDir(dirType, dir, lPad)
	{
		if (!!dir && existsSync(dir))
		{
			this.logger.value(`delete ${dirType} directory`, this.build.virtualEntry.dirDist, 2, lPad);
			const files = (await readdir(dir)).filter((f) => this.isOutputAsset(f));
			for (const file of files) {
				this.build.logger.value("   delete file asset", file, 3, lPad);
				await deleteFile(join(dir, file));
			}
			await rmDirIfEmpty(dir);
		}
	}
}


module.exports = WpwCleanPlugin.create;