import fs from "fs"; import path from "path"; import webpack from "webpack"; import { getFileName } from "../../utils/build-file.js"; import { getScriptRuntimeFromType } from "../../utils/script-runtime.js"; import scripts from "../../utils/scripts.js"; /** The only host this extension ever touches, used for every match pattern. */ const SITE_HOST = "banglarbhumi.gov.in"; /** Site-wide access: covers `web_accessible_resources` and `host_permissions`. */ const SITE_MATCH = `*://${SITE_HOST}/*`; /** The application section of the site, where every content script runs. */ const APP_MATCH = `*://${SITE_HOST}/BanglarBhumi/*`; const manifest = JSON.parse( fs.readFileSync( path.resolve("config/webpack/plugins/create-manifest-webpack-plugin/manifest-template.json") ) ); /** * Webpack plugin that dynamically generates the extension's * `manifest.json` asset. It reads the template, resolves the * current version from `package.json`, and builds `content_scripts` * entries by matching script definitions from `scripts.json` against * page URL patterns. Shared modules, injected scripts, and * web-accessible resources are added automatically based on import * counts collected during the build. * * Every generated pattern is scoped to {@link SITE_HOST}: match patterns * allow a single trailing `*` in the path, so the `"*"` script path maps to * {@link APP_MATCH} rather than the invalid `BanglarBhumi/**`. */ class CreateManifestPlugin { /** * @param {{ * sharedModulesImportedCount: Record, * injectedSharedModulesImportedCount: Record, * sortedSharedModules: string[] * }} options */ constructor({ sharedModulesImportedCount, injectedSharedModulesImportedCount, sortedSharedModules }) { this.sharedModulesImportedCount = sharedModulesImportedCount; this.injectedSharedModulesImportedCount = injectedSharedModulesImportedCount; this.sortedSharedModules = sortedSharedModules; } /** * @param {import("webpack").Compiler} compiler * @returns {void} */ apply(compiler) { compiler.hooks.compilation.tap("CreateManifestPlugin", (compilation) => { compilation.hooks.processAssets.tap( { name: "CreateManifestPlugin", stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL }, (assets) => { manifest.version = JSON.parse(fs.readFileSync((path.resolve(ROOT_DIR, "package.json")))).version; manifest.content_scripts = []; let resources = []; const arrangedScripts = {}; for (const scriptPath in scripts) { arrangedScripts[scriptPath] = {}; for (const scriptType in scripts[scriptPath]) { const scriptRuntime = getScriptRuntimeFromType(scriptType); if (typeof arrangedScripts[scriptPath][scriptRuntime] === "undefined") { arrangedScripts[scriptPath][scriptRuntime] = []; } arrangedScripts[scriptPath][scriptRuntime].push(`scripts/${getFileName(scriptType, scriptPath)}.js`); if (scriptType.startsWith("injected")) { resources.push(`scripts/injected/${getFileName(scriptType, scriptPath)}.js`); } } } manifest.content_scripts.push({ matches: [APP_MATCH], js: this.sortedSharedModules .filter((sharedModule) => this.sharedModulesImportedCount[sharedModule] > 0) .map((sharedModule) => `shared/${getFileName(sharedModule, "shared")}.js`) .concat(arrangedScripts["*"]["document_start"]) .filter((x) => x != null), run_at: "document_start" }); delete arrangedScripts["*"]["document_start"]; for (const scriptPath in arrangedScripts) { // Match patterns allow at most one trailing `*`, so `"*"` cannot // be interpolated into the path the way a literal segment can. const matches = [scriptPath === "*" ? APP_MATCH : `*://${SITE_HOST}/BanglarBhumi/${scriptPath}*`]; if (scriptPath === "Home") matches.push(`*://${SITE_HOST}/BanglarBhumi/${scriptPath}.action*`); for (const scriptRuntime in arrangedScripts[scriptPath]) { manifest.content_scripts.push({ matches, js: arrangedScripts[scriptPath][scriptRuntime], run_at: scriptRuntime }); } } for (const assetName in assets) { if (/^assets\/.*\.(jpg|png|gif)$/.test(assetName)) { resources.push(assetName); } } // `CreateRulesPlugin` skips emitting `rules.json` when `src/rules/` // holds no rule files, so drop the ruleset reference with it. if (!("rules.json" in assets)) { delete manifest.declarative_net_request; } const injectedSharedModules = this.sortedSharedModules .filter((sharedModule) => this.injectedSharedModulesImportedCount[sharedModule] > 0) .map((sharedModule) => `shared/${getFileName(sharedModule, "shared")}.js`); if (injectedSharedModules.length > 0) { resources = resources.concat(injectedSharedModules); manifest.content_scripts[0].js.unshift(`shared/${getFileName("injected-shared-modules", "shared")}.js`); } manifest.content_scripts[0].js.unshift(`shared/${getFileName("script-injector", "shared")}.js`); if (resources.length > 0) { manifest.web_accessible_resources = [ { resources, matches: [SITE_MATCH] } ]; } compilation.emitAsset( "manifest.json", new webpack.sources.RawSource( JSON.stringify( manifest, undefined, process.env.NODE_ENV === "production" ? undefined : "\t" ) ) ); } ); }); } } export default CreateManifestPlugin;