diff --git a/package.json b/package.json index c67366ad..8b354817 100644 --- a/package.json +++ b/package.json @@ -128,6 +128,7 @@ "is-wsl": "^3.0.0", "jiti": "^1.21.0", "json5": "^2.2.3", + "jszip": "^3.10.1", "linkedom": "^0.16.1", "minimatch": "^9.0.3", "natural-compare": "^1.4.0", @@ -139,8 +140,7 @@ "unimport": "^3.4.0", "vite": "^5.1.3", "web-ext-run": "^0.2.0", - "webextension-polyfill": "^0.10.0", - "zip-dir": "^2.0.0" + "webextension-polyfill": "^0.10.0" }, "devDependencies": { "@faker-js/faker": "^8.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92d69cc1..15c742d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,6 +74,9 @@ importers: json5: specifier: ^2.2.3 version: 2.2.3 + jszip: + specifier: ^3.10.1 + version: 3.10.1 linkedom: specifier: ^0.16.1 version: 0.16.1 @@ -110,9 +113,6 @@ importers: webextension-polyfill: specifier: ^0.10.0 version: 0.10.0 - zip-dir: - specifier: ^2.0.0 - version: 2.0.0 devDependencies: '@faker-js/faker': specifier: ^8.3.1 diff --git a/src/core/zip.ts b/src/core/zip.ts index 19ef3844..20f10105 100644 --- a/src/core/zip.ts +++ b/src/core/zip.ts @@ -1,6 +1,5 @@ import { InlineConfig } from '~/types'; -import zipdir from 'zip-dir'; -import { dirname, relative, resolve } from 'node:path'; +import path from 'node:path'; import fs from 'fs-extra'; import { kebabCaseAlphanumeric } from '~/core/utils/strings'; import { getPackageJson } from '~/core/utils/package'; @@ -9,10 +8,12 @@ import { formatDuration } from '~/core/utils/time'; import { printFileList } from '~/core/utils/log/printFileList'; import { internalBuild } from '~/core/utils/building'; import { registerWxt, wxt } from './wxt'; +import JSZip from 'jszip'; +import glob from 'fast-glob'; /** * Build and zip the extension for distribution. - * @param config Opitonal config that will override your `/wxt.config.ts`. + * @param config Optional config that will override your `/wxt.config.ts`. * @returns A list of all files included in the ZIP. */ export async function zip(config?: InlineConfig): Promise { @@ -26,7 +27,7 @@ export async function zip(config?: InlineConfig): Promise { const projectName = wxt.config.zip.name ?? kebabCaseAlphanumeric( - (await getPackageJson())?.name || dirname(process.cwd()), + (await getPackageJson())?.name || path.dirname(process.cwd()), ); const applyTemplate = (template: string): string => template @@ -43,31 +44,21 @@ export async function zip(config?: InlineConfig): Promise { // ZIP output directory const outZipFilename = applyTemplate(wxt.config.zip.artifactTemplate); - const outZipPath = resolve(wxt.config.outBaseDir, outZipFilename); - await zipdir(wxt.config.outDir, { - saveTo: outZipPath, - }); + const outZipPath = path.resolve(wxt.config.outBaseDir, outZipFilename); + await zipDir(wxt.config.outDir, outZipPath); zipFiles.push(outZipPath); // ZIP sources for Firefox if (wxt.config.browser === 'firefox') { const sourcesZipFilename = applyTemplate(wxt.config.zip.sourcesTemplate); - const sourcesZipPath = resolve(wxt.config.outBaseDir, sourcesZipFilename); - await zipdir(wxt.config.zip.sourcesRoot, { - saveTo: sourcesZipPath, - filter(path) { - const relativePath = relative(wxt.config.zip.sourcesRoot, path); - - return ( - wxt.config.zip.includeSources.some((pattern) => - minimatch(relativePath, pattern), - ) || - !wxt.config.zip.excludeSources.some((pattern) => - minimatch(relativePath, pattern), - ) - ); - }, + const sourcesZipPath = path.resolve( + wxt.config.outBaseDir, + sourcesZipFilename, + ); + await zipDir(wxt.config.zip.sourcesRoot, sourcesZipPath, { + include: wxt.config.zip.includeSources, + exclude: wxt.config.zip.excludeSources, }); zipFiles.push(sourcesZipPath); } @@ -81,3 +72,51 @@ export async function zip(config?: InlineConfig): Promise { return zipFiles; } +async function zipDir( + directory: string, + outputPath: string, + options?: { + include?: string[]; + exclude?: string[]; + transform?: ( + file: string, + content: string, + ) => Promise | string | undefined | void; + additionalWork?: (archive: JSZip) => Promise | void; + }, +): Promise { + const archive = new JSZip(); + const files = ( + await glob('**/*', { + cwd: directory, + // Ignore node_modules, otherwise this glob step takes forever + ignore: ['**/node_modules'], + onlyFiles: true, + }) + ).filter((relativePath) => { + return ( + wxt.config.zip.includeSources.some((pattern) => + minimatch(relativePath, pattern), + ) || + !wxt.config.zip.excludeSources.some((pattern) => + minimatch(relativePath, pattern), + ) + ); + }); + for (const file of files) { + const absolutePath = path.resolve(directory, file); + if (file.endsWith('.json')) { + const content = await fs.readFile(absolutePath, 'utf-8'); + archive.file( + file, + (await options?.transform?.(file, content)) || content, + ); + } else { + const content = await fs.readFile(absolutePath); + archive.file(file, content); + } + } + await options?.additionalWork?.(archive); + const buffer = await archive.generateAsync({ type: 'base64' }); + await fs.writeFile(outputPath, buffer, 'base64'); +}