Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 155626fbed | |||
| e802aa211f | |||
| 54fe8c6687 | |||
| 5da6e4ff75 | |||
| 4090bb100e | |||
| 54ed99d91d | |||
| 3db3eac62a | |||
| 0315882fa4 | |||
| 9757e2634f | |||
| 42b55f66fb | |||
| e64058bc94 |
@@ -283,7 +283,7 @@
|
||||
},
|
||||
"packages/wxt": {
|
||||
"name": "wxt",
|
||||
"version": "0.20.23",
|
||||
"version": "0.20.25",
|
||||
"bin": {
|
||||
"wxt": "./bin/wxt.mjs",
|
||||
"wxt-publish-extension": "./bin/wxt-publish-extension.mjs",
|
||||
|
||||
@@ -200,6 +200,23 @@ export default defineConfig({
|
||||
});
|
||||
```
|
||||
|
||||
:::warning
|
||||
|
||||
Different browsers support different permissions. You are responsible for passing only the permissions required for each browser:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: ({ browser }) => ({
|
||||
permissions:
|
||||
browser === 'chrome'
|
||||
? ['storage', 'favicon', 'declarativeNetRequest']
|
||||
: ['storage', 'webRequest'],
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Host Permissions
|
||||
|
||||
> [Chrome docs](https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions#host-permissions)
|
||||
|
||||
@@ -35,14 +35,16 @@ describe('I18n Types', () => {
|
||||
});
|
||||
|
||||
describe('With type-safety', () => {
|
||||
const i18n = createI18n<{
|
||||
type MyStructure = {
|
||||
simple: { plural: false; substitutions: 0 };
|
||||
simpleSub1: { plural: false; substitutions: 1 };
|
||||
simpleSub2: { plural: false; substitutions: 2 };
|
||||
plural: { plural: true; substitutions: 0 };
|
||||
pluralSub1: { plural: true; substitutions: 1 };
|
||||
pluralSub2: { plural: true; substitutions: 2 };
|
||||
}>();
|
||||
};
|
||||
|
||||
const i18n = createI18n<MyStructure>();
|
||||
|
||||
describe('t', () => {
|
||||
it('should only allow passing valid combinations of arguments', () => {
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
/** @module @wxt-dev/i18n */
|
||||
import {
|
||||
I18nStructure,
|
||||
DefaultI18nStructure,
|
||||
I18n,
|
||||
Substitution,
|
||||
} from './types';
|
||||
import { I18nStructure, I18n, Substitution, UntypedI18n } from './types';
|
||||
import { browser } from '@wxt-dev/browser';
|
||||
|
||||
export function createI18n<
|
||||
T extends I18nStructure = DefaultI18nStructure,
|
||||
>(): I18n<T> {
|
||||
const t = (key: string, ...args: any[]) => {
|
||||
export function createI18n(): UntypedI18n;
|
||||
export function createI18n<T extends I18nStructure>(): I18n<T>;
|
||||
export function createI18n(): UntypedI18n {
|
||||
const t: UntypedI18n['t'] = (key: string, ...args: unknown[]) => {
|
||||
// Resolve args
|
||||
let sub: Substitution[] | undefined;
|
||||
let count: number | undefined;
|
||||
@@ -64,5 +59,5 @@ export function createI18n<
|
||||
}
|
||||
};
|
||||
|
||||
return { t } as I18n<T>;
|
||||
return { t };
|
||||
}
|
||||
|
||||
@@ -3,13 +3,22 @@ export interface I18nFeatures {
|
||||
substitutions: SubstitutionCount;
|
||||
}
|
||||
|
||||
export type I18nStructure = {
|
||||
[K: string]: I18nFeatures;
|
||||
export interface UntypedI18n {
|
||||
t: UntypedTFunction;
|
||||
}
|
||||
|
||||
export type UntypedTFunction = {
|
||||
(key: string): string;
|
||||
(key: string, substitutions: Substitution[]): string;
|
||||
(key: string, n: number): string;
|
||||
(key: string, n: number, substitutions: Substitution[]): string;
|
||||
};
|
||||
|
||||
export type DefaultI18nStructure = {
|
||||
[K: string]: any;
|
||||
};
|
||||
export type I18nStructure = Record<string, I18nFeatures>;
|
||||
|
||||
export interface I18n<T extends I18nStructure> {
|
||||
t: TFunction<T>;
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
export type SubstitutionTuple<T extends SubstitutionCount> =
|
||||
@@ -66,10 +75,6 @@ export type TFunction<T extends I18nStructure> = {
|
||||
): string;
|
||||
};
|
||||
|
||||
export interface I18n<T extends DefaultI18nStructure> {
|
||||
t: TFunction<T>;
|
||||
}
|
||||
|
||||
export type Substitution = string | number;
|
||||
|
||||
type SubstitutionCount = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
||||
|
||||
@@ -25,6 +25,7 @@ describe('Options', () => {
|
||||
describe('extensionDir', () => {
|
||||
it('should default to the current working directory', async () => {
|
||||
const actual = await resolveRunOptions({});
|
||||
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
extensionDir: process.cwd(),
|
||||
});
|
||||
@@ -34,17 +35,27 @@ describe('Options', () => {
|
||||
const actual = await resolveRunOptions({
|
||||
extensionDir: './path/to/extension',
|
||||
});
|
||||
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
extensionDir: resolve(process.cwd(), './path/to/extension'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should use absolute paths as-is', async () => {
|
||||
const actual = await resolveRunOptions({
|
||||
extensionDir: '/abs/path/to/extension2',
|
||||
});
|
||||
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
extensionDir: resolve('/abs/path/to/extension2'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('target', () => {
|
||||
it('should be "chrome" by default', async () => {
|
||||
const actual = await resolveRunOptions({
|
||||
extensionDir: 'path/to/extension',
|
||||
});
|
||||
const actual = await resolveRunOptions({});
|
||||
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
target: 'chrome',
|
||||
});
|
||||
@@ -52,12 +63,12 @@ describe('Options', () => {
|
||||
|
||||
it('should be what is passed in', async () => {
|
||||
const actual = await resolveRunOptions({
|
||||
extensionDir: 'path/to/extension',
|
||||
target: 'custom',
|
||||
browserBinaries: {
|
||||
custom: '/path/to/custom/browser',
|
||||
},
|
||||
});
|
||||
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
target: 'custom',
|
||||
});
|
||||
@@ -65,9 +76,9 @@ describe('Options', () => {
|
||||
|
||||
it('should throw an error if the target binary could not be found', async () => {
|
||||
const actual = resolveRunOptions({
|
||||
extensionDir: 'path/to/extension',
|
||||
target: 'custom',
|
||||
});
|
||||
|
||||
await expect(actual).rejects.toThrow('Could not find "custom" binary.');
|
||||
});
|
||||
});
|
||||
@@ -83,7 +94,6 @@ describe('Options', () => {
|
||||
? 'C:\\path\\to\\custom\\browser.exe'
|
||||
: path;
|
||||
const actual = await resolveRunOptions({
|
||||
extensionDir: 'path/to/extension',
|
||||
target: 'custom',
|
||||
browserBinaries: {
|
||||
custom: path,
|
||||
@@ -98,9 +108,11 @@ describe('Options', () => {
|
||||
describe('chromiumArgs', () => {
|
||||
it('should log a warning when --user-data-dir is passed in', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn');
|
||||
|
||||
await resolveRunOptions({
|
||||
chromiumArgs: ['--user-data-dir=some/custom/path'],
|
||||
});
|
||||
|
||||
expect(warnSpy).toBeCalledTimes(1);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
@@ -111,9 +123,11 @@ describe('Options', () => {
|
||||
|
||||
it('should log a warning when --remote-debugging-port is passed in', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn');
|
||||
|
||||
await resolveRunOptions({
|
||||
chromiumArgs: ['--remote-debugging-port=9222'],
|
||||
});
|
||||
|
||||
expect(warnSpy).toBeCalledTimes(1);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
@@ -126,6 +140,7 @@ describe('Options', () => {
|
||||
const actual = await resolveRunOptions({
|
||||
chromiumArgs: ['--window-size=1920,1080'],
|
||||
});
|
||||
|
||||
expect(actual.chromiumArgs).toEqual([
|
||||
// Defaults
|
||||
'--disable-features=Translate,OptimizationHints,MediaRouter,DialMediaRouteProvider,CalculateNativeWinOcclusion,InterestFeedContentSuggestions,CertificateTransparencyComponentUpdater,AutofillServerCommunication,PrivacySandboxSettings4',
|
||||
@@ -161,9 +176,11 @@ describe('Options', () => {
|
||||
describe('firefoxArgs', () => {
|
||||
it('should log a warning when --profile is passed in', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn');
|
||||
|
||||
await resolveRunOptions({
|
||||
firefoxArgs: ['--profile=some/custom/path'],
|
||||
});
|
||||
|
||||
expect(warnSpy).toBeCalledTimes(1);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Custom Firefox --profile argument ignored'),
|
||||
@@ -172,9 +189,11 @@ describe('Options', () => {
|
||||
|
||||
it('should log a warning when --remote-debugging-port is passed in', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn');
|
||||
|
||||
await resolveRunOptions({
|
||||
firefoxArgs: ['--remote-debugging-port=9222'],
|
||||
});
|
||||
|
||||
expect(warnSpy).toBeCalledTimes(1);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
@@ -187,6 +206,7 @@ describe('Options', () => {
|
||||
const actual = await resolveRunOptions({
|
||||
firefoxArgs: ['--window-size=1920,1080'],
|
||||
});
|
||||
|
||||
expect(actual.firefoxArgs).toEqual([
|
||||
// Defaults
|
||||
'--new-instance',
|
||||
|
||||
@@ -135,11 +135,12 @@ export const KNOWN_BROWSER_PATHS: Record<
|
||||
*/
|
||||
export const FALLBACK_TARGETS: Partial<Record<KnownTarget, KnownTarget[]>> = {
|
||||
chrome: [
|
||||
'arc',
|
||||
'chromium',
|
||||
'chrome-canary',
|
||||
'chrome-beta',
|
||||
'chrome-dev',
|
||||
'brave',
|
||||
'arc',
|
||||
'dia',
|
||||
'edge',
|
||||
'edge-canary',
|
||||
|
||||
@@ -1,5 +1,37 @@
|
||||
# Changelog
|
||||
|
||||
## v0.20.25
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.24...wxt-v0.20.25)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Port change in dev server on reload ([#2283](https://github.com/wxt-dev/wxt/pull/2283))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Update `permissions` section with per-browser warning ([#2284](https://github.com/wxt-dev/wxt/pull/2284))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Increase PNPM test timeout ([3db3eac6](https://github.com/wxt-dev/wxt/commit/3db3eac6))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v0.20.24
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.23...wxt-v0.20.24)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Correct `actionKey` for Firefox in MV3 ([#2274](https://github.com/wxt-dev/wxt/pull/2274))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- John Wong <john_wong@live.com>
|
||||
|
||||
## v0.20.23
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.22...wxt-v0.20.23)
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
import { createServer as createNetServer } from 'node:net';
|
||||
|
||||
/** Starts a TCP server on the given port and returns a cleanup function. */
|
||||
function occupyPort(port: number): Promise<() => Promise<void>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const srv = createNetServer();
|
||||
srv.listen(port, 'localhost', () => {
|
||||
resolve(() => new Promise<void>((res) => srv.close(() => res())));
|
||||
});
|
||||
srv.on('error', reject);
|
||||
});
|
||||
}
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { TestProject, occupyPort } from '../utils';
|
||||
|
||||
describe('Dev Mode', () => {
|
||||
it('should not change ports when restarting the server', async () => {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { registerWxt, wxt } from '../../src/core/wxt';
|
||||
import { TestProject, occupyPort } from '../utils';
|
||||
|
||||
describe('WXT Global', () => {
|
||||
describe('reloadConfig', () => {
|
||||
it('should not change dev server ports when reloading config, even if the port is in-use', async () => {
|
||||
const project = new TestProject();
|
||||
|
||||
await registerWxt('serve', { root: project.root });
|
||||
|
||||
const firstPort = wxt.config.dev.server!.port;
|
||||
expect(firstPort).toBeDefined();
|
||||
|
||||
const cleanup = await occupyPort(firstPort);
|
||||
try {
|
||||
await wxt.reloadConfig();
|
||||
const secondPort = wxt.config.dev.server?.port;
|
||||
expect(secondPort).toBe(firstPort);
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import merge from 'lodash.merge';
|
||||
import spawn, { Subprocess } from 'nano-spawn';
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
||||
import { createServer as createNetServer } from 'node:net';
|
||||
import { dirname, relative, resolve } from 'path';
|
||||
import { glob } from 'tinyglobby';
|
||||
import {
|
||||
@@ -204,3 +205,14 @@ export class TestProject {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Starts a TCP server on the given port and returns a cleanup function. */
|
||||
export function occupyPort(port: number): Promise<() => Promise<void>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const srv = createNetServer();
|
||||
srv.listen(port, 'localhost', () => {
|
||||
resolve(() => new Promise<void>((res) => srv.close(() => res())));
|
||||
});
|
||||
srv.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wxt",
|
||||
"type": "module",
|
||||
"version": "0.20.23",
|
||||
"version": "0.20.25",
|
||||
"description": "⚡ Next-gen Web Extension Framework",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -6,13 +6,13 @@ import { pnpm } from '../pnpm';
|
||||
process.env.WXT_PNPM_IGNORE_WORKSPACE = 'true';
|
||||
|
||||
describe('PNPM Package Management Utils', () => {
|
||||
describe('listDependencies', { timeout: 30e3 }, () => {
|
||||
describe('listDependencies', () => {
|
||||
const cwd = path.resolve(__dirname, 'fixtures/simple-pnpm-project');
|
||||
|
||||
beforeAll(async () => {
|
||||
// PNPM needs the modules installed, or 'pnpm ls' will return a blank list.
|
||||
await spawn('pnpm', ['install'], { cwd });
|
||||
});
|
||||
}, 30e3);
|
||||
|
||||
it('should list direct dependencies', async () => {
|
||||
const actual = await pnpm.listDependencies({ cwd });
|
||||
|
||||
@@ -34,6 +34,7 @@ describe('Manifest Utils', () => {
|
||||
describe('generateManifest', () => {
|
||||
describe('popup', () => {
|
||||
type ActionType = 'browser_action' | 'page_action';
|
||||
type Mv3ActionType = 'action' | 'page_action';
|
||||
const popupEntrypoint = (type?: ActionType) =>
|
||||
fakePopupEntrypoint({
|
||||
options: {
|
||||
@@ -107,29 +108,39 @@ describe('Manifest Utils', () => {
|
||||
},
|
||||
);
|
||||
|
||||
it('should allow converting action to page_action for Firefox MV3', async () => {
|
||||
const popup = popupEntrypoint('page_action');
|
||||
const buildOutput = fakeBuildOutput();
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 3,
|
||||
outDir,
|
||||
browser: 'firefox',
|
||||
},
|
||||
});
|
||||
const expected = {
|
||||
default_icon: popup.options.defaultIcon,
|
||||
default_title: popup.options.defaultTitle,
|
||||
default_popup: 'popup.html',
|
||||
};
|
||||
it.each<{
|
||||
inputType: ActionType | undefined;
|
||||
expectedType: Mv3ActionType;
|
||||
}>([
|
||||
{ inputType: undefined, expectedType: 'action' },
|
||||
{ inputType: 'browser_action', expectedType: 'action' },
|
||||
{ inputType: 'page_action', expectedType: 'page_action' },
|
||||
])(
|
||||
'should use the correct action for Firefox in mv3: %j',
|
||||
async ({ inputType, expectedType }) => {
|
||||
const popup = popupEntrypoint(inputType);
|
||||
const buildOutput = fakeBuildOutput();
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 3,
|
||||
outDir,
|
||||
browser: 'firefox',
|
||||
},
|
||||
});
|
||||
const expected = {
|
||||
default_icon: popup.options.defaultIcon,
|
||||
default_title: popup.options.defaultTitle,
|
||||
default_popup: 'popup.html',
|
||||
};
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[popup],
|
||||
buildOutput,
|
||||
);
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[popup],
|
||||
buildOutput,
|
||||
);
|
||||
|
||||
expect(actual.page_action).toEqual(expected);
|
||||
});
|
||||
expect(actual[expectedType]).toEqual(expected);
|
||||
},
|
||||
);
|
||||
|
||||
it('should include default_area for Firefox in mv3', async () => {
|
||||
const popup = fakePopupEntrypoint({
|
||||
|
||||
@@ -307,8 +307,9 @@ function addEntrypoints(
|
||||
const actionKey =
|
||||
manifest.manifest_version === 2
|
||||
? (popup.options.actionType ?? 'browser_action')
|
||||
: wxt.config.browser === 'firefox'
|
||||
? (popup.options.actionType ?? 'action')
|
||||
: wxt.config.browser === 'firefox' &&
|
||||
popup.options.actionType === 'page_action'
|
||||
? 'page_action'
|
||||
: 'action';
|
||||
|
||||
manifest[actionKey] = {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { InlineConfig, Wxt, WxtCommand, WxtHooks, WxtModule } from '../types';
|
||||
import { resolveConfig } from './resolve-config';
|
||||
import { createHooks } from 'hookable';
|
||||
import { createWxtPackageManager } from './package-managers';
|
||||
import { createViteBuilder } from './builders/vite';
|
||||
import { builtinModules } from '../builtin-modules';
|
||||
import { relative } from 'path';
|
||||
import { builtinModules } from '../builtin-modules';
|
||||
import { InlineConfig, Wxt, WxtCommand, WxtHooks, WxtModule } from '../types';
|
||||
import { createViteBuilder } from './builders/vite';
|
||||
import { createWxtPackageManager } from './package-managers';
|
||||
import { resolveConfig } from './resolve-config';
|
||||
|
||||
/**
|
||||
* Global variable set once `createWxt` is called once. Since this variable is
|
||||
@@ -47,6 +47,7 @@ export async function registerWxt(
|
||||
inlineConfig.dev ??= {};
|
||||
inlineConfig.dev.server ??= {};
|
||||
inlineConfig.dev.server.port = wxt.config.dev.server.port;
|
||||
inlineConfig.dev.server.strictPort = true;
|
||||
}
|
||||
|
||||
wxt.config = await resolveConfig(inlineConfig, command);
|
||||
|
||||
Vendored
+1
-1
@@ -23,6 +23,6 @@
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@wxt-dev/module-react": "^1.1.5",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "^0.20.23"
|
||||
"wxt": "^0.20.25"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -20,6 +20,6 @@
|
||||
"devDependencies": {
|
||||
"@wxt-dev/module-solid": "^1.1.4",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "^0.20.23"
|
||||
"wxt": "^0.20.25"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -21,6 +21,6 @@
|
||||
"svelte-check": "^4.4.4",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "^0.20.23"
|
||||
"wxt": "^0.20.25"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -16,6 +16,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "^0.20.23"
|
||||
"wxt": "^0.20.25"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -21,6 +21,6 @@
|
||||
"@wxt-dev/module-vue": "^1.0.3",
|
||||
"typescript": "^5.9.3",
|
||||
"vue-tsc": "^3.2.5",
|
||||
"wxt": "^0.20.23"
|
||||
"wxt": "^0.20.25"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user