Skip to content
Merged
22 changes: 22 additions & 0 deletions packages/wxt/e2e/tests/zip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,4 +285,26 @@ describe('Zipping', () => {
expect(await project.fileExists(sourcesZip)).toBe(false);
},
);

it('should include files in the zip when negated in zip.exclude', async () => {
const project = new TestProject({
name: 'test',
version: '1.0.0',
});
project.addFile(
'entrypoints/background.ts',
'export default defineBackground(() => {});',
);
const unzipDir = project.resolvePath('.output/test-1.0.0-chrome');
const sourcesZip = project.resolvePath('.output/test-1.0.0-chrome.zip');

await project.zip({
zip: {
exclude: ['**/*.json', '!manifest.json'],
},
});

await extract(sourcesZip, { dir: unzipDir });
expect(await project.fileExists(unzipDir, 'manifest.json')).toBe(true);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, it, expect } from 'vitest';
import { minimatchMultiple } from '../minimatch-multiple';

describe('minimatchMultiple', () => {
it('should return false if the pattern array is undefined', () => {
const patterns = undefined;
const search = 'test.json';

expect(minimatchMultiple(search, patterns)).toBe(false);
});

it('should return false if the pattern array is empty', () => {
const patterns: string[] = [];
const search = 'test.json';

expect(minimatchMultiple(search, patterns)).toBe(false);
});

it('should return true if the pattern array contains a match', () => {
const patterns = ['test.yml', 'test.json'];
const search = 'test.json';

expect(minimatchMultiple(search, patterns)).toBe(true);
});

it('should return false if the pattern array does not contain a match', () => {
const patterns = ['test.yml', 'test.json'];
const search = 'test.txt';

expect(minimatchMultiple(search, patterns)).toBe(false);
});

it('should return false if the pattern matches a negative pattern', () => {
const patterns = ['test.*', '!test.json'];
const search = 'test.json';

expect(minimatchMultiple(search, patterns)).toBe(false);
});

it('should return false if the pattern matches a negative pattern, regardless of order', () => {
const patterns = ['!test.json', 'test.*'];
const search = 'test.json';

expect(minimatchMultiple(search, patterns)).toBe(false);
});
});
40 changes: 40 additions & 0 deletions packages/wxt/src/core/utils/minimatch-multiple.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { minimatch, MinimatchOptions } from 'minimatch';

/**
* Run [`minimatch`](https://npmjs.com/package/minimatch) against multiple
* patterns.
*
* Supports negated patterns, the order does not matter. If your `search` string
* matches any of the negative patterns, it will return `false`.
*
* @example
* ```ts
* minimatchMultiple('a.json', ['*.json', '!b.json']); // => true
* minimatchMultiple('b.json', ['*.json', '!b.json']); // => false
* ```
*/
export function minimatchMultiple(
search: string,
patterns: string[] | undefined,
options?: MinimatchOptions,
): boolean {
if (patterns == null) return false;

const negatePatterns: string[] = [];
const positivePatterns: string[] = [];
for (const pattern of patterns) {
if (pattern[0] === '!') negatePatterns.push(pattern.slice(1));
else positivePatterns.push(pattern);
}

if (
negatePatterns.some((negatePattern) =>
minimatch(search, negatePattern, options),
)
)
return false;

return positivePatterns.some((positivePattern) =>
minimatch(search, positivePattern, options),
);
}
6 changes: 3 additions & 3 deletions packages/wxt/src/core/zip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import path from 'node:path';
import fs from 'fs-extra';
import { safeFilename } from './utils/strings';
import { getPackageJson } from './utils/package';
import { minimatch } from 'minimatch';
import { formatDuration } from './utils/time';
import { printFileList } from './utils/log/printFileList';
import { findEntrypoints, internalBuild } from './utils/building';
import { registerWxt, wxt } from './wxt';
import JSZip from 'jszip';
import glob from 'fast-glob';
import { normalizePath } from './utils/paths';
import { minimatchMultiple } from './utils/minimatch-multiple';

/**
* Build and zip the extension for distribution.
Expand Down Expand Up @@ -122,8 +122,8 @@ async function zipDir(
})
).filter((relativePath) => {
return (
options?.include?.some((pattern) => minimatch(relativePath, pattern)) ||
!options?.exclude?.some((pattern) => minimatch(relativePath, pattern))
minimatchMultiple(relativePath, options?.include) ||
!minimatchMultiple(relativePath, options?.exclude)
);
});
const filesToZip = [
Expand Down