plugins_release_licensefiles.js

/**
 * @file plugin/release/licensefiles.js
 * @copyright @spmhome @_2025
 * @author Scott Meesseman @spmeesseman
 *//** */

const { WebpackTargets } = require("../../utils");
const WpwPlugin = require("../base");
const LicenseWebpackPlugin = require("license-webpack-plugin").LicenseWebpackPlugin;


// class WpwLicenseFilesPlugin extends WpwPlugin
class WpwLicenseFilesPlugin extends WpwPlugin
{
    /**
     * @type {Record<string, boolean>}
     */
    static targetBuilds = {};


    /**
     * @param {WpwPluginOptions} options
     */
	constructor(options)
	{
		super(options);
        WpwLicenseFilesPlugin[`${this.build.target}`] = true;
        this.buildOptions = /** @type {WpwBuildOptionsConfig<"licensefiles">} */(this.buildOptions);
	}


	/**
     * @override
     * @param {WpwBuild} build
     */
	static create = (build) =>
        WpwLicenseFilesPlugin.wrap.call(this, build, build.isAnyAppOrLib, !WpwLicenseFilesPlugin[`${build.target}`]);


    /**
     * @override
     * @returns {WpwPluginTapOptions<any, any, any> | void}
     */
    onApply()
    {
        // return apply({}, // super.onApply(),
        // {
        //     combineLicenseFiles: {
        //         // hook: "compilation",
        //         hook: "thisCompilation",
        //         // stage: "ANALYSE",
        //         stage: "OPTIMIZE_COUNT",
        //         hookCompilation: "processAssets",
        //         callback: this.combineLicenseFiles.bind(this)
        //     }
        // });
        return {
            checkLicenseFiles: {
                hook: "compilation",
                hookCompilation: "afterProcessAssets",
                callback: this.checkLicenseFiles.bind(this)
            }
        };
    }


	/**
	 * @private
	 * @since 1.8.6
	 * @param {WebpackCompilationAssets} assets
	 */
	checkLicenseFiles(assets)
	{
        const licFile = Object.keys(assets).map((a) => this.compilation.getAsset(a)).find((a) => a?.name.endsWith(".LICENSE"));
        if (licFile)
        {   this.hookstart();
            const b = this.build,
                  sz = this._types_.isNumber(licFile.info.size) ? licFile.info.size :
                       (this._types_.isNumber(licFile.source.size()) ? licFile.source.size() : licFile.source.buffer().length);
            if (this._types_.isNumber(sz) && sz <= 1)
            {
                b.logger.write(`   bold(delete) empty license-file asset '${licFile.name}'`);
                this.compilation.deleteAsset(licFile.name);
            }
            else
            {   let licContent = licFile.source.buffer().toString();
                const targets = WebpackTargets.filter((t) => b.buildConfigs.find((b) => b.target === t)),
                      nms = b.nodeModules.filter((nm) =>
                          !nm.builtin && !nm.external && !/^\./.test(nm.req) &&
                          !licContent.includes(`\n\n--- VENDOR | DEPENDENCY [${nm.name}/${licFile.name}]`)
                      );
                b.logger.write(`   valid license-file asset '${licFile.name}'`);
                for (const nm of nms)
                {   if (!licContent.includes(`--- VENDOR | DEPENDENCY [${nm.name}/${licFile.name}]`))
                    {   const vLic = this._fs_.findExPathSync(
                        [ "vendor.LICENSE", ...targets.map((t) => `vendor.${t}.LICENSE`) ], [
                            this._path_.resolvePath(b.nodeModulesPath, nm.name),
                            ...targets.map((t) => this._path_.resolvePath(b.nodeModulesPath, nm.name, `${t}`))
                        ], true);
                        if (vLic)
                        {   const vLicContent = this._fs_.readFileSync(vLic);
                            b.logger.write(`   update content with vendor license | '${nm.name}'`);
                            this.compilation.updateAsset(
                                licFile.name,
                                (s) => this.addLicenseToSource(licFile.name, nm.name, vLicContent, s),
                                this._obj_.clone(licFile.info)
                            );
                            licContent += `\n--- VENDOR | DEPENDENCY [${nm.name}/${licFile.name}] ---\n`;
                        }
                    }
                }
                b.logger.value("   combined license-file accumulated size", this._num_.toStorageSize(sz));
            }
            this.hookdone();
        }
    }


