UNPKG

wxt

Version:

⚡ Next-gen Web Extension Framework

151 lines (150 loc) 6.1 kB
import path from "node:path"; import fs from "fs-extra"; import { safeFilename } from "./utils/strings.mjs"; import { getPackageJson } from "./utils/package.mjs"; import { formatDuration } from "./utils/time.mjs"; import { printFileList } from "./utils/log/printFileList.mjs"; import { findEntrypoints, internalBuild } from "./utils/building/index.mjs"; import { registerWxt, wxt } from "./wxt.mjs"; import JSZip from "jszip"; import glob from "fast-glob"; import { normalizePath } from "./utils/paths.mjs"; import { minimatchMultiple } from "./utils/minimatch-multiple.mjs"; export async function zip(config) { await registerWxt("build", config); const output = await internalBuild(); await wxt.hooks.callHook("zip:start", wxt); const start = Date.now(); wxt.logger.info("Zipping extension..."); const zipFiles = []; const packageJson = await getPackageJson(); const projectName = wxt.config.zip.name ?? safeFilename(packageJson?.name || path.basename(process.cwd())); const applyTemplate = (template) => template.replaceAll("{{name}}", projectName).replaceAll("{{browser}}", wxt.config.browser).replaceAll( "{{version}}", output.manifest.version_name ?? output.manifest.version ).replaceAll("{{packageVersion}}", packageJson?.version).replaceAll("{{mode}}", wxt.config.mode).replaceAll("{{manifestVersion}}", `mv${wxt.config.manifestVersion}`); await fs.ensureDir(wxt.config.outBaseDir); await wxt.hooks.callHook("zip:extension:start", wxt); const outZipFilename = applyTemplate(wxt.config.zip.artifactTemplate); const outZipPath = path.resolve(wxt.config.outBaseDir, outZipFilename); await zipDir(wxt.config.outDir, outZipPath, { exclude: wxt.config.zip.exclude }); zipFiles.push(outZipPath); await wxt.hooks.callHook("zip:extension:done", wxt, outZipPath); if (wxt.config.zip.zipSources) { const entrypoints = await findEntrypoints(); const skippedEntrypoints = entrypoints.filter((entry) => entry.skipped); const excludeSources = [ ...wxt.config.zip.excludeSources, ...skippedEntrypoints.map( (entry) => path.relative(wxt.config.zip.sourcesRoot, entry.inputPath) ) ].map((paths) => paths.replaceAll("\\", "/")); await wxt.hooks.callHook("zip:sources:start", wxt); const { overrides, files: downloadedPackages } = await downloadPrivatePackages(); const sourcesZipFilename = applyTemplate(wxt.config.zip.sourcesTemplate); const sourcesZipPath = path.resolve( wxt.config.outBaseDir, sourcesZipFilename ); await zipDir(wxt.config.zip.sourcesRoot, sourcesZipPath, { include: wxt.config.zip.includeSources, exclude: excludeSources, transform(absolutePath, zipPath, content) { if (zipPath.endsWith("package.json")) { return addOverridesToPackageJson(absolutePath, content, overrides); } }, additionalFiles: downloadedPackages }); zipFiles.push(sourcesZipPath); await wxt.hooks.callHook("zip:sources:done", wxt, sourcesZipPath); } await printFileList( wxt.logger.success, `Zipped extension in ${formatDuration(Date.now() - start)}`, wxt.config.outBaseDir, zipFiles ); await wxt.hooks.callHook("zip:done", wxt, zipFiles); return zipFiles; } async function zipDir(directory, outputPath, options) { const archive = new JSZip(); const files = (await glob(["**/*", ...options?.include || []], { cwd: directory, // Ignore node_modules, otherwise this glob step takes forever ignore: ["**/node_modules"], onlyFiles: true })).filter((relativePath) => { return minimatchMultiple(relativePath, options?.include) || !minimatchMultiple(relativePath, options?.exclude); }); const filesToZip = [ ...files, ...(options?.additionalFiles ?? []).map( (file) => path.relative(directory, file) ) ]; for (const file of filesToZip) { const absolutePath = path.resolve(directory, file); if (file.endsWith(".json")) { const content = await fs.readFile(absolutePath, "utf-8"); archive.file( file, await options?.transform?.(absolutePath, file, content) || content ); } else { const content = await fs.readFile(absolutePath); archive.file(file, content); } } await options?.additionalWork?.(archive); await new Promise( (resolve, reject) => archive.generateNodeStream({ type: "nodebuffer", ...wxt.config.zip.compressionLevel === 0 ? { compression: "STORE" } : { compression: "DEFLATE", compressionOptions: { level: wxt.config.zip.compressionLevel } } }).pipe(fs.createWriteStream(outputPath)).on("error", reject).on("close", resolve) ); } async function downloadPrivatePackages() { const overrides = {}; const files = []; if (wxt.config.zip.downloadPackages.length > 0) { const _downloadPackages = new Set(wxt.config.zip.downloadPackages); const allPackages = await wxt.pm.listDependencies({ all: true, cwd: wxt.config.root }); const downloadPackages = allPackages.filter( (pkg) => _downloadPackages.has(pkg.name) ); for (const pkg of downloadPackages) { wxt.logger.info(`Downloading package: ${pkg.name}@${pkg.version}`); const id = `${pkg.name}@${pkg.version}`; const tgzPath = await wxt.pm.downloadDependency( id, wxt.config.zip.downloadedPackagesDir ); files.push(tgzPath); overrides[id] = tgzPath; } } return { overrides, files }; } function addOverridesToPackageJson(absolutePackageJsonPath, content, overrides) { if (Object.keys(overrides).length === 0) return content; const packageJsonDir = path.dirname(absolutePackageJsonPath); const oldPackage = JSON.parse(content); const newPackage = { ...oldPackage, [wxt.pm.overridesKey]: { ...oldPackage[wxt.pm.overridesKey] } }; Object.entries(overrides).forEach(([key, absolutePath]) => { newPackage[wxt.pm.overridesKey][key] = "file://./" + normalizePath(path.relative(packageJsonDir, absolutePath)); }); return JSON.stringify(newPackage, null, 2); }