Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 48 additions & 11 deletions packages/wxt/e2e/tests/zip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ describe('Zipping', () => {
expect(await project.pathExists(unzipDir, '.hidden-dir/file')).toBe(false);
});

it('should not zip files inside hidden directories if only the directory is specified', async () => {
it('should zip hidden files and directories when dotSources is enabled', async () => {
const project = new TestProject({
name: 'test',
version: '1.0.0',
Expand All @@ -127,17 +127,17 @@ describe('Zipping', () => {
await project.zip({
browser: 'firefox',
zip: {
includeSources: ['.hidden-dir'],
dotSources: true,
},
});
await extract(sourcesZip, { dir: unzipDir });
expect(await project.pathExists(unzipDir, '.hidden-dir/file')).toBe(false);
expect(await project.pathExists(unzipDir, '.hidden-dir/file')).toBe(true);
expect(await project.pathExists(unzipDir, '.hidden-dir/nested/file')).toBe(
false,
true,
);
});

it('should allow zipping hidden files into sources when explicitly listed', async () => {
it('should allow ignoring some hidden files', async () => {
const project = new TestProject({
name: 'test',
version: '1.0.0',
Expand All @@ -156,20 +156,57 @@ describe('Zipping', () => {
await project.zip({
browser: 'firefox',
zip: {
includeSources: ['.env', '.hidden-dir/file', '.hidden-dir/nested/**'],
dotSources: true,
excludeSources: ['.hidden-dir/nested'],
},
});
await extract(sourcesZip, { dir: unzipDir });
expect(await project.pathExists(unzipDir, '.env')).toBe(true);
expect(await project.pathExists(unzipDir, '.hidden-dir/file')).toBe(true);
expect(await project.pathExists(unzipDir, '.hidden-dir/nested/file1')).toBe(
true,
false,
);
expect(await project.pathExists(unzipDir, '.hidden-dir/nested/file2')).toBe(
true,
false,
);
});

it('should not include all files when includeSources is provided', async () => {
const project = new TestProject({
name: 'test',
version: '1.0.0',
});
project.addFile(
'entrypoints/background.ts',
'export default defineBackground(() => {});',
);
project.addFile('utils/example.ts', 'export const x = 1;');
project.addFile('secrets/api-key.txt', 'supersecret');
project.addFile('cache/data.json', '{}');
const unzipDir = project.resolvePath('.output/test-1.0.0-sources');
const sourcesZip = project.resolvePath('.output/test-1.0.0-sources.zip');

await project.zip({
browser: 'firefox',
zip: {
includeSources: ['entrypoints/**', 'utils/**'],
},
});
await extract(sourcesZip, { dir: unzipDir });

// Included files should be present
expect(
await project.pathExists(unzipDir, 'entrypoints/background.ts'),
).toBe(true);
expect(await project.pathExists(unzipDir, 'utils/example.ts')).toBe(true);

// Non-included files should NOT be present (allowlist behavior)
expect(await project.pathExists(unzipDir, 'secrets/api-key.txt')).toBe(
false,
);
expect(await project.pathExists(unzipDir, 'cache/data.json')).toBe(false);
});

it('should exclude skipped entrypoints from respective browser sources zip', async () => {
const project = new TestProject({
name: 'test',
Expand All @@ -188,8 +225,7 @@ describe('Zipping', () => {
`export default defineContentScript({
matches: ['*://*/*'],
main(ctx) {},
});
`,
});`,
);
const unzipDir = project.resolvePath('.output/test-1.0.0-sources');
const sourcesZip = project.resolvePath('.output/test-1.0.0-sources.zip');
Expand Down Expand Up @@ -289,7 +325,8 @@ describe('Zipping', () => {

await project.zip({
zip: {
exclude: ['**/*.json', '!manifest.json'],
// Exclude all JSON files except for ones named `manifest.json`
exclude: ['**/!(manifest).json'],
},
});

Expand Down
5 changes: 2 additions & 3 deletions packages/wxt/src/core/resolve-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ function resolveZipConfig(
artifactTemplate:
'{{name}}-{{packageVersion}}-{{browser}}{{modeSuffix}}.zip',
sourcesRoot: root,
includeSources: [],
includeSources: mergedConfig.zip?.includeSources ?? ['**/*'],
compressionLevel: 9,
...mergedConfig.zip,
zipSources:
Expand All @@ -314,8 +314,6 @@ function resolveZipConfig(
'**/node_modules',
// WXT files
'**/web-ext.config.ts',
// Hidden files
'**/.*',
// Tests
'**/__tests__/**',
'**/*.+(test|spec).?(c|m)+(j|t)s?(x)',
Expand All @@ -324,6 +322,7 @@ function resolveZipConfig(
// From user
...(mergedConfig.zip?.excludeSources ?? []),
],
dotSources: mergedConfig.zip?.dotSources ?? false,
downloadPackages: mergedConfig.zip?.downloadPackages ?? [],
downloadedPackagesDir,
};
Expand Down
71 changes: 0 additions & 71 deletions packages/wxt/src/core/utils/__tests__/picomatch-multiple.test.ts

This file was deleted.

38 changes: 0 additions & 38 deletions packages/wxt/src/core/utils/picomatch-multiple.ts

This file was deleted.

1 change: 1 addition & 0 deletions packages/wxt/src/core/utils/testing/fake-objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
artifactTemplate: '{{name}}-{{version}}.zip',
includeSources: [],
excludeSources: [],
dotSources: false,
exclude: [],
sourcesRoot: fakeDir(),
sourcesTemplate: '{{name}}-sources.zip',
Expand Down
25 changes: 10 additions & 15 deletions packages/wxt/src/core/zip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { registerWxt, wxt } from './wxt';
import JSZip from 'jszip';
import { glob } from 'tinyglobby';
import { normalizePath } from './utils';
import { picomatchMultiple } from './utils/picomatch-multiple';

/**
* Build and zip the extension for distribution.
Expand Down Expand Up @@ -70,7 +69,7 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
...skippedEntrypoints.map((entry) =>
path.relative(wxt.config.zip.sourcesRoot, entry.inputPath),
),
].map((paths) => paths.replaceAll('\\', '/'));
].map((paths) => paths.replaceAll('\\', '/')); // TODO: Use normalizePath?
await wxt.hooks.callHook('zip:sources:start', wxt);
const { overrides, files: downloadedPackages } =
await downloadPrivatePackages();
Expand All @@ -88,6 +87,7 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
}
},
additionalFiles: downloadedPackages,
dot: wxt.config.zip.dotSources,
});
zipFiles.push(sourcesZipPath);
await wxt.hooks.callHook('zip:sources:done', wxt, sourcesZipPath);
Expand Down Expand Up @@ -118,22 +118,17 @@ async function zipDir(
) => Promise<string | undefined | void> | string | undefined | void;
additionalWork?: (archive: JSZip) => Promise<void> | void;
additionalFiles?: string[];
dot?: boolean;
},
): Promise<void> {
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,
expandDirectories: false,
})
).filter((relativePath) => {
return (
picomatchMultiple(relativePath, options?.include) ||
!picomatchMultiple(relativePath, options?.exclude)
);
// includeSources patterns are used directly (defaults to ['**/*'] from config)
// excludeSources patterns are passed to glob's ignore option for efficient filtering
const files = await glob(options?.include ?? ['**/*'], {
cwd: directory,
ignore: options?.exclude ?? [],
onlyFiles: true,
dot: options?.dot,
});
const filesToZip = [
...files,
Expand Down
45 changes: 39 additions & 6 deletions packages/wxt/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,28 +254,58 @@ export interface InlineConfig {
* include when creating a ZIP of all your source code for Firefox. Patterns
* are relative to your `config.zip.sourcesRoot`.
*
* This setting overrides `excludeSources`. So if a file matches both lists,
* it is included in the ZIP.
* Sources ZIP files are created using standard allowlist/blocklist
* behavior:
*
* - You specify a pattern to "include" (via `includeSources`), then a pattern
* to "exclude" from the included files (via `excludeSources`).
*
* By default, this option includes all files except for hidden files and
* directories (files/directories starting with a `.`).
*
* If you want to include hidden files/directories in your sources ZIP, see
* `InlineConfig.zip.dotSources`.
*
* @example
* [
* 'coverage', // Include the coverage directory in the `sourcesRoot`
* ];
* ['entrypoints/**', 'wxt.config.ts', 'package.json', 'tsconfig.json'];
*/
includeSources?: string[];
/**
* [Picomatch](https://www.npmjs.com/package/picomatch) patterns of files to
* exclude when creating a ZIP of all your source code for Firefox. Patterns
* are relative to your `config.zip.sourcesRoot`.
*
* Hidden files, node_modules, and tests are ignored by default.
* By default, WXT excludes some files:
*
* - `node_modules`
* - Tests files and directories
* - Output directory
*
* Any values specified in this option will be merged with the ones above -
* you cannot replace the default values, only add to them.
*
* @example
* [
* 'coverage', // Ignore the coverage directory in the `sourcesRoot`
* ];
*/
excludeSources?: string[];
/**
* Include hidden files/directories in your sources ZIP.
*
* [Picomatch](https://www.npmjs.com/package/picomatch) does not match
* against files and directory that start with a `.` by default. For
* example, if you need to include a `.env` file, you need to set this to
* `true`, then exclude other hidden files/directories in `excludeSources`.
*
* **Be very careful when this is enabled - WXT may include files with
* secrets in your ZIP you did not intend to share with mozzila or upload to
* other places**. Make sure all hidden files you don't want to include are
* added to `excludeSources`.
*
* @default false
*/
dotSources?: boolean;
/**
* [Picomatch](https://www.npmjs.com/package/picomatch) patterns of files to
* exclude when zipping the extension.
Expand All @@ -284,6 +314,8 @@ export interface InlineConfig {
* [
* '**\/*.map', // Exclude all sourcemaps
* ];
*
* @default [ ]
*/
exclude?: string[];
/**
Expand Down Expand Up @@ -1533,6 +1565,7 @@ export interface ResolvedConfig {
sourcesTemplate: string;
includeSources: string[];
excludeSources: string[];
dotSources: boolean;
sourcesRoot: string;
downloadedPackagesDir: string;
downloadPackages: string[];
Expand Down
Loading