    /**
     * @private
     * @param {string} file
     * @param {string} pkg
     * @param {string} license
     * @param {WebpackSource} sourceInfo
     * @returns {WebpackSource}
     */
    addLicenseToSource(file, pkg, license, sourceInfo)
    {
        const source= sourceInfo.source(),
              sep = "".padEnd(31 + pkg.length + file.length, "-"),
              rplRgx = new RegExp(`^.*?${this._rgx_.escapeRegExp(license)}.*?[\r\n]`),
              src = (source.toString().trim().replace(rplRgx, "") +
                    `\n\n${sep}\n--- VENDOR | DEPENDENCY [${pkg}/${file}] ---\n${sep}\n\n${license}\n`)
                    .replace(/(?:\r?\n){3,}/g, "\n\n");
        return new this.build.wp.sources.RawSource(src);
    }


    // /**
    //  * @private
    //  * @param {WebpackCompilationAssets} assets
    //  */
    // combineLicenseFiles = (assets) =>
    // {
    //     const licAssets = Object.entries(assets).filter(([ f ]) => f.includes(".LICENSE") && !f.includes(".debug"));
    //     if (licAssets.length > 0)
    //     {
    //         const l = this.hookstart();
    //         let license = "", licenseAsset;
    //         licAssets.forEach(([ file, source ]) =>
    //         {
    //             l.write(`   add license file italic(${file})`, 2);
    //             license += "----------------------------------------------------\n";
    //             license += `${file}\n`;
    //             license += "----------------------------------------------------\n\n";
    //             license += `${source.source().toString()}\n\n\n`;
    //             licenseAsset ||= file;
    //         });
    //         if (licenseAsset)
    //         {
    //             const licFileName = "vendor.LICENSE",
    //                 // src = new this.build.wp.sources.ConcatSource(...licAssets.map(([ _, s ]) => s));
    //                 src = new this.build.wp.sources.RawSource(`${license.trimEnd()}\n`),
    //                 contenthash = this.build.getContentHash(src);
    //             // this.compilation.renameAsset(licenseAsset, licFileName);
    //             // this.compilation.updateAsset(licFileName, source, { sourceFilename: licenseAsset, contenthash });
    //             // Object.keys(assets).filter((f) => f !== licFileName).forEach((f) => this.compilation.deleteAsset(f));
    //             this.compilation.emitAsset(licFileName, src, { contenthash, related: { files: Object.keys(licAssets) }});
    //         }
    //         this.hookdone();
    //     };
    // };


    /**
     * @override
     * @param {WebpackCompiler} _compiler
     * @param {boolean} firstPass
     * @returns {WebpackPluginInstance  | undefined}
     */
    getVendorPlugin(_compiler, firstPass)
    {
        if (!firstPass)
        {
            const build = this.build,
                  // rgxTest = /LICENSE/i,
                  outputFilename = build.isMultiTarget ? `vendor.${build.target}.LICENSE` : "vendor.LICENSE";
            return /** @type {any} */(new LicenseWebpackPlugin({
                outputFilename,
                perChunkOutput: false,
                skipChildCompilers: false,
                // licenseInclusionTest: (l) => rgxTest.test(l),
                modulesDirectories: build.wpc.resolve.modules,
                stats: {
                    warnings: build.logger.level >= 2,
                    errors: !build.logger.isDisabled
                }
            }));
        }
    }
}


module.exports = WpwLicenseFilesPlugin.create;