import fs from "fs" import crypto from "crypto" import path from "path" import { validate } from "schema-utils" import { access as accessCps } from "fs" import { execFile as execFileCps } from "child_process" import { promisify } from "util" class SRIPlugin { static defaultOptions = { algorithm: "sha512", sourceFile: "assets.json" } constructor(options = {}) { this.options = { ...SRIPlugin.defaultOptions, ...options } validate( { type: "object", properties: { sourceFile: { type: "string" }, outputFile: { type: "string" }, algorithm: { type: "string" } } }, options, { name: "SRI Plugin", baseDataPath: "options" } ) } apply(compiler) { compiler.hooks.done.tap("SRIPlugin", () => { const data = JSON.parse(fs.readFileSync(this.options.sourceFile, "utf8")) const outputFile = this.options.outputFile || this.options.sourceFile const { algorithm } = this.options const calculateSRI = (file) => { const fileContent = fs.readFileSync(path.join(".", "static", file)) const hash = crypto.createHash(algorithm).update(fileContent).digest("base64") return `${algorithm}-${hash}` } Object.keys(data).forEach((key) => { data[key].integrity = calculateSRI(data[key].src) }) fs.writeFileSync(outputFile, JSON.stringify(data, null, 2), { encoding: "utf8", flag: "w" }) }) } } class GitVersionPlugin { static defaultOptions = { outputFile: "VERSION" } constructor(options = {}) { this.options = { ...GitVersionPlugin.defaultOptions, ...options } validate( { type: "object", properties: { outputFile: { type: "string" } } }, options, { baseDataPath: "options", name: "GitVersion Plugin" } ) } apply(compiler) { const { webpack, hooks, context } = compiler const { Compilation } = webpack hooks.beforeCompile.tapPromise("GitVersionPlugin", async () => { const access = promisify(accessCps) try { await access(".git") this.dependsOnGit = true } catch { this.dependsOnGit = false } }) hooks.compilation.tap("GitVersionPlugin", (compilation) => { if (this.dependsOnGit) { compilation.fileDependencies.add(path.join(context, ".git/logs/HEAD")) compilation.contextDependencies.add(path.join(context, ".git/refs/tags")) } compilation.hooks.processAssets.tapPromise( { name: "GitVersionPlugin", stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL }, async (assets) => { try { const v = await this.version() assets[this.options.outputFile] = { source: () => `${v}\n`, size: () => v.length + 1 } } catch { assets[this.options.outputFile] = { source: () => "", size: () => 0 } } } ) }) } async version() { const execFile = promisify(execFileCps) try { const { stdout: describe } = await execFile("git", ["describe", "--long", "--tags"]) const [, tag, offset] = describe.trim().match(/^(.*)-(\d+)-g[0-9a-f]+$/) return parseInt(offset) === 0 ? tag : this.getBranchAndHash() } catch { return this.getBranchAndHash() } } async getBranchAndHash() { const execFile = promisify(execFileCps) const [{ stdout: branch }, { stdout: hash }] = await Promise.all([ execFile("git", ["rev-parse", "--abbrev-ref", "HEAD"]), execFile("git", ["rev-parse", "HEAD"]) ]) return `${branch.trim()}@${hash.substring(0, 7)}` } } export { SRIPlugin, GitVersionPlugin }