Skip to content

Commit ac8408d

Browse files
kaigritunlishaduckTensai75aklinker1PatrykKuniczak
authored
fix(zip)!: make includeSources/excludeSources follow standard allowlist behavior (#2114)
Co-authored-by: Eli <88557639+lishaduck@users.noreply.github.com> Co-authored-by: ТΞNSΛI <tensai@gmx.net> Co-authored-by: Aaron <aaronklinker1@gmail.com> Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com> Co-authored-by: Kai Gritun <kai@kaigritun.com>
1 parent 59e7d1c commit ac8408d

7 files changed

Lines changed: 109 additions & 153 deletions

File tree

packages/wxt/e2e/tests/zip.test.ts

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ describe('Zipping', () => {
110110
expect(await project.pathExists(unzipDir, '.hidden-dir/file')).toBe(false);
111111
});
112112

113-
it('should not zip files inside hidden directories if only the directory is specified', async () => {
113+
it('should zip hidden files and directories when dotSources is enabled', async () => {
114114
const project = new TestProject({
115115
name: 'test',
116116
version: '1.0.0',
@@ -127,17 +127,17 @@ describe('Zipping', () => {
127127
await project.zip({
128128
browser: 'firefox',
129129
zip: {
130-
includeSources: ['.hidden-dir'],
130+
dotSources: true,
131131
},
132132
});
133133
await extract(sourcesZip, { dir: unzipDir });
134-
expect(await project.pathExists(unzipDir, '.hidden-dir/file')).toBe(false);
134+
expect(await project.pathExists(unzipDir, '.hidden-dir/file')).toBe(true);
135135
expect(await project.pathExists(unzipDir, '.hidden-dir/nested/file')).toBe(
136-
false,
136+
true,
137137
);
138138
});
139139

140-
it('should allow zipping hidden files into sources when explicitly listed', async () => {
140+
it('should allow ignoring some hidden files', async () => {
141141
const project = new TestProject({
142142
name: 'test',
143143
version: '1.0.0',
@@ -156,20 +156,57 @@ describe('Zipping', () => {
156156
await project.zip({
157157
browser: 'firefox',
158158
zip: {
159-
includeSources: ['.env', '.hidden-dir/file', '.hidden-dir/nested/**'],
159+
dotSources: true,
160+
excludeSources: ['.hidden-dir/nested'],
160161
},
161162
});
162163
await extract(sourcesZip, { dir: unzipDir });
163164
expect(await project.pathExists(unzipDir, '.env')).toBe(true);
164165
expect(await project.pathExists(unzipDir, '.hidden-dir/file')).toBe(true);
165166
expect(await project.pathExists(unzipDir, '.hidden-dir/nested/file1')).toBe(
166-
true,
167+
false,
167168
);
168169
expect(await project.pathExists(unzipDir, '.hidden-dir/nested/file2')).toBe(
169-
true,
170+
false,
170171
);
171172
});
172173

174+
it('should not include all files when includeSources is provided', async () => {
175+
const project = new TestProject({
176+
name: 'test',
177+
version: '1.0.0',
178+
});
179+
project.addFile(
180+
'entrypoints/background.ts',
181+
'export default defineBackground(() => {});',
182+
);
183+
project.addFile('utils/example.ts', 'export const x = 1;');
184+
project.addFile('secrets/api-key.txt', 'supersecret');
185+
project.addFile('cache/data.json', '{}');
186+
const unzipDir = project.resolvePath('.output/test-1.0.0-sources');
187+
const sourcesZip = project.resolvePath('.output/test-1.0.0-sources.zip');
188+
189+
await project.zip({
190+
browser: 'firefox',
191+
zip: {
192+
includeSources: ['entrypoints/**', 'utils/**'],
193+
},
194+
});
195+
await extract(sourcesZip, { dir: unzipDir });
196+
197+
// Included files should be present
198+
expect(
199+
await project.pathExists(unzipDir, 'entrypoints/background.ts'),
200+
).toBe(true);
201+
expect(await project.pathExists(unzipDir, 'utils/example.ts')).toBe(true);
202+
203+
// Non-included files should NOT be present (allowlist behavior)
204+
expect(await project.pathExists(unzipDir, 'secrets/api-key.txt')).toBe(
205+
false,
206+
);
207+
expect(await project.pathExists(unzipDir, 'cache/data.json')).toBe(false);
208+
});
209+
173210
it('should exclude skipped entrypoints from respective browser sources zip', async () => {
174211
const project = new TestProject({
175212
name: 'test',
@@ -188,8 +225,7 @@ describe('Zipping', () => {
188225
`export default defineContentScript({
189226
matches: ['*://*/*'],
190227
main(ctx) {},
191-
});
192-
`,
228+
});`,
193229
);
194230
const unzipDir = project.resolvePath('.output/test-1.0.0-sources');
195231
const sourcesZip = project.resolvePath('.output/test-1.0.0-sources.zip');
@@ -289,7 +325,8 @@ describe('Zipping', () => {
289325

290326
await project.zip({
291327
zip: {
292-
exclude: ['**/*.json', '!manifest.json'],
328+
// Exclude all JSON files except for ones named `manifest.json`
329+
exclude: ['**/!(manifest).json'],
293330
},
294331
});
295332

packages/wxt/src/core/resolve-config.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ function resolveZipConfig(
304304
artifactTemplate:
305305
'{{name}}-{{packageVersion}}-{{browser}}{{modeSuffix}}.zip',
306306
sourcesRoot: root,
307-
includeSources: [],
307+
includeSources: mergedConfig.zip?.includeSources ?? ['**/*'],
308308
compressionLevel: 9,
309309
...mergedConfig.zip,
310310
zipSources:
@@ -314,8 +314,6 @@ function resolveZipConfig(
314314
'**/node_modules',
315315
// WXT files
316316
'**/web-ext.config.ts',
317-
// Hidden files
318-
'**/.*',
319317
// Tests
320318
'**/__tests__/**',
321319
'**/*.+(test|spec).?(c|m)+(j|t)s?(x)',
@@ -324,6 +322,7 @@ function resolveZipConfig(
324322
// From user
325323
...(mergedConfig.zip?.excludeSources ?? []),
326324
],
325+
dotSources: mergedConfig.zip?.dotSources ?? false,
327326
downloadPackages: mergedConfig.zip?.downloadPackages ?? [],
328327
downloadedPackagesDir,
329328
};

packages/wxt/src/core/utils/__tests__/picomatch-multiple.test.ts

Lines changed: 0 additions & 71 deletions
This file was deleted.

packages/wxt/src/core/utils/picomatch-multiple.ts

Lines changed: 0 additions & 38 deletions
This file was deleted.

packages/wxt/src/core/utils/testing/fake-objects.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
285285
artifactTemplate: '{{name}}-{{version}}.zip',
286286
includeSources: [],
287287
excludeSources: [],
288+
dotSources: false,
288289
exclude: [],
289290
sourcesRoot: fakeDir(),
290291
sourcesTemplate: '{{name}}-sources.zip',

packages/wxt/src/core/zip.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { registerWxt, wxt } from './wxt';
1111
import JSZip from 'jszip';
1212
import { glob } from 'tinyglobby';
1313
import { normalizePath } from './utils';
14-
import { picomatchMultiple } from './utils/picomatch-multiple';
1514

1615
/**
1716
* Build and zip the extension for distribution.
@@ -70,7 +69,7 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
7069
...skippedEntrypoints.map((entry) =>
7170
path.relative(wxt.config.zip.sourcesRoot, entry.inputPath),
7271
),
73-
].map((paths) => paths.replaceAll('\\', '/'));
72+
].map((paths) => paths.replaceAll('\\', '/')); // TODO: Use normalizePath?
7473
await wxt.hooks.callHook('zip:sources:start', wxt);
7574
const { overrides, files: downloadedPackages } =
7675
await downloadPrivatePackages();
@@ -88,6 +87,7 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
8887
}
8988
},
9089
additionalFiles: downloadedPackages,
90+
dot: wxt.config.zip.dotSources,
9191
});
9292
zipFiles.push(sourcesZipPath);
9393
await wxt.hooks.callHook('zip:sources:done', wxt, sourcesZipPath);
@@ -118,22 +118,17 @@ async function zipDir(
118118
) => Promise<string | undefined | void> | string | undefined | void;
119119
additionalWork?: (archive: JSZip) => Promise<void> | void;
120120
additionalFiles?: string[];
121+
dot?: boolean;
121122
},
122123
): Promise<void> {
123124
const archive = new JSZip();
124-
const files = (
125-
await glob(['**/*', ...(options?.include || [])], {
126-
cwd: directory,
127-
// Ignore node_modules, otherwise this glob step takes forever
128-
ignore: ['**/node_modules'],
129-
onlyFiles: true,
130-
expandDirectories: false,
131-
})
132-
).filter((relativePath) => {
133-
return (
134-
picomatchMultiple(relativePath, options?.include) ||
135-
!picomatchMultiple(relativePath, options?.exclude)
136-
);
125+
// includeSources patterns are used directly (defaults to ['**/*'] from config)
126+
// excludeSources patterns are passed to glob's ignore option for efficient filtering
127+
const files = await glob(options?.include ?? ['**/*'], {
128+
cwd: directory,
129+
ignore: options?.exclude ?? [],
130+
onlyFiles: true,
131+
dot: options?.dot,
137132
});
138133
const filesToZip = [
139134
...files,

0 commit comments

Comments
 (0)