Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1dd1370ed7 | |||
| 9ff90d0acf | |||
| b754435589 | |||
| 195d2cceaf | |||
| 420d78b638 | |||
| b35e499355 | |||
| 41c3d6d015 | |||
| 64610bad41 | |||
| 45b7d7c67f | |||
| 0bb2746869 | |||
| f9b0aa45f8 | |||
| 6f6ce33bbd | |||
| e1c6020997 | |||
| 012bd7e67f | |||
| 35778f70cb | |||
| cb4f9aa752 | |||
| f80fb42483 | |||
| 02c79ca244 | |||
| 50e8c86ce9 |
@@ -1,2 +1,5 @@
|
||||
* text=auto eol=lf
|
||||
pnpm-lock.yaml linguist-generated
|
||||
package-lock.json linguist-generated
|
||||
bun.lockb linguist-generated
|
||||
yarn.lock linguist-generated
|
||||
|
||||
@@ -43,6 +43,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- name: pnpm test:coverage
|
||||
run: pnpm test:coverage --reporter=default --reporter=hanging-process
|
||||
- uses: codecov/codecov-action@v4
|
||||
|
||||
@@ -1,5 +1,59 @@
|
||||
# Changelog
|
||||
|
||||
## v0.17.5
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.4...v0.17.5)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Expose package management utils under `wxt.pm` ([#502](https://github.com/wxt-dev/wxt/pull/502))
|
||||
- Download and override private packages for Firefox code review ([#507](https://github.com/wxt-dev/wxt/pull/507))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Fix typos ([#503](https://github.com/wxt-dev/wxt/pull/503))
|
||||
- Add docs about configuring the manifest as a function ([195d2cc](https://github.com/wxt-dev/wxt/commit/195d2cc))
|
||||
- Fix CLI generation ([b754435](https://github.com/wxt-dev/wxt/commit/b754435))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Use JSZip for `wxt zip`, enabling future features ([#501](https://github.com/wxt-dev/wxt/pull/501))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Btea ([@btea](http://github.com/btea))
|
||||
|
||||
## v0.17.4
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.3...v0.17.4)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add basic content script to templates ([#495](https://github.com/wxt-dev/wxt/pull/495))
|
||||
- Add `ResolvedConfig.wxtModuleDir`, resolving the directory once ([#497](https://github.com/wxt-dev/wxt/pull/497))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Resolve the path to `node_modules/wxt` correctly ([#498](https://github.com/wxt-dev/wxt/pull/498))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Added DocVersionRedirector to "Using WXT" section ([#492](https://github.com/wxt-dev/wxt/pull/492))
|
||||
- Fix typos ([f80fb42](https://github.com/wxt-dev/wxt/commit/f80fb42))
|
||||
- Add CRXJS to comparison page ([cb4f9aa](https://github.com/wxt-dev/wxt/commit/cb4f9aa))
|
||||
- Update comparison page ([35778f7](https://github.com/wxt-dev/wxt/commit/35778f7))
|
||||
- Update context usage ([012bd7e](https://github.com/wxt-dev/wxt/commit/012bd7e))
|
||||
- Add testing example for `ContentScriptContext` ([e1c6020](https://github.com/wxt-dev/wxt/commit/e1c6020))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Fix tests after template change ([f9b0aa4](https://github.com/wxt-dev/wxt/commit/f9b0aa4))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Btea ([@btea](http://github.com/btea))
|
||||
- Leo Shklovskii <leo@thermopylae.net>
|
||||
|
||||
## v0.17.3
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.2...v0.17.3)
|
||||
|
||||
@@ -15,4 +15,7 @@ export default defineConfig({
|
||||
alias: {
|
||||
public: 'src/public',
|
||||
},
|
||||
zip: {
|
||||
downloadPackages: ['sass'],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -18,6 +18,8 @@ const chromeExtensionIds = [
|
||||
'lknmjhcajhfbbglglccadlfdjbaiifig', // tl;dv - Record, Transcribe & ChatGPT for Google Meet
|
||||
'youtube中文配音/oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
|
||||
'agjnjboanicjcpenljmaaigopkgdnihi', // PreMiD
|
||||
'aiakblgmlabokilgljkglggnpflljdgp', // Markdown Sticky Notes
|
||||
'nomnkbngkijpffepcgbbofhcnafpkiep', // DocVersionRedirector
|
||||
];
|
||||
|
||||
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
||||
|
||||
@@ -19,7 +19,7 @@ const title = 'Next-gen Web Extension Framework';
|
||||
const titleSuffix = ' – WXT';
|
||||
|
||||
const description =
|
||||
"WXT provides the best developer experience, making it quick, easy, and fun to develop chrome extensions for all browsers. With built-in utilties for building, zipping, and publishing your extension, it's easy to get started.";
|
||||
"WXT provides the best developer experience, making it quick, easy, and fun to develop chrome extensions for all browsers. With built-in utilities for building, zipping, and publishing your extension, it's easy to get started.";
|
||||
const ogTitle = `${title}${titleSuffix}`;
|
||||
const ogUrl = 'https://wxt.dev';
|
||||
const ogImage = 'https://wxt.dev/social-preview.png';
|
||||
@@ -124,7 +124,7 @@ export default defineConfig({
|
||||
{ text: 'Options', link: '/entrypoints/options.md' },
|
||||
{ text: 'Popup', link: '/entrypoints/popup.md' },
|
||||
{ text: 'Sandbox', link: '/entrypoints/sandbox.md' },
|
||||
{ text: 'Sidepanel', link: '/entrypoints/sidepanel.md' },
|
||||
{ text: 'Side Panel', link: '/entrypoints/sidepanel.md' },
|
||||
{ text: 'Unlisted Pages', link: '/entrypoints/unlisted-pages.md' },
|
||||
{
|
||||
text: 'Unlisted Scripts',
|
||||
|
||||
@@ -32,14 +32,20 @@ export function generateCliDocs() {
|
||||
'```',
|
||||
];
|
||||
|
||||
const commands = await Promise.all(
|
||||
const commands = await Promise.allSettled(
|
||||
extractCommands(dev.info).map(async (command) => {
|
||||
const res = await execaCommand(`pnpm -s wxt ${command} --help`);
|
||||
const { rest: docs } = splitInfo(res.stdout);
|
||||
return [`## \`wxt ${command}\``, '```sh', docs, '```'];
|
||||
}),
|
||||
);
|
||||
lines.push(...commands);
|
||||
lines.push(
|
||||
...commands
|
||||
.filter((res) => res.status === 'fulfilled')
|
||||
.map(
|
||||
(res) => (res as unknown as PromiseFulfilledResult<string>).value,
|
||||
),
|
||||
);
|
||||
|
||||
const text = await format(
|
||||
PREFACE +
|
||||
|
||||
@@ -52,9 +52,11 @@ When defining multiple content scripts, content script entrypoints that have the
|
||||
|
||||
## Context
|
||||
|
||||
Old content scripts are not automatically stopped when an extension updates and reloads. Often, this leads to "Invalidated context" errors in production when a content script from an old version of your extension tries to use a extension API.
|
||||
Old content scripts are not automatically stopped when an extension updates and reloads. Often, this leads to "Invalidated context" errors in production when a content script from an old version of your extension tries to use a web extension API (ie, the `browser` or `chrome` globals).
|
||||
|
||||
WXT provides a utility for managing this process: `ContentScriptContext`. An instance of this class is provided to you automatically inside the `main` function of your content script.
|
||||
WXT provides a utility for handling this process: `ContentScriptContext`. An instance of this class is provided to you automatically inside the `main` function of your content script.
|
||||
|
||||
When your extension updates or is uninstalled, the context will become invalidated, and will trigger any `ctx.onInvalidated` listeners you add:
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
@@ -65,26 +67,33 @@ export default defineContentScript({
|
||||
// ...
|
||||
});
|
||||
|
||||
// Stop fetch requests
|
||||
fetch('...url', { signal: ctx.signal });
|
||||
|
||||
// Timeout utilities
|
||||
// Timeout utilities that are automatically cleared when invalidated
|
||||
ctx.setTimeout(() => {
|
||||
// ...
|
||||
}, 5e3);
|
||||
ctx.setInterval(() => {
|
||||
// ...
|
||||
}, 60e3);
|
||||
|
||||
// Or add event listeners that get removed when invalidated
|
||||
ctx.addEventListener(document, 'visibilitychange', (event) => {
|
||||
// ...
|
||||
});
|
||||
|
||||
// You can also stop fetch requests
|
||||
fetch('...url', { signal: ctx.signal });
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The class extends [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) and provides other utilities for stopping a content script's logic once it becomes invalidated.
|
||||
|
||||
:::tip
|
||||
:::warning
|
||||
When working with content scripts, **you should always use the `ctx` object to stop any async or future work.**
|
||||
|
||||
This prevents old content scripts from interfering with new content scripts, and prevents error messages from the console in production.
|
||||
|
||||
If you're using a framework like React, Vue, Svelte, etc., make sure you're unmounting your UI properly in the `onRemove` option of [`createShadowRootUi`](https://wxt.dev/guide/content-script-ui.html#shadow-root).
|
||||
:::
|
||||
|
||||
## CSS
|
||||
|
||||
+29
-28
@@ -1,39 +1,40 @@
|
||||
# Compare
|
||||
|
||||
Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework), another web extension framework.
|
||||
Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework) (another web extension framework) and [CRXJS](https://crxjs.dev/vite-plugin) (the most popular bundler plugin).
|
||||
|
||||
## Overview
|
||||
|
||||
| Features | WXT | Plasmo |
|
||||
| ---------------------------------------------------- | :-------------------------: | :--------------------------------------: |
|
||||
| Supports all browsers | ✅ | ✅ |
|
||||
| MV2 Support | ✅ | ✅ |
|
||||
| MV3 Support | ✅ | ✅ |
|
||||
| Create Extension ZIPs | ✅ | ✅ |
|
||||
| Create Firefox Sources ZIP | ✅ | ❌ |
|
||||
| First-class TypeScript support | ✅ | ✅ |
|
||||
| File based entrypoint discovery | ✅ | ✅ |
|
||||
| Inline entrypoint config | ✅ | ✅ |
|
||||
| Auto-imports | ✅ | ❌ |
|
||||
| Supports all frontend frameworks | ✅ | 🟡 Only React, Vue, and Svelte |
|
||||
| Framework specific entrypoints (like `Popup.tsx`) | 🟡 `.html` `.ts` `.tsx` | ✅ `.html` `.ts` `.tsx` `.vue` `.svelte` |
|
||||
| Automated publishing | 🟡 Coming soon | ✅ |
|
||||
| Remote Code Bundling (Google Analytics) | ✅ | ✅ |
|
||||
| Features | WXT | Plasmo | CRXJS |
|
||||
| ---------------------------------------------------- | :-------------------------: | :--------------------------------------: | :---------------------------------------------------------------------: |
|
||||
| Supports all browsers | ✅ | ✅ | ❌ See [#56](https://github.com/crxjs/chrome-extension-tools/issues/56) |
|
||||
| MV2 Support | ✅ | ✅ | 🟡 Either MV2 or MV3 |
|
||||
| MV3 Support | ✅ | ✅ | 🟡 Either MV2 or MV3 |
|
||||
| Create Extension ZIPs | ✅ | ✅ | ❌ |
|
||||
| Create Firefox Sources ZIP | ✅ | ❌ | ❌ |
|
||||
| First-class TypeScript support | ✅ | ✅ | ✅ |
|
||||
| Entrypoint discovery | File based | File based | ❌ |
|
||||
| Inline entrypoint config | ✅ | ✅ | Manifest based |
|
||||
| Auto-imports | ✅ | ❌ | ❌ |
|
||||
| Supports all frontend frameworks | ✅ | 🟡 Only React, Vue, and Svelte | ✅ |
|
||||
| Framework specific entrypoints (like `Popup.tsx`) | 🟡 `.html` `.ts` `.tsx` | ✅ `.html` `.ts` `.tsx` `.vue` `.svelte` | ❌ |
|
||||
| Automated publishing | ✅ | ✅ | ❌ |
|
||||
| Remote Code Bundling (Google Analytics) | ✅ | ✅ | ❌ |
|
||||
| <strong style="opacity: 50%">Dev Mode</strong> | | |
|
||||
| `.env` Files | ✅ | ✅ |
|
||||
| Opens browser and install extension | ✅ | ❌ |
|
||||
| HMR for UIs | ✅ | 🟡 React only |
|
||||
| Reload HTML Files on Change | ✅ | 🟡 Reloads entire extension |
|
||||
| Reload Content Scripts on Change | ✅ | 🟡 Reloads entire extension |
|
||||
| Reload Background on Change | 🟡 Reloads entire extension | 🟡 Reloads entire extension |
|
||||
| <strong style="opacity: 50%">Built-in Utils</strong> | | |
|
||||
| Storage | ✅ | ✅ |
|
||||
| Messaging | 🟡 Coming soon | ✅ |
|
||||
| Content Script UI | ✅ | ✅ |
|
||||
| `.env` Files | ✅ | ✅ | ✅ |
|
||||
| Opens browser and install extension | ✅ | ❌ | ❌ |
|
||||
| HMR for UIs | ✅ | 🟡 React only | ✅ |
|
||||
| Reload HTML Files on Change | ✅ | 🟡 Reloads entire extension | ✅ |
|
||||
| Reload Content Scripts on Change | ✅ | 🟡 Reloads entire extension | ✅ |
|
||||
| Reload Background on Change | 🟡 Reloads entire extension | 🟡 Reloads entire extension | 🟡 Reloads entire extension |
|
||||
| Respects Content Script `run_at` | ✅ | ✅ | ❌ ESM-style loaders run asynchronously |
|
||||
| <strong style="opacity: 50%">Built-in Utils</strong> | | | |
|
||||
| Storage | ✅ | ✅ | ❌ |
|
||||
| Messaging | ❌ | ✅ | ❌ |
|
||||
| Content Script UI | ✅ | ✅ | ❌ |
|
||||
|
||||
## Dev Mode
|
||||
## Why use WXT?
|
||||
|
||||
WXT's main goal is improving the development experience (DX) of creating web extensions. There are two things WXT does differently:
|
||||
WXT's main goal is improving the development experience (DX) of creating web extensions, while not sacrificing support. There are two things WXT does differently:
|
||||
|
||||
1. Automatically opens a browser with the extension installed when starting development
|
||||
2. Reload each part of the extension individually rather than reloading the entire extension
|
||||
|
||||
@@ -114,7 +114,7 @@ WXT 0.14.1
|
||||
[8:55:54 AM] ERROR entrypoints/content.ts: Cannot use imported variable "GOOGLE_MATCHES" before main function. See https://wxt.dev/guide/entrypoints.html#side-effects
|
||||
```
|
||||
|
||||
This throws an error because WXT needs to import each entrypoint during the build process to extract its definition (containing the `match`, `run_at`, `include`/`exclude`, etc.) to render the `manifest.json` correctly. Before loading an entrypoint, a transformation is applied to remove all imports. This prevents imported modules (local or NPM) with side-effects from running during the build process, potentially throwing an error.
|
||||
This throws an error because WXT needs to import each entrypoint during the build process to extract its definition (containing the `match`, `runAt`, `include`/`exclude`, etc.) to render the `manifest.json` correctly. Before loading an entrypoint, a transformation is applied to remove all imports. This prevents imported modules (local or NPM) with side-effects from running during the build process, potentially throwing an error.
|
||||
|
||||
:::details Why?
|
||||
|
||||
|
||||
+49
-5
@@ -1,3 +1,7 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Manifest.json
|
||||
|
||||
The manifest.json is generated at build-time based on files in the `entrypoints/` directory and `wxt.config.ts`.
|
||||
@@ -20,11 +24,11 @@ export default defineConfig({
|
||||
});
|
||||
```
|
||||
|
||||
## `name`
|
||||
### `name`
|
||||
|
||||
If not provided via the `manifest` config, the [manifest's `name`](https://developer.chrome.com/docs/extensions/mv3/manifest/name/) defaults to your package.json's `name` property.
|
||||
|
||||
## `version` and `version_name`
|
||||
### `version` and `version_name`
|
||||
|
||||
The [manifest's `version` and `version_name`](https://developer.chrome.com/docs/extensions/mv3/manifest/version/) properties are based on the `version` field listed in your `package.json` or `wxt.config.ts`.
|
||||
|
||||
@@ -33,7 +37,7 @@ The [manifest's `version` and `version_name`](https://developer.chrome.com/docs/
|
||||
|
||||
If a version is not found, a warning is logged and the version defaults to `"0.0.0"`.
|
||||
|
||||
### Example
|
||||
#### Example
|
||||
|
||||
```json
|
||||
// package.json
|
||||
@@ -50,7 +54,7 @@ If a version is not found, a warning is logged and the version defaults to `"0.0
|
||||
}
|
||||
```
|
||||
|
||||
## `icons`
|
||||
### `icons`
|
||||
|
||||
By default, WXT will discover icons in your [`public` directory](/guide/assets#public-directory) and use them for the [manifest's `icons`](https://developer.chrome.com/docs/extensions/mv3/manifest/icons/).
|
||||
|
||||
@@ -83,7 +87,7 @@ export default defineConfig({
|
||||
});
|
||||
```
|
||||
|
||||
## `permissions`
|
||||
### `permissions`
|
||||
|
||||
[Permissions](https://developer.chrome.com/docs/extensions/reference/permissions/) must be listed in the manifest config.
|
||||
|
||||
@@ -125,3 +129,43 @@ export default defineConfig({
|
||||
See the official localization examples for more details:
|
||||
|
||||
<ExampleList tag="i18n" />
|
||||
|
||||
## Per-Browser Configuration
|
||||
|
||||
The `manifest` field can be a function. If you are building and extension for multiple browsers, and need to modify the manifest per browser, using a function instead of an object is very useful.
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: ({ browser, manifestVersion, mode, command }) => {
|
||||
return {
|
||||
// Your manifest
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::info
|
||||
The first argument is of type `ConfigEnv`. See the [API reference](/api/wxt/interfaces/ConfigEnv) for info about each property.
|
||||
:::
|
||||
|
||||
For example, say you use OAuth, and you need to provide a different `oauth.client_id` for each browser:
|
||||
|
||||
```ts
|
||||
const clientIds = {
|
||||
chrome: '<your-chrome-client-id>',
|
||||
edge: '<your-edge-client-id>',
|
||||
firefox: '<your-firefox-client-id>',
|
||||
opera: '<your-opera-client-id>',
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
manifest: ({ browser }) => ({
|
||||
oauth: {
|
||||
client_id: clientIds[browser],
|
||||
scopes: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
@@ -136,6 +136,28 @@ See Issue [#377](https://github.com/wxt-dev/wxt/issues/377) for more details.
|
||||
|
||||
Ensure that you have a `README.md` or `SOURCE_CODE_REVIEW.md` file with the above commands so that the Firefox team knows how to build your extension.
|
||||
|
||||
#### Private Packages
|
||||
|
||||
If you use private packages and you don't want to provide your auth token to the Firefox team during the review process, you can use `zip.downloadPackages` to download any private packages and include them in the zip.
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
zip: {
|
||||
downloadPackages: [
|
||||
'@mycompany/some-package',
|
||||
//...
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Depending on your package manager, the `package.json` in the sources zip will be modified to use the downloaded dependencies via the `overrides` or `resolutions` field.
|
||||
|
||||
:::warning
|
||||
WXT uses the command `npm pack <package-name>` to download the package. That means regardless of your package manager, you need to properly setup a `.npmrc` file. NPM and PNPM both respect `.npmrc` files, but Yarn and Bun have their own ways of authorizing private registries, so you'll need to add an `.npmrc` file.
|
||||
:::
|
||||
|
||||
### Safari
|
||||
|
||||
> 🚧 Not supported yet
|
||||
|
||||
@@ -25,6 +25,7 @@ describe('Init command', () => {
|
||||
"assets/vue.svg",
|
||||
"components/HelloWorld.vue",
|
||||
"entrypoints/background.ts",
|
||||
"entrypoints/content.ts",
|
||||
"entrypoints/popup/App.vue",
|
||||
"entrypoints/popup/index.html",
|
||||
"entrypoints/popup/main.ts",
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
import extract from 'extract-zip';
|
||||
import { execaCommand } from 'execa';
|
||||
import { readFile, writeFile } from 'fs-extra';
|
||||
|
||||
process.env.WXT_PNPM_IGNORE_WORKSPACE = 'true';
|
||||
|
||||
describe('Zipping', () => {
|
||||
it('should download packages and produce a valid build when zipping sources', async () => {
|
||||
const project = new TestProject({
|
||||
name: 'test',
|
||||
version: '1.0.0',
|
||||
dependencies: {
|
||||
flatten: '1.0.3',
|
||||
},
|
||||
});
|
||||
project.addFile(
|
||||
'entrypoints/background.ts',
|
||||
'export default defineBackground(() => {});',
|
||||
);
|
||||
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: { downloadPackages: ['flatten'] },
|
||||
});
|
||||
expect(await project.fileExists('.output/')).toBe(true);
|
||||
|
||||
await extract(sourcesZip, { dir: unzipDir });
|
||||
// Update package json wxt path
|
||||
const packageJsonPath = project.resolvePath(unzipDir, 'package.json');
|
||||
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8'));
|
||||
packageJson.dependencies.wxt = '../../../../..';
|
||||
await writeFile(
|
||||
packageJsonPath,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
// Build zipped extension
|
||||
await expect(
|
||||
execaCommand('pnpm i --ignore-workspace --frozen-lockfile false', {
|
||||
cwd: unzipDir,
|
||||
}),
|
||||
).resolves.toMatchObject({ exitCode: 0 });
|
||||
await expect(
|
||||
execaCommand('pnpm wxt build -b firefox', { cwd: unzipDir }),
|
||||
).resolves.toMatchObject({ exitCode: 0 });
|
||||
|
||||
await expect(project.fileExists(unzipDir, '.output')).resolves.toBe(true);
|
||||
expect(
|
||||
await project.serializeFile(
|
||||
project.resolvePath(unzipDir, 'package.json'),
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
".output/test-1.0.0-sources/package.json
|
||||
----------------------------------------
|
||||
{
|
||||
"name": "test",
|
||||
"description": "Example description",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"wxt": "../../../../..",
|
||||
"flatten": "1.0.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"flatten@1.0.3": "file://./.wxt/local_modules/flatten-1.0.3.tgz"
|
||||
}
|
||||
}"
|
||||
`);
|
||||
});
|
||||
});
|
||||
+2
-2
@@ -175,8 +175,8 @@ export class TestProject {
|
||||
].join(`\n${''.padEnd(40, '-')}\n`);
|
||||
}
|
||||
|
||||
fileExists(path: string): Promise<boolean> {
|
||||
return fs.exists(this.resolvePath(path));
|
||||
fileExists(...path: string[]): Promise<boolean> {
|
||||
return fs.exists(this.resolvePath(...path));
|
||||
}
|
||||
|
||||
async getOutputManifest(
|
||||
|
||||
+5
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wxt",
|
||||
"type": "module",
|
||||
"version": "0.17.3",
|
||||
"version": "0.17.5",
|
||||
"description": "Next gen framework for developing web extensions",
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
@@ -128,10 +128,12 @@
|
||||
"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",
|
||||
"normalize-path": "^3.0.0",
|
||||
"nypm": "^0.3.6",
|
||||
"ora": "^7.0.1",
|
||||
"picocolors": "^1.0.0",
|
||||
"prompts": "^2.4.2",
|
||||
@@ -139,8 +141,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",
|
||||
@@ -152,6 +153,7 @@
|
||||
"@types/prompts": "^2.4.9",
|
||||
"@vitest/coverage-v8": "^1.0.1",
|
||||
"execa": "^8.0.1",
|
||||
"extract-zip": "^2.0.1",
|
||||
"happy-dom": "^13.3.8",
|
||||
"lint-staged": "^15.2.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
|
||||
Generated
+35
-18
@@ -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
|
||||
@@ -86,6 +89,9 @@ importers:
|
||||
normalize-path:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
nypm:
|
||||
specifier: ^0.3.6
|
||||
version: 0.3.6
|
||||
ora:
|
||||
specifier: ^7.0.1
|
||||
version: 7.0.1
|
||||
@@ -110,9 +116,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
|
||||
@@ -141,6 +144,9 @@ importers:
|
||||
execa:
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.1
|
||||
extract-zip:
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1
|
||||
happy-dom:
|
||||
specifier: ^13.3.8
|
||||
version: 13.3.8
|
||||
@@ -1177,7 +1183,6 @@ packages:
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@types/node': 20.10.3
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@vitejs/plugin-vue@5.0.2(vite@5.1.3)(vue@3.4.3):
|
||||
@@ -1712,7 +1717,6 @@ packages:
|
||||
|
||||
/buffer-crc32@0.2.13:
|
||||
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
||||
dev: false
|
||||
|
||||
/buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
@@ -1879,6 +1883,12 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/citty@0.1.6:
|
||||
resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==}
|
||||
dependencies:
|
||||
consola: 3.2.3
|
||||
dev: false
|
||||
|
||||
/cli-boxes@3.0.0:
|
||||
resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -2260,7 +2270,6 @@ packages:
|
||||
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
dev: false
|
||||
|
||||
/entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
@@ -2482,7 +2491,6 @@ packages:
|
||||
'@types/yauzl': 2.10.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/fast-glob@3.3.2:
|
||||
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
|
||||
@@ -2503,7 +2511,6 @@ packages:
|
||||
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
|
||||
dependencies:
|
||||
pend: 1.2.0
|
||||
dev: false
|
||||
|
||||
/filesize@10.1.0:
|
||||
resolution: {integrity: sha512-GTLKYyBSDz3nPhlLVPjPWZCnhkd9TrrRArNcy8Z+J2cqScB7h2McAzR6NBX6nYOoWafql0roY8hrocxnZBv9CQ==}
|
||||
@@ -2660,7 +2667,6 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
pump: 3.0.0
|
||||
dev: false
|
||||
|
||||
/get-stream@6.0.1:
|
||||
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
|
||||
@@ -3673,9 +3679,9 @@ packages:
|
||||
resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==}
|
||||
dependencies:
|
||||
acorn: 8.11.2
|
||||
pathe: 1.1.1
|
||||
pathe: 1.1.2
|
||||
pkg-types: 1.0.3
|
||||
ufo: 1.3.1
|
||||
ufo: 1.4.0
|
||||
|
||||
/moment@2.29.4:
|
||||
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
|
||||
@@ -3848,6 +3854,17 @@ packages:
|
||||
boolbase: 1.0.0
|
||||
dev: false
|
||||
|
||||
/nypm@0.3.6:
|
||||
resolution: {integrity: sha512-2CATJh3pd6CyNfU5VZM7qSwFu0ieyabkEdnogE30Obn1czrmOYiZ8DOZLe1yBdLKWoyD3Mcy2maUs+0MR3yVjQ==}
|
||||
engines: {node: ^14.16.0 || >=16.10.0}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
citty: 0.1.6
|
||||
execa: 8.0.1
|
||||
pathe: 1.1.2
|
||||
ufo: 1.4.0
|
||||
dev: false
|
||||
|
||||
/object-assign@4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -3876,7 +3893,7 @@ packages:
|
||||
dependencies:
|
||||
destr: 2.0.2
|
||||
node-fetch-native: 1.4.1
|
||||
ufo: 1.3.1
|
||||
ufo: 1.4.0
|
||||
dev: false
|
||||
|
||||
/ohash@1.1.3:
|
||||
@@ -4053,12 +4070,14 @@ packages:
|
||||
/pathe@1.1.1:
|
||||
resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==}
|
||||
|
||||
/pathe@1.1.2:
|
||||
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
|
||||
|
||||
/pathval@1.1.1:
|
||||
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
|
||||
|
||||
/pend@1.2.0:
|
||||
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
|
||||
dev: false
|
||||
|
||||
/perfect-debounce@1.0.0:
|
||||
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
|
||||
@@ -4098,7 +4117,7 @@ packages:
|
||||
dependencies:
|
||||
jsonc-parser: 3.2.0
|
||||
mlly: 1.4.2
|
||||
pathe: 1.1.1
|
||||
pathe: 1.1.2
|
||||
|
||||
/postcss-load-config@4.0.1:
|
||||
resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==}
|
||||
@@ -4213,7 +4232,6 @@ packages:
|
||||
dependencies:
|
||||
end-of-stream: 1.4.4
|
||||
once: 1.4.0
|
||||
dev: false
|
||||
|
||||
/punycode@2.3.0:
|
||||
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
|
||||
@@ -5072,8 +5090,8 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/ufo@1.3.1:
|
||||
resolution: {integrity: sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==}
|
||||
/ufo@1.4.0:
|
||||
resolution: {integrity: sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==}
|
||||
|
||||
/uhyphen@0.2.0:
|
||||
resolution: {integrity: sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==}
|
||||
@@ -5691,7 +5709,6 @@ packages:
|
||||
dependencies:
|
||||
buffer-crc32: 0.2.13
|
||||
fd-slicer: 1.1.0
|
||||
dev: false
|
||||
|
||||
/yocto-queue@1.0.0:
|
||||
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
|
||||
|
||||
@@ -10,6 +10,29 @@ import { createLocationWatcher } from './location-watcher';
|
||||
*
|
||||
* It also provides several utilities like `ctx.setTimeout` and `ctx.setInterval` that should be used in
|
||||
* content scripts instead of `window.setTimeout` or `window.setInterval`.
|
||||
*
|
||||
* To create context for testing, you can use the class's constructor:
|
||||
*
|
||||
* ```ts
|
||||
* import { ContentScriptContext } from 'wxt/client';
|
||||
*
|
||||
* test("storage listener should be removed when context is invalidated", () => {
|
||||
* const ctx = new ContentScriptContext('test');
|
||||
* const item = storage.defineItem("local:count", { defaultValue: 0 });
|
||||
* const watcher = vi.fn();
|
||||
*
|
||||
* const unwatch = item.watch(watcher);
|
||||
* ctx.onInvalidated(unwatch); // Listen for invalidate here
|
||||
*
|
||||
* await item.setValue(1);
|
||||
* expect(watcher).toBeCalledTimes(1);
|
||||
* expect(watcher).toBeCalledWith(1, 0);
|
||||
*
|
||||
* ctx.notifyInvalidated(); // Use this function to invalidate the context
|
||||
* await item.setValue(2);
|
||||
* expect(watcher).toBeCalledTimes(1);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export class ContentScriptContext implements AbortController {
|
||||
private static SCRIPT_STARTED_MESSAGE_TYPE = 'wxt:content-script-started';
|
||||
|
||||
@@ -16,8 +16,8 @@ export function devHtmlPrerender(
|
||||
): vite.PluginOption {
|
||||
const htmlReloadId = '@wxt/reload-html';
|
||||
const resolvedHtmlReloadId = resolve(
|
||||
config.root,
|
||||
'node_modules/wxt/dist/virtual/reload-html.js',
|
||||
config.wxtModuleDir,
|
||||
'dist/virtual/reload-html.js',
|
||||
);
|
||||
const virtualReactRefreshId = '@wxt/virtual-react-refresh';
|
||||
const resolvedVirtualReactRefreshId = '\0' + virtualReactRefreshId;
|
||||
|
||||
@@ -30,10 +30,7 @@ export function virtualEntrypoint(
|
||||
|
||||
const inputPath = id.replace(resolvedVirtualId, '');
|
||||
const template = await fs.readFile(
|
||||
resolve(
|
||||
config.root,
|
||||
`node_modules/wxt/dist/virtual/${type}-entrypoint.js`,
|
||||
),
|
||||
resolve(config.wxtModuleDir, `dist/virtual/${type}-entrypoint.js`),
|
||||
'utf-8',
|
||||
);
|
||||
return template.replace(`virtual:user-${type}`, inputPath);
|
||||
|
||||
@@ -26,8 +26,8 @@ export function webextensionPolyfillMock(
|
||||
alias: {
|
||||
// Alias to use a mocked version of the polyfill
|
||||
'webextension-polyfill': path.resolve(
|
||||
config.root,
|
||||
'node_modules/wxt/dist/virtual/mock-browser',
|
||||
config.wxtModuleDir,
|
||||
'dist/virtual/mock-browser',
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import path from 'node:path';
|
||||
import { bun } from '../bun';
|
||||
|
||||
const cwd = path.resolve(__dirname, 'fixtures/bun-project');
|
||||
|
||||
describe.skipIf(() => process.platform === 'win32')(
|
||||
'Bun Package Management Utils',
|
||||
() => {
|
||||
describe('listDependencies', () => {
|
||||
it('should list direct dependencies', async () => {
|
||||
const actual = await bun.listDependencies({ cwd });
|
||||
expect(actual).toEqual([
|
||||
{ name: 'flatten', version: '1.0.3' },
|
||||
{ name: 'mime-types', version: '2.1.35' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should list all dependencies', async () => {
|
||||
const actual = await bun.listDependencies({ cwd, all: true });
|
||||
expect(actual).toEqual([
|
||||
{ name: 'flatten', version: '1.0.3' },
|
||||
{ name: 'mime-db', version: '1.52.0' },
|
||||
{ name: 'mime-types', version: '2.1.35' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "bun-ls",
|
||||
"dependencies": {
|
||||
"mime-types": "2.1.35"
|
||||
},
|
||||
"devDependencies": {
|
||||
"flatten": "1.0.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "npm-ls",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "npm-ls",
|
||||
"dependencies": {
|
||||
"mime-types": "2.1.35"
|
||||
},
|
||||
"devDependencies": {
|
||||
"flatten": "1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/flatten": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz",
|
||||
"integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==",
|
||||
"deprecated": "flatten is deprecated in favor of utility frameworks such as lodash.",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "npm-ls",
|
||||
"dependencies": {
|
||||
"mime-types": "2.1.35"
|
||||
},
|
||||
"devDependencies": {
|
||||
"flatten": "1.0.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "pnpm-ls",
|
||||
"dependencies": {
|
||||
"mime-types": "2.1.35"
|
||||
},
|
||||
"devDependencies": {
|
||||
"flatten": "1.0.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
lockfileVersion: '6.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
dependencies:
|
||||
mime-types:
|
||||
specifier: 2.1.35
|
||||
version: 2.1.35
|
||||
|
||||
devDependencies:
|
||||
flatten:
|
||||
specifier: 1.0.3
|
||||
version: 1.0.3
|
||||
|
||||
packages:
|
||||
|
||||
/flatten@1.0.3:
|
||||
resolution: {integrity: sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==}
|
||||
deprecated: flatten is deprecated in favor of utility frameworks such as lodash.
|
||||
dev: true
|
||||
|
||||
/mime-db@1.52.0:
|
||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/mime-types@2.1.35:
|
||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
dev: false
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "yarn-ls",
|
||||
"packageManager": "yarn@1.22.19",
|
||||
"dependencies": {
|
||||
"mime-types": "2.1.35"
|
||||
},
|
||||
"devDependencies": {
|
||||
"flatten": "1.0.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
flatten@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
|
||||
integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
|
||||
|
||||
mime-db@1.52.0:
|
||||
version "1.52.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||
|
||||
mime-types@2.1.35:
|
||||
version "2.1.35"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||
dependencies:
|
||||
mime-db "1.52.0"
|
||||
@@ -0,0 +1,46 @@
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
import path from 'node:path';
|
||||
import { npm } from '../npm';
|
||||
import { execaCommand } from 'execa';
|
||||
import { exists } from 'fs-extra';
|
||||
|
||||
const cwd = path.resolve(__dirname, 'fixtures/npm-project');
|
||||
|
||||
describe('NPM Package Management Utils', () => {
|
||||
beforeAll(async () => {
|
||||
// NPM needs the modules installed for 'npm ls' to work
|
||||
await execaCommand('npm i', { cwd });
|
||||
});
|
||||
|
||||
describe('listDependencies', () => {
|
||||
it('should list direct dependencies', async () => {
|
||||
const actual = await npm.listDependencies({ cwd });
|
||||
expect(actual).toEqual([
|
||||
{ name: 'flatten', version: '1.0.3' },
|
||||
{ name: 'mime-types', version: '2.1.35' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should list all dependencies', async () => {
|
||||
const actual = await npm.listDependencies({ cwd, all: true });
|
||||
expect(actual).toEqual([
|
||||
{ name: 'flatten', version: '1.0.3' },
|
||||
{ name: 'mime-types', version: '2.1.35' },
|
||||
{ name: 'mime-db', version: '1.52.0' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('downloadDependency', () => {
|
||||
it('should download the dependency as a tarball', async () => {
|
||||
const downloadDir = path.resolve(cwd, 'dist');
|
||||
const id = 'mime-db@1.52.0';
|
||||
const expected = path.resolve(downloadDir, 'mime-db-1.52.0.tgz');
|
||||
|
||||
const actual = await npm.downloadDependency(id, downloadDir);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
expect(await exists(actual)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
import path from 'node:path';
|
||||
import { pnpm } from '../pnpm';
|
||||
import { execaCommand } from 'execa';
|
||||
|
||||
const cwd = path.resolve(__dirname, 'fixtures/pnpm-project');
|
||||
|
||||
process.env.WXT_PNPM_IGNORE_WORKSPACE = 'true';
|
||||
|
||||
describe('PNPM Package Management Utils', () => {
|
||||
beforeAll(async () => {
|
||||
// PNPM needs the modules installed, or 'pnpm ls' will return a blank list.
|
||||
await execaCommand('pnpm i --ignore-workspace', { cwd });
|
||||
});
|
||||
|
||||
describe('listDependencies', () => {
|
||||
it('should list direct dependencies', async () => {
|
||||
const actual = await pnpm.listDependencies({ cwd });
|
||||
expect(actual).toEqual([
|
||||
{ name: 'flatten', version: '1.0.3' },
|
||||
{ name: 'mime-types', version: '2.1.35' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should list all dependencies', async () => {
|
||||
const actual = await pnpm.listDependencies({ cwd, all: true });
|
||||
expect(actual).toEqual([
|
||||
{ name: 'flatten', version: '1.0.3' },
|
||||
{ name: 'mime-types', version: '2.1.35' },
|
||||
{ name: 'mime-db', version: '1.52.0' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import path from 'node:path';
|
||||
import { yarn } from '../yarn';
|
||||
|
||||
const cwd = path.resolve(__dirname, 'fixtures/yarn-project');
|
||||
|
||||
describe('Yarn Package Management Utils', () => {
|
||||
describe('listDependencies', () => {
|
||||
it('should list direct dependencies', async () => {
|
||||
const actual = await yarn.listDependencies({ cwd });
|
||||
expect(actual).toEqual([
|
||||
{ name: 'mime-db', version: '1.52.0' },
|
||||
{ name: 'flatten', version: '1.0.3' },
|
||||
{ name: 'mime-types', version: '2.1.35' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should list all dependencies', async () => {
|
||||
const actual = await yarn.listDependencies({ cwd, all: true });
|
||||
expect(actual).toEqual([
|
||||
{ name: 'mime-db', version: '1.52.0' },
|
||||
{ name: 'flatten', version: '1.0.3' },
|
||||
{ name: 'mime-types', version: '2.1.35' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { dedupeDependencies, npm } from './npm';
|
||||
import { WxtPackageManagerImpl } from './types';
|
||||
|
||||
export const bun: WxtPackageManagerImpl = {
|
||||
overridesKey: 'overrides', // But also supports "resolutions"
|
||||
downloadDependency(...args) {
|
||||
return npm.downloadDependency(...args);
|
||||
},
|
||||
async listDependencies(options) {
|
||||
const args = ['pm', 'ls'];
|
||||
if (options?.all) {
|
||||
args.push('--all');
|
||||
}
|
||||
const { execa } = await import('execa');
|
||||
const res = await execa('bun', args, { cwd: options?.cwd });
|
||||
return dedupeDependencies(
|
||||
res.stdout
|
||||
.split('\n')
|
||||
.slice(1) // Skip the first line, is not a dependency
|
||||
.map((line) => line.trim())
|
||||
.map((line) => /.* (@?\S+)@(\S+)$/.exec(line))
|
||||
// @ts-expect-error: Filtering to known non-null matches
|
||||
.filter<RegExpExecArray>((match) => !!match)
|
||||
.map(([_, name, version]) => ({ name, version })),
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
import {
|
||||
detectPackageManager,
|
||||
addDependency,
|
||||
addDevDependency,
|
||||
ensureDependencyInstalled,
|
||||
installDependencies,
|
||||
removeDependency,
|
||||
PackageManager,
|
||||
PackageManagerName,
|
||||
} from 'nypm';
|
||||
import { WxtPackageManager } from '~/types';
|
||||
import { bun } from './bun';
|
||||
import { WxtPackageManagerImpl } from './types';
|
||||
import { yarn } from './yarn';
|
||||
import { pnpm } from './pnpm';
|
||||
import { npm } from './npm';
|
||||
|
||||
export async function createWxtPackageManager(
|
||||
root: string,
|
||||
): Promise<WxtPackageManager> {
|
||||
const pm = await detectPackageManager(root, {
|
||||
includeParentDirs: true,
|
||||
});
|
||||
|
||||
// Use requirePm to prevent throwing errors before the package manager utils are used.
|
||||
const requirePm = <T>(cb: (pm: PackageManager) => T) => {
|
||||
if (pm == null) throw Error('Could not detect package manager');
|
||||
return cb(pm);
|
||||
};
|
||||
|
||||
return {
|
||||
get name() {
|
||||
return requirePm((pm) => pm.name);
|
||||
},
|
||||
get command() {
|
||||
return requirePm((pm) => pm.command);
|
||||
},
|
||||
get version() {
|
||||
return requirePm((pm) => pm.version);
|
||||
},
|
||||
get majorVersion() {
|
||||
return requirePm((pm) => pm.majorVersion);
|
||||
},
|
||||
get lockFile() {
|
||||
return requirePm((pm) => pm.lockFile);
|
||||
},
|
||||
get files() {
|
||||
return requirePm((pm) => pm.files);
|
||||
},
|
||||
addDependency,
|
||||
addDevDependency,
|
||||
ensureDependencyInstalled,
|
||||
installDependencies,
|
||||
removeDependency,
|
||||
get overridesKey() {
|
||||
return requirePm((pm) => packageManagers[pm.name].overridesKey);
|
||||
},
|
||||
downloadDependency(...args) {
|
||||
return requirePm((pm) =>
|
||||
packageManagers[pm.name].downloadDependency(...args),
|
||||
);
|
||||
},
|
||||
listDependencies(...args) {
|
||||
return requirePm((pm) =>
|
||||
packageManagers[pm.name].listDependencies(...args),
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const packageManagers: Record<PackageManagerName, WxtPackageManagerImpl> = {
|
||||
npm,
|
||||
pnpm,
|
||||
bun,
|
||||
yarn,
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Dependency } from '~/types';
|
||||
import { WxtPackageManagerImpl } from './types';
|
||||
import path from 'node:path';
|
||||
import { ensureDir } from 'fs-extra';
|
||||
|
||||
export const npm: WxtPackageManagerImpl = {
|
||||
overridesKey: 'overrides',
|
||||
async downloadDependency(id, downloadDir) {
|
||||
await ensureDir(downloadDir);
|
||||
const { execa } = await import('execa');
|
||||
const res = await execa('npm', ['pack', id, '--json'], {
|
||||
cwd: downloadDir,
|
||||
});
|
||||
const packed: PackedDependency[] = JSON.parse(res.stdout);
|
||||
return path.resolve(downloadDir, packed[0].filename);
|
||||
},
|
||||
async listDependencies(options) {
|
||||
const args = ['ls', '--json'];
|
||||
if (options?.all) {
|
||||
args.push('--depth', 'Infinity');
|
||||
}
|
||||
const { execa } = await import('execa');
|
||||
const res = await execa('npm', args, { cwd: options?.cwd });
|
||||
const project: NpmListProject = JSON.parse(res.stdout);
|
||||
|
||||
return flattenNpmListOutput([project]);
|
||||
},
|
||||
};
|
||||
|
||||
export function flattenNpmListOutput(projects: NpmListProject[]): Dependency[] {
|
||||
const queue: Record<string, NpmListDependency>[] = projects.flatMap(
|
||||
(project) => {
|
||||
const acc: Record<string, NpmListDependency>[] = [];
|
||||
if (project.dependencies) acc.push(project.dependencies);
|
||||
if (project.devDependencies) acc.push(project.devDependencies);
|
||||
return acc;
|
||||
},
|
||||
);
|
||||
const dependencies: Dependency[] = [];
|
||||
while (queue.length > 0) {
|
||||
Object.entries(queue.pop()!).forEach(([name, meta]) => {
|
||||
dependencies.push({
|
||||
name,
|
||||
version: meta.version,
|
||||
});
|
||||
if (meta.dependencies) queue.push(meta.dependencies);
|
||||
if (meta.devDependencies) queue.push(meta.devDependencies);
|
||||
});
|
||||
}
|
||||
return dedupeDependencies(dependencies);
|
||||
}
|
||||
|
||||
export function dedupeDependencies(dependencies: Dependency[]): Dependency[] {
|
||||
const hashes = new Set<string>();
|
||||
return dependencies.filter((dep) => {
|
||||
const hash = `${dep.name}@${dep.version}`;
|
||||
if (hashes.has(hash)) {
|
||||
return false;
|
||||
} else {
|
||||
hashes.add(hash);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export interface NpmListProject {
|
||||
name: string;
|
||||
dependencies?: Record<string, NpmListDependency>;
|
||||
devDependencies?: Record<string, NpmListDependency>;
|
||||
}
|
||||
|
||||
export interface NpmListDependency {
|
||||
version: string;
|
||||
resolved?: string;
|
||||
overridden?: boolean;
|
||||
dependencies?: Record<string, NpmListDependency>;
|
||||
devDependencies?: Record<string, NpmListDependency>;
|
||||
}
|
||||
|
||||
interface PackedDependency {
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
filename: string;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { NpmListProject, flattenNpmListOutput, npm } from './npm';
|
||||
import { WxtPackageManagerImpl } from './types';
|
||||
|
||||
export const pnpm: WxtPackageManagerImpl = {
|
||||
overridesKey: 'resolutions', // "pnpm.overrides" has a higher priority, but I don't want to deal with nesting
|
||||
downloadDependency(...args) {
|
||||
return npm.downloadDependency(...args);
|
||||
},
|
||||
async listDependencies(options) {
|
||||
const args = ['ls', '--json'];
|
||||
if (options?.all) {
|
||||
args.push('--depth', 'Infinity');
|
||||
}
|
||||
// Helper for testing - since WXT uses pnpm workspaces, folders inside it don't behave like
|
||||
// standalone projects unless you pass the --ignore-workspace flag.
|
||||
if (
|
||||
typeof process !== 'undefined' &&
|
||||
process.env.WXT_PNPM_IGNORE_WORKSPACE === 'true'
|
||||
) {
|
||||
args.push('--ignore-workspace');
|
||||
}
|
||||
const { execa } = await import('execa');
|
||||
const res = await execa('pnpm', args, { cwd: options?.cwd });
|
||||
const projects: NpmListProject[] = JSON.parse(res.stdout);
|
||||
|
||||
return flattenNpmListOutput(projects);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
import { WxtPackageManager } from '~/types';
|
||||
|
||||
export type WxtPackageManagerImpl = Pick<
|
||||
WxtPackageManager,
|
||||
'downloadDependency' | 'listDependencies' | 'overridesKey'
|
||||
>;
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Dependency } from '~/types';
|
||||
import { WxtPackageManagerImpl } from './types';
|
||||
import { dedupeDependencies, npm } from './npm';
|
||||
|
||||
export const yarn: WxtPackageManagerImpl = {
|
||||
overridesKey: 'resolutions',
|
||||
downloadDependency(...args) {
|
||||
return npm.downloadDependency(...args);
|
||||
},
|
||||
async listDependencies(options) {
|
||||
const args = ['list', '--json'];
|
||||
if (options?.all) {
|
||||
args.push('--depth', 'Infinity');
|
||||
}
|
||||
const { execa } = await import('execa');
|
||||
const res = await execa('yarn', args, { cwd: options?.cwd });
|
||||
const tree = res.stdout
|
||||
.split('\n')
|
||||
.map<JsonLine>((line) => JSON.parse(line))
|
||||
.find((line) => line.type === 'tree')?.data as JsonLineTree | undefined;
|
||||
if (tree == null) throw Error("'yarn list --json' did not output a tree");
|
||||
|
||||
const queue = [...tree.trees];
|
||||
const dependencies: Dependency[] = [];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const { name: treeName, children } = queue.pop()!;
|
||||
const match = /(@?\S+)@(\S+)$/.exec(treeName);
|
||||
if (match) {
|
||||
const [_, name, version] = match;
|
||||
dependencies.push({ name, version });
|
||||
}
|
||||
if (children != null) {
|
||||
queue.push(...children);
|
||||
}
|
||||
}
|
||||
|
||||
return dedupeDependencies(dependencies);
|
||||
},
|
||||
};
|
||||
|
||||
type JsonLine =
|
||||
| { type: unknown; data: unknown }
|
||||
| { type: 'tree'; data: JsonLineTree };
|
||||
|
||||
interface JsonLineTree {
|
||||
type: 'list';
|
||||
trees: Tree[];
|
||||
}
|
||||
|
||||
interface Tree {
|
||||
name: string;
|
||||
children?: Tree[];
|
||||
}
|
||||
@@ -52,8 +52,8 @@ export async function importEntrypointFile<T>(path: string): Promise<T> {
|
||||
esmResolve: true,
|
||||
alias: {
|
||||
'webextension-polyfill': resolve(
|
||||
wxt.config.root,
|
||||
'node_modules/wxt/dist/virtual/mock-browser.js',
|
||||
wxt.config.wxtModuleDir,
|
||||
'dist/virtual/mock-browser.js',
|
||||
),
|
||||
},
|
||||
// Continue using node to load TS files even if `bun run --bun` is detected. Jiti does not
|
||||
|
||||
@@ -71,6 +71,7 @@ export async function resolveConfig(
|
||||
inlineConfig.root ?? userConfig.root ?? process.cwd(),
|
||||
);
|
||||
const wxtDir = path.resolve(root, '.wxt');
|
||||
const wxtModuleDir = await resolveWxtModuleDir();
|
||||
const srcDir = path.resolve(root, mergedConfig.srcDir ?? root);
|
||||
const entrypointsDir = path.resolve(
|
||||
srcDir,
|
||||
@@ -127,6 +128,7 @@ export async function resolveConfig(
|
||||
outBaseDir,
|
||||
outDir,
|
||||
publicDir,
|
||||
wxtModuleDir,
|
||||
root,
|
||||
runnerConfig,
|
||||
srcDir,
|
||||
@@ -256,6 +258,7 @@ function resolveInternalZipConfig(
|
||||
root: string,
|
||||
mergedConfig: InlineConfig,
|
||||
): NullablyRequired<ResolvedConfig['zip']> {
|
||||
const downloadedPackagesDir = path.resolve(root, '.wxt/local_modules');
|
||||
return {
|
||||
name: undefined,
|
||||
sourcesTemplate: '{{name}}-{{version}}-sources.zip',
|
||||
@@ -275,6 +278,8 @@ function resolveInternalZipConfig(
|
||||
// From user
|
||||
...(mergedConfig.zip?.excludeSources ?? []),
|
||||
],
|
||||
downloadPackages: mergedConfig.zip?.downloadPackages ?? [],
|
||||
downloadedPackagesDir,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -322,3 +327,15 @@ async function getUnimportOptions(
|
||||
defaultOptions,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to `node_modules/wxt`.
|
||||
*/
|
||||
async function resolveWxtModuleDir() {
|
||||
const requireResolve =
|
||||
require?.resolve ??
|
||||
(await import('node:module')).default.createRequire(import.meta.url)
|
||||
.resolve;
|
||||
// require.resolve returns the wxt/dist/index file, not the package's root directory, which we want to return
|
||||
return path.resolve(requireResolve('wxt'), '../..');
|
||||
}
|
||||
|
||||
@@ -262,6 +262,7 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
|
||||
outDir: fakeDir(),
|
||||
publicDir: fakeDir(),
|
||||
root: fakeDir(),
|
||||
wxtModuleDir: fakeDir(),
|
||||
runnerConfig: {
|
||||
config: {},
|
||||
},
|
||||
@@ -285,6 +286,8 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
|
||||
sourcesRoot: fakeDir(),
|
||||
sourcesTemplate: '{{name}}-sources.zip',
|
||||
name: faker.person.firstName().toLowerCase(),
|
||||
downloadedPackagesDir: fakeDir(),
|
||||
downloadPackages: [],
|
||||
},
|
||||
transformManifest: () => {},
|
||||
userConfigMetadata: {},
|
||||
@@ -305,6 +308,7 @@ export const fakeWxt = fakeObjectCreator<Wxt>(() => ({
|
||||
hooks: mock(),
|
||||
logger: mock(),
|
||||
reloadConfig: vi.fn(),
|
||||
pm: mock(),
|
||||
}));
|
||||
|
||||
export function setFakeWxt(overrides?: DeepPartial<Wxt>) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { InlineConfig, Wxt, WxtDevServer, WxtHooks } from '~/types';
|
||||
import { resolveConfig } from './utils/building';
|
||||
import { createHooks } from 'hookable';
|
||||
import { createWxtPackageManager } from './package-managers';
|
||||
|
||||
/**
|
||||
* Global variable set once `createWxt` is called once. Since this variable is used everywhere, this
|
||||
@@ -18,6 +19,7 @@ export async function registerWxt(
|
||||
): Promise<void> {
|
||||
const config = await resolveConfig(inlineConfig, command, server);
|
||||
const hooks = createHooks<WxtHooks>();
|
||||
const pm = await createWxtPackageManager(config.root);
|
||||
|
||||
wxt = {
|
||||
config,
|
||||
@@ -28,6 +30,7 @@ export async function registerWxt(
|
||||
async reloadConfig() {
|
||||
wxt.config = await resolveConfig(inlineConfig, command, server);
|
||||
},
|
||||
pm,
|
||||
};
|
||||
|
||||
// Initialize hooks
|
||||
|
||||
+124
-22
@@ -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,13 @@ 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';
|
||||
import { normalizePath } from './utils/paths';
|
||||
|
||||
/**
|
||||
* Build and zip the extension for distribution.
|
||||
* @param config Opitonal config that will override your `<root>/wxt.config.ts`.
|
||||
* @param config Optional config that will override your `<root>/wxt.config.ts`.
|
||||
* @returns A list of all files included in the ZIP.
|
||||
*/
|
||||
export async function zip(config?: InlineConfig): Promise<string[]> {
|
||||
@@ -26,7 +28,7 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
|
||||
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 +45,29 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
|
||||
// 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 { overrides, files: downloadedPackages } =
|
||||
await downloadPrivatePackages();
|
||||
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,
|
||||
transform(file, content) {
|
||||
if (file.endsWith('package.json')) {
|
||||
return addOverridesToPackageJson(content, overrides);
|
||||
}
|
||||
},
|
||||
additionalFiles: downloadedPackages,
|
||||
});
|
||||
zipFiles.push(sourcesZipPath);
|
||||
}
|
||||
@@ -81,3 +81,105 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
|
||||
|
||||
return zipFiles;
|
||||
}
|
||||
async function zipDir(
|
||||
directory: string,
|
||||
outputPath: string,
|
||||
options?: {
|
||||
include?: string[];
|
||||
exclude?: string[];
|
||||
transform?: (
|
||||
file: string,
|
||||
content: string,
|
||||
) => Promise<string | undefined | void> | string | undefined | void;
|
||||
additionalWork?: (archive: JSZip) => Promise<void> | void;
|
||||
additionalFiles?: string[];
|
||||
},
|
||||
): Promise<void> {
|
||||
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),
|
||||
)
|
||||
);
|
||||
});
|
||||
const filesToZip = [
|
||||
...files,
|
||||
...(options?.additionalFiles ?? []).map((file) =>
|
||||
path.relative(directory, file),
|
||||
),
|
||||
];
|
||||
for (const file of filesToZip) {
|
||||
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');
|
||||
}
|
||||
|
||||
async function downloadPrivatePackages() {
|
||||
const overrides: Record<string, string> = {};
|
||||
const files: string[] = [];
|
||||
|
||||
if (wxt.config.zip.downloadPackages.length > 0) {
|
||||
const _downloadPackages = new Set(wxt.config.zip.downloadPackages);
|
||||
const allPackages = await wxt.pm.listDependencies({
|
||||
all: true,
|
||||
cwd: wxt.config.root,
|
||||
});
|
||||
const downloadPackages = allPackages.filter((pkg) =>
|
||||
_downloadPackages.has(pkg.name),
|
||||
);
|
||||
|
||||
for (const pkg of downloadPackages) {
|
||||
wxt.logger.info(`Downloading package: ${pkg.name}@${pkg.version}`);
|
||||
const id = `${pkg.name}@${pkg.version}`;
|
||||
const tgzPath = await wxt.pm.downloadDependency(
|
||||
id,
|
||||
wxt.config.zip.downloadedPackagesDir,
|
||||
);
|
||||
files.push(tgzPath);
|
||||
overrides[id] =
|
||||
'file://./' + normalizePath(path.relative(wxt.config.root, tgzPath));
|
||||
}
|
||||
}
|
||||
|
||||
return { overrides, files };
|
||||
}
|
||||
|
||||
function addOverridesToPackageJson(
|
||||
content: string,
|
||||
overrides: Record<string, string>,
|
||||
): string {
|
||||
if (Object.keys(overrides).length === 0) return content;
|
||||
|
||||
const oldPackage = JSON.parse(content);
|
||||
const newPackage = {
|
||||
...oldPackage,
|
||||
[wxt.pm.overridesKey]: {
|
||||
...oldPackage[wxt.pm.overridesKey],
|
||||
...overrides,
|
||||
},
|
||||
};
|
||||
return JSON.stringify(newPackage, null, 2);
|
||||
}
|
||||
|
||||
+85
-2
@@ -7,6 +7,7 @@ import type { PluginVisualizerOptions } from '@aklinker1/rollup-plugin-visualize
|
||||
import type { FSWatcher } from 'chokidar';
|
||||
import { ResolvedConfig as C12ResolvedConfig } from 'c12';
|
||||
import { Hookable, NestedHooks } from 'hookable';
|
||||
import type * as Nypm from 'nypm';
|
||||
|
||||
export interface InlineConfig {
|
||||
/**
|
||||
@@ -171,6 +172,31 @@ export interface InlineConfig {
|
||||
* ]
|
||||
*/
|
||||
excludeSources?: string[];
|
||||
/**
|
||||
* The Firefox review process requires the extension be buildable from source to make reviewing
|
||||
* easier. This field allows you to use private packages without exposing your auth tokens.
|
||||
*
|
||||
* Just list the name of all the packages you want to download and include in the sources zip.
|
||||
* Usually, these will be private packages behind auth tokens, but they don't have to be.
|
||||
*
|
||||
* All packages listed here will be downloaded to in `.wxt/local_modules/` and an `overrides` or
|
||||
* `resolutions` field (depending on your package manager) will be added to the `package.json`,
|
||||
* pointing to the downloaded packages.
|
||||
*
|
||||
* > ***DO NOT include versions or version filters.*** Just the package name. If multiple
|
||||
* > versions of a package are present in the project, all versions will be downloaded and
|
||||
* > referenced in the package.json correctly.
|
||||
*
|
||||
* @default []
|
||||
*
|
||||
* @example
|
||||
* // Correct:
|
||||
* ["@scope/package-name", "package-name"]
|
||||
*
|
||||
* // Incorrect, don't include versions!!!
|
||||
* ["@scope/package-name@1.1.3", "package-name@^2"]
|
||||
*/
|
||||
downloadPackages?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -729,14 +755,23 @@ export type UserManifestFn = (
|
||||
) => UserManifest | Promise<UserManifest>;
|
||||
|
||||
export interface ConfigEnv {
|
||||
/**
|
||||
* The build mode passed into the CLI. By default, `wxt` uses `"development"` and `wxt build|zip`
|
||||
* uses `"production"`.
|
||||
*/
|
||||
mode: string;
|
||||
/**
|
||||
* The command used to run WXT. `"serve"` during development and `"build"` for any other command.
|
||||
*/
|
||||
command: 'build' | 'serve';
|
||||
/**
|
||||
* Browser passed in from the CLI
|
||||
* Browser passed in from the CLI via the `-b` or `--browser` flag. Defaults to `"chrome"` when not passed.
|
||||
*/
|
||||
browser: TargetBrowser;
|
||||
/**
|
||||
* Manifest version passed in from the CLI
|
||||
* Manifest version passed in from the CLI via the `--mv2` or `--mv3` flags. When not passed, it depends on the target browser. See
|
||||
* [the guide](https://wxt.dev/guide/multiple-browsers.html#target-manifest-version) for more
|
||||
* details.
|
||||
*/
|
||||
manifestVersion: 2 | 3;
|
||||
}
|
||||
@@ -950,6 +985,10 @@ export interface Wxt {
|
||||
* Reload config file and update the `config` field with the result.
|
||||
*/
|
||||
reloadConfig: () => Promise<void>;
|
||||
/**
|
||||
* Package manager utilities.
|
||||
*/
|
||||
pm: WxtPackageManager;
|
||||
}
|
||||
|
||||
export interface ResolvedConfig {
|
||||
@@ -963,6 +1002,10 @@ export interface ResolvedConfig {
|
||||
outBaseDir: string;
|
||||
outDir: string;
|
||||
debug: boolean;
|
||||
/**
|
||||
* Directory pointing to `node_modules/wxt`, wherever WXT is installed.
|
||||
*/
|
||||
wxtModuleDir: string;
|
||||
mode: string;
|
||||
command: 'build' | 'serve';
|
||||
browser: TargetBrowser;
|
||||
@@ -981,6 +1024,8 @@ export interface ResolvedConfig {
|
||||
includeSources: string[];
|
||||
excludeSources: string[];
|
||||
sourcesRoot: string;
|
||||
downloadedPackagesDir: string;
|
||||
downloadPackages: string[];
|
||||
};
|
||||
transformManifest: (manifest: Manifest.WebExtensionManifest) => void;
|
||||
analysis: {
|
||||
@@ -1072,3 +1117,41 @@ export type WxtUnimportOptions = Partial<UnimportOptions> & {
|
||||
export type WxtResolvedUnimportOptions = Partial<UnimportOptions> & {
|
||||
eslintrc: ResolvedEslintrc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Package management utils built on top of [`nypm`](https://www.npmjs.com/package/nypm)
|
||||
*/
|
||||
export interface WxtPackageManager extends Nypm.PackageManager {
|
||||
addDependency: typeof Nypm.addDependency;
|
||||
addDevDependency: typeof Nypm.addDevDependency;
|
||||
ensureDependencyInstalled: typeof Nypm.ensureDependencyInstalled;
|
||||
installDependencies: typeof Nypm.installDependencies;
|
||||
removeDependency: typeof Nypm.removeDependency;
|
||||
/**
|
||||
* Download a package's TGZ file and move it into the `downloadDir`. Use's `npm pack <name>`, so
|
||||
* you must have setup authorization in `.npmrc` file, regardless of the package manager used.
|
||||
*
|
||||
* @param id Name of the package to download, can include a version (like `wxt@0.17.1`)
|
||||
* @param downloadDir Where to store the package.
|
||||
* @returns Absolute path to downloaded file.
|
||||
*/
|
||||
downloadDependency: (id: string, downloadDir: string) => Promise<string>;
|
||||
/**
|
||||
* Run `npm ls`, `pnpm ls`, or `bun pm ls`, or `yarn list` and return the results.
|
||||
*
|
||||
* WARNING: Yarn always returns all dependencies
|
||||
*/
|
||||
listDependencies: (options?: {
|
||||
cwd?: string;
|
||||
all?: boolean;
|
||||
}) => Promise<Dependency[]>;
|
||||
/**
|
||||
* Key used to override package versions. Sometimes called "resolutions".
|
||||
*/
|
||||
overridesKey: string;
|
||||
}
|
||||
|
||||
export interface Dependency {
|
||||
name: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export default defineContentScript({
|
||||
matches: ['*://*.google.com/*'],
|
||||
main() {
|
||||
console.log('Hello content.');
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
export default defineContentScript({
|
||||
matches: ['*://*.google.com/*'],
|
||||
main() {
|
||||
console.log('Hello content.');
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
export default defineContentScript({
|
||||
matches: ['*://*.google.com/*'],
|
||||
main() {
|
||||
console.log('Hello content.');
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
export default defineContentScript({
|
||||
matches: ['*://*.google.com/*'],
|
||||
main() {
|
||||
console.log('Hello content.');
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
export default defineContentScript({
|
||||
matches: ['*://*.google.com/*'],
|
||||
main() {
|
||||
console.log('Hello content.');
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user