/* eslint-disable @typescript-eslint/no-var-requires */ const path = require("path"); const { unlink } = require("fs"); const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin"); const LicenseCheckerWebpackPlugin = require("license-checker-webpack-plugin"); let rustLicenses = []; let debugMode = false; try { // eslint-disable-next-line global-require, import/extensions, import/no-unresolved rustLicenses = require("./rust-licenses"); } catch (_) { // Rust licenses are not generated by Cargo About except in release mode (`npm run build`) debugMode = true; } module.exports = { lintOnSave: "warning", // https://cli.vuejs.org/guide/webpack.html chainWebpack: (config) => { // WASM Pack Plugin integrates compiled Rust code (.wasm) and generated wasm-bindgen code (.js) with the webpack bundle // Use this JS to import the bundled Rust entry points: const wasm = import("@/../wasm/pkg"); // Then call WASM functions with: (await wasm).function_name() // https://github.com/wasm-tool/wasm-pack-plugin config // https://cli.vuejs.org/guide/webpack.html#modifying-options-of-a-plugin .plugin("wasm-pack") .use(WasmPackPlugin) .init( (Plugin) => new Plugin({ crateDirectory: path.resolve(__dirname, "wasm"), // Remove when this issue is resolved: https://github.com/wasm-tool/wasm-pack-plugin/issues/93 outDir: path.resolve(__dirname, "wasm/pkg"), watchDirectories: [ path.resolve(__dirname, "../editor"), path.resolve(__dirname, "../graphene"), path.resolve(__dirname, "../charcoal"), path.resolve(__dirname, "../proc-macros"), ], }) ) .end(); // License Checker Webpack Plugin validates the license compatibility of all dependencies which are compiled into the webpack bundle // It also writes the third-party license notices to a file which is displayed in the application // https://github.com/microsoft/license-checker-webpack-plugin config .plugin("license-checker") .use(LicenseCheckerWebpackPlugin) .init( (Plugin) => new Plugin({ allow: "(Apache-2.0 OR BSD-2-Clause OR BSD-3-Clause OR MIT)", emitError: true, outputFilename: "third-party-licenses.txt", outputWriter: formatThirdPartyLicenses, }) ); // Vue SVG Loader enables importing .svg files into .vue single-file components and using them directly in the HTML // https://vue-svg-loader.js.org/ config.module // Replace Vue's existing base loader by first clearing it (https://cli.vuejs.org/guide/webpack.html#replacing-loaders-of-a-rule) .rule("svg") .uses.clear() .end() // Add vue-loader as a loader .use("vue-loader") .loader("vue-loader") .end() // Add vue-svg-loader as a loader .use("vue-svg-loader") .loader("vue-svg-loader") .end(); }, }; function formatThirdPartyLicenses(jsLicenses) { // Remove the HTML character encoding caused by Handlebars const licenses = rustLicenses.map((rustLicense) => ({ licenseName: htmlDecode(rustLicense.licenseName), licenseText: htmlDecode(rustLicense.licenseText), packages: rustLicense.packages.map((package) => ({ name: htmlDecode(package.name), version: htmlDecode(package.version), author: htmlDecode(package.author).replace(/\[(.*), \]/, "$1"), repository: htmlDecode(package.repository), })), })); // Augment the imported Rust license list with the provided JS license list jsLicenses.dependencies.forEach((jsLicense) => { const { name, version, author, repository, licenseName, licenseText } = jsLicense; // Remove the `git+` or `git://` prefix and `.git` suffix const repo = repository ? repository.replace(/^.*(github.com\/.*?\/.*?)(?:.git)/, "https://$1") : repository; const matchedLicense = licenses.find((license) => license.licenseName.trim() === licenseName.trim() && license.licenseText.trim() === licenseText.trim()); const packages = { name, version, author, repository: repo }; if (matchedLicense) matchedLicense.packages.push(packages); else licenses.push({ licenseName, licenseText, packages: [packages] }); }); // Sort the licenses, and the packages using each license, alphabetically licenses.sort((a, b) => a.licenseName.localeCompare(b.licenseName)); licenses.forEach((license) => { license.packages.sort((a, b) => a.name.localeCompare(b.name)); }); // Generate the formatted text file let formattedLicenseNotice = "THIRD-PARTY SOFTWARE LICENSE NOTICES\n\n"; if (debugMode) formattedLicenseNotice += "WARNING: Licenses for Rust packages are excluded in debug mode to improve performance — do not release without their inclusion!\n\n"; licenses.forEach((license) => { let packagesWithSameLicense = ""; license.packages.forEach((package) => { const { name, version, author, repository } = package; packagesWithSameLicense += `${name} ${version}${author ? ` - ${author}` : ""}${repository ? ` - ${repository}` : ""}\n`; }); formattedLicenseNotice += `-------------------------------------------------------------------------------- The following packages are licensed under the terms of the ${license.licenseName} license: ${packagesWithSameLicense} ${license.licenseText} `; }); // Clean up by deleting the `rust-licenses.js` Rust licenses data file generated by Cargo About unlink("./rust-licenses.js", (_) => _); return formattedLicenseNotice; } const htmlEntities = { nbsp: " ", copy: "©", reg: "®", lt: "<", gt: ">", amp: "&", apos: "'", // eslint-disable-next-line quotes quot: '"', }; function htmlDecode(str) { if (!str) return str; return str.replace(/&([^;]+);/g, (entity, entityCode) => { let match; if (entityCode in htmlEntities) { return htmlEntities[entityCode]; } // eslint-disable-next-line no-cond-assign if ((match = entityCode.match(/^#x([\da-fA-F]+)$/))) { return String.fromCharCode(parseInt(match[1], 16)); } // eslint-disable-next-line no-cond-assign if ((match = entityCode.match(/^#(\d+)$/))) { // eslint-disable-next-line no-bitwise return String.fromCharCode(~~match[1]); } return entity; }); }