Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f32cdafdf6 | |||
| 2e2272d07a | |||
| 27ffff7e0a | |||
| 305cebe0d0 | |||
| 4602a068f8 | |||
| 3a947d72c6 | |||
| 1940e3530f | |||
| dd549dc686 | |||
| 3417bd3ed2 | |||
| 9470d2b696 | |||
| f523a7d31d | |||
| ea4c2b035b | |||
| c5bfba837f | |||
| c5ad957b25 | |||
| cc849e7cea | |||
| 22428da54d | |||
| f6071f7e2d | |||
| 08e8560865 | |||
| 75f01db543 | |||
| 5a028d2551 | |||
| 07a4ba8bc5 | |||
| f84921f735 | |||
| 872494e490 | |||
| d0848b0be5 | |||
| c848956777 | |||
| 296997d10e | |||
| ffdb22238c | |||
| a63c320403 | |||
| aa86ed78a3 | |||
| 19456ec855 | |||
| 2c57840c44 | |||
| 26b7927f62 | |||
| d4267af725 | |||
| 16a2aedcd0 | |||
| 755f04f777 | |||
| cf9539ddab | |||
| 99d558f33c | |||
| 1fe073004b | |||
| 6ae5fb5edb | |||
| 3a3953edf6 | |||
| c8e788eaf8 | |||
| 088a44496d | |||
| 60a57c5abc | |||
| 482466bbbf | |||
| 65deb7317b | |||
| 00ae061822 | |||
| 9bd7fc1bd5 | |||
| 98ddb83f1d | |||
| 6dd8b9d072 |
@@ -10,7 +10,7 @@ jobs:
|
||||
if: github.repository_owner == 'wxt-dev'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
if: ${{ github.repository == 'wxt-dev/wxt' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# Only fetch the config file from the repository
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
- validate
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ssh-key: ${{ secrets.DEPLOY_KEY }} # https://github.com/sbellone/release-workflow-example
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
id-token: write # OIDC for NPM publishing
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ssh-key: ${{ secrets.DEPLOY_KEY }}
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
- name: Commit Changes
|
||||
id: commit
|
||||
uses: stefanzweifel/git-auto-commit-action@01d77ca6cb089da1360e540865f7d035c95aa199 # v6.0.1
|
||||
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -68,13 +68,13 @@ jobs:
|
||||
coverage: false
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@22457c87c1b161cf7dde222c3e82b2b5f8d2bed2 # v2.0.2
|
||||
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||
|
||||
- name: Run Tests
|
||||
if: ${{ ! matrix.coverage }}
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
|
||||
- name: Upload Coverage
|
||||
if: matrix.coverage
|
||||
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
- vue
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
path: 'docs/tapes/init-demo.tape'
|
||||
|
||||
- name: Save recorded GIF
|
||||
uses: stefanzweifel/git-auto-commit-action@01d77ca6cb089da1360e540865f7d035c95aa199 # v6.0.1
|
||||
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
Vendored
+2
-1
@@ -10,5 +10,6 @@
|
||||
// Additional guidelines for Copilot
|
||||
"github.copilot.chat.codeGeneration.instructions": [
|
||||
{ "file": "CONTRIBUTING.md" }
|
||||
]
|
||||
],
|
||||
"oxc.enable": true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Maintainers
|
||||
|
||||
A couple of things for you to consider before merging a PR or giving the go-ahead on a feature proposal.
|
||||
|
||||
## Be picky about new features and packages
|
||||
|
||||
We are responsible for maintaining them and fixing related bugs after the PR is merged. The community can always release their own WXT modules or packages, not everything needs to be built into WXT.
|
||||
|
||||
## Prefer standards over customization
|
||||
|
||||
Don't merge PRs that just add another way to do something, like [this one](https://github.com/wxt-dev/wxt/pull/2053#issuecomment-3857010196).
|
||||
|
||||
WXT is opinionated, if you have questions about what is WXT's opinion or we need to create a new one, create an issue and @ me to discuss.
|
||||
|
||||
## PRs should be small and targeted
|
||||
|
||||
A PR should make one change. They should not make any unrelated changes outside of accomplishing the one thing. This makes PRs easier to review and they get merged more quickly - a win-win for everyone.
|
||||
|
||||
## `@wxt-dev/*` packages are separate
|
||||
|
||||
We can't make changes to these packages assuming people are using them only with WXT. For example, I almost missed [this PR](https://github.com/wxt-dev/wxt/pull/2049#issuecomment-3861251599).
|
||||
|
||||
I want these packages to be usable on their own so if people don't like WXT's build tool, they can still use our other awesome packages.
|
||||
|
||||
## Minimize dependencies
|
||||
|
||||
I don't like how heavy lots of frameworks are. It's unavoidable to a certain extent, but if you can do something without another dependency, try to.
|
||||
|
||||
If you need a dependency, look for one with zero dependencies and that's respectable.
|
||||
|
||||
## Look at milestones
|
||||
|
||||
I've organized WXT's long term plans into [milestones](https://github.com/wxt-dev/wxt/milestones), check those out to get an idea of my priorities for some of the larger features.
|
||||
|
||||
## Require tests
|
||||
|
||||
If someone opens a PR to fix a bug but doesn't include tests, don't merge the PR. Tests are required to prevent regressions and maintain a codebase long term.
|
||||
|
||||
## Ask for reproductions for bugs
|
||||
|
||||
You don't need to triage bugs if someone doesn't give you enough information. You can always ask for a repo with a reproduction or wait for more details.
|
||||
|
||||
If there's not an easy way to reproduce a bug, you're wasting your time triaging it. Just be mindful of your own time!
|
||||
|
||||
Here's an example of how to ask for a reproduction: <https://github.com/wxt-dev/wxt/issues/2064#issuecomment-3862579110>. You could be more blunt than this.
|
||||
|
||||
## Add yourself as a code owner
|
||||
|
||||
If you want to be responsible for a specific package or directory, add yourself to the [`.github/CODEOWNERS`](https://github.com/wxt-dev/wxt/blob/main/.github/CODEOWNERS) file to get added as a reviewer to PRs automatically. You can also add yourself to the default list to be added as a reviewer on all PRs.
|
||||
@@ -116,6 +116,7 @@ const chromeExtensionIds = [
|
||||
'jmghclbfbbapimhbgnpffbimphlpolnm', // Teams Chat Exporter
|
||||
'jdcppdokgfbnhiacbeplahgnciahnhck', // Lofi BGM Player - Free lofi focus music for work & study
|
||||
'cgpmbiiagnehkikhcbnhiagfomajncpa', // Margin - Annotate and highlight any webpage, with your notes saved to the decentralized AT Protocol.
|
||||
'mfjdonmgmgcijagclnkfhmjiblbfjaid', // KeyFloat - Floating multilingual keyboard with native key mappings, drag, dark mode, sounds, and dynamic layouts for macOS & Windows
|
||||
];
|
||||
|
||||
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import { Feed } from 'feed';
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import llmstxt from 'vitepress-plugin-llms';
|
||||
|
||||
const origin = 'https://wxt.dev';
|
||||
|
||||
@@ -71,6 +72,7 @@ export default defineConfig({
|
||||
vite: {
|
||||
clearScreen: false,
|
||||
plugins: [
|
||||
llmstxt(),
|
||||
groupIconVitePlugin({
|
||||
customIcon: {
|
||||
'wxt.config.ts': localIconLoader(
|
||||
|
||||
@@ -105,3 +105,24 @@ export default defineWebExtConfig({
|
||||
disabled: true,
|
||||
});
|
||||
```
|
||||
|
||||
### Enabling Chrome Features
|
||||
|
||||
Some APIs are disabled in Chrome during development because of the default flags `web-ext` uses to launch Chrome, like the [Prompt API](https://developer.chrome.com/docs/ai/prompt-api).
|
||||
|
||||
If your extension depends on new features or services, you can enable them via `chromiumArgs`:
|
||||
|
||||
```ts
|
||||
import { defineWebExtConfig } from 'wxt';
|
||||
|
||||
export default defineWebExtConfig({
|
||||
chromiumArgs: [
|
||||
// For example, this flag enables the Prompt API
|
||||
'--disable-features=DisableLoadExtensionCommandLineSwitch',
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
There is no comprehensive list of what feature flags enable what APIs and services.
|
||||
|
||||
Alternatively, if you can't find a flag that enables a feature you're looking for, [disable the opening the browser during development](#disable-opening-browser) and use your regular chrome profile for development.
|
||||
|
||||
@@ -20,6 +20,8 @@ export default defineConfig({
|
||||
|
||||
Most hooks provide the `wxt` object as the first argument. It contains the resolved config and other info about the current build. The other arguments can be modified by reference to change different parts of the build system.
|
||||
|
||||
You can find the [list of all available hooks](/api/reference/wxt/interfaces/WxtHooks) in the API reference.
|
||||
|
||||
Putting one-off hooks like this in your config file is simple, but if you find yourself writing lots of hooks, you should extract them into [WXT Modules](/guide/essentials/wxt-modules) instead.
|
||||
|
||||
## Execution Order
|
||||
|
||||
@@ -470,6 +470,19 @@ When you define a Newtab entrypoint, WXT will automatically update the manifest
|
||||
/>
|
||||
<meta name="manifest.type" content="page_action|browser_action" />
|
||||
<meta name="manifest.browser_style" content="true|false" />
|
||||
<!-- Firefox only: where to place the action button -->
|
||||
<meta
|
||||
name="manifest.default_area"
|
||||
content="navbar|menupanel|tabstrip|personaltoolbar"
|
||||
/>
|
||||
<!-- Firefox only: icons for light/dark themes -->
|
||||
<meta
|
||||
name="manifest.theme_icons"
|
||||
content="[
|
||||
{ light: '/icon-light-16.png', dark: '/icon-dark-16.png', size: 16 },
|
||||
{ light: '/icon-light-32.png', dark: '/icon-dark-32.png', size: 32 }
|
||||
]"
|
||||
/>
|
||||
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
|
||||
@@ -66,7 +66,7 @@ Each module's setup function is executed after the `wxt.config.ts` file is loade
|
||||
|
||||
Refer to the [API reference](/api/reference/wxt/interfaces/Wxt) for a complete list of properties and functions available.
|
||||
|
||||
Also to make sure and read about all the [hooks that are available](https://wxt.dev/api/reference/wxt/interfaces/WxtHooks) - they are essential to writing modules.
|
||||
Also make sure to read about [all the hooks that are available](/api/reference/wxt/interfaces/WxtHooks) - they are essential to writing modules.
|
||||
|
||||
### Recipes
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ This page is dedicated to all the awesome people how have made something for WXT
|
||||
## Blog Posts
|
||||
|
||||
- [Building Modern Cross Browser Web Extensions](https://aabidk.dev/tags/wxt/) by Aabid ([@aabidk20](https://github.com/aabidk20))
|
||||
- [Building Browser Extensions with WXT](https://rxliuli.com/blog/browser-extension-dev-01-introduction-to-basic-concepts/) by rxliuli ([@rxliuli](https://github.com/rxliuli))
|
||||
|
||||
## NPM Packages
|
||||
|
||||
|
||||
@@ -164,6 +164,10 @@ Both issues have the same cause: the library puts something outside the `ShadowR
|
||||
|
||||
Both issues have the same fix: tell the library to put elements inside the `ShadowRoot`, not outside it. See the details above for more information and example fixes for each problem.
|
||||
|
||||
## Does WXT provide docs for LLMs?
|
||||
|
||||
Yes, WXT's documentation provides markdown files based on the [the /llms.txt proposal](https://llmstxt.org/).
|
||||
|
||||
## Is there an LLM trained on WXT's docs that I chat with?
|
||||
|
||||
Yes! There's a "Ask AI" button in the bottom right of the page, try it out! Or visit <https://knowledge.wxt.dev/> for a fullscreen experience.
|
||||
@@ -186,3 +190,33 @@ To run the WXT dev server in a devcontainer, but load the dev build of your exte
|
||||
|
||||
3. **Tell WXT to listen on all network interfaces**
|
||||
To enable hot-reloading, your extension has to connect to the WXT dev server running inside your container. WXT will only listen on `localhost` by default, which prevents connections from outside the devcontainer. To fix this you can instruct WXT to listen on all interfaces with `wxt --host 0.0.0.0`.
|
||||
|
||||
## How do I use the new Prompt API in Chrome?
|
||||
|
||||
The service responsible for the [Prompt API](https://developer.chrome.com/docs/ai/prompt-api) is not enabled by default if you let WXT open the browser during dev mode. When checking `LanguageModel.availability`, you will always receive "unavailable".
|
||||
|
||||
You have two options:
|
||||
|
||||
1. Pass the `--disable-features=DisableLoadExtensionCommandLineSwitch` feature flag to enable the service in the browser WXT opens:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
webExt: {
|
||||
chromiumArgs: [
|
||||
'--disable-features=DisableLoadExtensionCommandLineSwitch',
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
2. Disable the runner and install your extension in your regular chrome profile manually:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
webExt: {
|
||||
disabled: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
+2
-2
@@ -4,13 +4,12 @@
|
||||
"engines": {
|
||||
"node": ">=18.20.3"
|
||||
},
|
||||
"packageManager": "pnpm@10.15.1",
|
||||
"packageManager": "pnpm@10.29.2+sha512.bef43fa759d91fd2da4b319a5a0d13ef7a45bb985a3d7342058470f9d2051a3ba8674e629672654686ef9443ad13a82da2beb9eeb3e0221c87b8154fff9d74b8",
|
||||
"scripts": {
|
||||
"check": "check && pnpm -r --sequential run check",
|
||||
"test": "pnpm -r --sequential run test run",
|
||||
"test:coverage": "pnpm -r --sequential run test:coverage",
|
||||
"prepare": "simple-git-hooks",
|
||||
"prepublish": "pnpm -s build",
|
||||
"docs:gen": "typedoc --options docs/typedoc.json",
|
||||
"docs:dev": "pnpm -s docs:gen && vitepress dev docs",
|
||||
"docs:build": "pnpm -s docs:gen && vitepress build docs",
|
||||
@@ -47,6 +46,7 @@
|
||||
"vitepress": "^1.6.4",
|
||||
"vitepress-knowledge": "^0.4.1",
|
||||
"vitepress-plugin-group-icons": "^1.7.1",
|
||||
"vitepress-plugin-llms": "^1.11.0",
|
||||
"vitest-mock-extended": "^3.1.0",
|
||||
"vue": "^3.5.27",
|
||||
"wxt": "workspace:*"
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# Changelog
|
||||
|
||||
## v0.5.2
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.1...analytics-v0.5.2)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Normalize path for createAnalytics of analytics/index.ts ([#2013](https://github.com/wxt-dev/wxt/pull/2013))
|
||||
- Allow custom API URL in Google Analytics 4 provider options ([#1653](https://github.com/wxt-dev/wxt/pull/1653))
|
||||
|
||||
### 💅 Refactors
|
||||
|
||||
- Code cleanup in analytics package ([#2084](https://github.com/wxt-dev/wxt/pull/2084))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Fix other type error after `chrome` types update ([31ebf966](https://github.com/wxt-dev/wxt/commit/31ebf966))
|
||||
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
|
||||
- Use `tsdown` to build packages ([#2006](https://github.com/wxt-dev/wxt/pull/2006))
|
||||
- Move script-only dev dependencies to top-level `package.json` ([#2007](https://github.com/wxt-dev/wxt/pull/2007))
|
||||
- Update dependencies ([#2069](https://github.com/wxt-dev/wxt/pull/2069))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Honwhy Wang <honwhy.wang@gmail.com>
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
|
||||
|
||||
## v0.5.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.0...analytics-v0.5.1)
|
||||
|
||||
@@ -13,6 +13,22 @@ import { browser } from '@wxt-dev/browser';
|
||||
|
||||
const ANALYTICS_PORT = '@wxt-dev/analytics';
|
||||
|
||||
const INTERACTIVE_TAGS = new Set([
|
||||
'A',
|
||||
'BUTTON',
|
||||
'INPUT',
|
||||
'SELECT',
|
||||
'TEXTAREA',
|
||||
]);
|
||||
const INTERACTIVE_ROLES = new Set([
|
||||
'button',
|
||||
'link',
|
||||
'checkbox',
|
||||
'menuitem',
|
||||
'tab',
|
||||
'radio',
|
||||
]);
|
||||
|
||||
export function createAnalytics(config?: AnalyticsConfig): Analytics {
|
||||
if (!browser?.runtime?.id)
|
||||
throw Error(
|
||||
@@ -62,8 +78,8 @@ function createBackgroundAnalytics(
|
||||
|
||||
const getBackgroundMeta = () => ({
|
||||
timestamp: Date.now(),
|
||||
// Don't track sessions for the background, it can be running
|
||||
// indefinitely, and will inflate session duration stats.
|
||||
// Don't track sessions for the background, it can be running indefinitely
|
||||
// and will inflate session duration stats.
|
||||
sessionId: undefined,
|
||||
language: navigator.language,
|
||||
referrer: undefined,
|
||||
@@ -75,7 +91,7 @@ function createBackgroundAnalytics(
|
||||
const getBaseEvent = async (
|
||||
meta: AnalyticsEventMetadata,
|
||||
): Promise<BaseAnalyticsEvent> => {
|
||||
const platform = await platformInfo;
|
||||
const { arch, os } = await platformInfo;
|
||||
return {
|
||||
meta,
|
||||
user: {
|
||||
@@ -84,8 +100,8 @@ function createBackgroundAnalytics(
|
||||
version: config?.version ?? manifest.version_name ?? manifest.version,
|
||||
wxtMode: import.meta.env.MODE,
|
||||
wxtBrowser: import.meta.env.BROWSER,
|
||||
arch: platform.arch,
|
||||
os: platform.os,
|
||||
arch,
|
||||
os,
|
||||
browser: userAgent.browser.name,
|
||||
browserVersion: userAgent.browser.version,
|
||||
...(await userProperties),
|
||||
@@ -259,22 +275,6 @@ function defineStorageItem<T>(
|
||||
};
|
||||
}
|
||||
|
||||
const INTERACTIVE_TAGS = new Set([
|
||||
'A',
|
||||
'BUTTON',
|
||||
'INPUT',
|
||||
'SELECT',
|
||||
'TEXTAREA',
|
||||
]);
|
||||
const INTERACTIVE_ROLES = new Set([
|
||||
'button',
|
||||
'link',
|
||||
'checkbox',
|
||||
'menuitem',
|
||||
'tab',
|
||||
'radio',
|
||||
]);
|
||||
|
||||
export function defineAnalyticsProvider<T = never>(
|
||||
definition: (
|
||||
/** The analytics object. */
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from 'wxt/modules';
|
||||
import { relative, resolve } from 'node:path';
|
||||
import type { AnalyticsConfig } from './types';
|
||||
import { normalizePath } from 'wxt';
|
||||
|
||||
declare module 'wxt/utils/define-app-config' {
|
||||
export interface WxtAppConfig {
|
||||
@@ -38,17 +39,15 @@ export default defineWxtModule({
|
||||
});
|
||||
|
||||
// Generate #analytics module
|
||||
const wxtAnalyticsCode = [
|
||||
`import { createAnalytics } from '${
|
||||
process.env.NPM
|
||||
? clientModuleId
|
||||
: relative(wxtAnalyticsFolder, clientModuleId)
|
||||
}';`,
|
||||
`import { useAppConfig } from '#imports';`,
|
||||
``,
|
||||
`export const analytics = createAnalytics(useAppConfig().analytics);`,
|
||||
``,
|
||||
].join('\n');
|
||||
const wxtAnalyticsCode = `import { createAnalytics } from '${
|
||||
process.env.NPM
|
||||
? clientModuleId
|
||||
: normalizePath(relative(wxtAnalyticsFolder, clientModuleId))
|
||||
}';
|
||||
import { useAppConfig } from '#imports';
|
||||
|
||||
export const analytics = createAnalytics(useAppConfig().analytics);
|
||||
`;
|
||||
addAlias(wxt, '#analytics', wxtAnalyticsIndex);
|
||||
wxt.hook('prepare:types', async (_, entries) => {
|
||||
entries.push({
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { BaseAnalyticsEvent } from '../types';
|
||||
const DEFAULT_ENGAGEMENT_TIME_IN_MSEC = 100;
|
||||
|
||||
export interface GoogleAnalytics4ProviderOptions {
|
||||
apiUrl?: string;
|
||||
apiSecret: string;
|
||||
measurementId: string;
|
||||
}
|
||||
@@ -18,7 +19,7 @@ export const googleAnalytics4 =
|
||||
): Promise<void> => {
|
||||
const url = new URL(
|
||||
config?.debug ? '/debug/mp/collect' : '/mp/collect',
|
||||
'https://www.google-analytics.com',
|
||||
options.apiUrl ?? 'https://www.google-analytics.com',
|
||||
);
|
||||
if (options.apiSecret)
|
||||
url.searchParams.set('api_secret', options.apiSecret);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wxt-dev/analytics",
|
||||
"version": "0.5.1",
|
||||
"version": "0.5.2",
|
||||
"description": "Add analytics to your web extension",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineWxtModule } from 'wxt/modules';
|
||||
import { resolve, relative } from 'node:path';
|
||||
import defu from 'defu';
|
||||
import sharp from 'sharp';
|
||||
import { ensureDir, exists } from 'fs-extra';
|
||||
import { ensureDir, pathExists } from 'fs-extra';
|
||||
|
||||
export default defineWxtModule<AutoIconsOptions>({
|
||||
name: '@wxt-dev/auto-icons',
|
||||
@@ -37,7 +37,7 @@ export default defineWxtModule<AutoIconsOptions>({
|
||||
if (!parsedOptions.enabled)
|
||||
return wxt.logger.warn(`\`[auto-icons]\` ${this.name} disabled`);
|
||||
|
||||
if (!(await exists(resolvedPath))) {
|
||||
if (!(await pathExists(resolvedPath))) {
|
||||
return wxt.logger.warn(
|
||||
`\`[auto-icons]\` Skipping icon generation, no base icon found at ${relative(process.cwd(), resolvedPath)}`,
|
||||
);
|
||||
|
||||
@@ -114,7 +114,7 @@ async function findBrowserBinary(target: string): Promise<string | undefined> {
|
||||
for (const target of targets) {
|
||||
const potentialPaths = KNOWN_BROWSER_PATHS[target]?.[platform] ?? [];
|
||||
for (const path of potentialPaths) {
|
||||
if (await exists(path)) return path;
|
||||
if (await pathExists(path)) return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,7 +207,7 @@ function deduplicateArgs(
|
||||
return args;
|
||||
}
|
||||
|
||||
async function exists(path: string): Promise<boolean> {
|
||||
async function pathExists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await open(path, 'r');
|
||||
return true;
|
||||
|
||||
@@ -706,6 +706,47 @@ describe('Storage Utils', () => {
|
||||
expect(migrateToV3).toBeCalledWith(4);
|
||||
});
|
||||
|
||||
it('should set the version without running migrations for empty storage items', async () => {
|
||||
const migrate = vi.fn((n: number) => n * 2);
|
||||
|
||||
const item = storage.defineItem<number>('local:key', {
|
||||
init: () => 1,
|
||||
version: 2,
|
||||
migrations: {
|
||||
2: migrate,
|
||||
},
|
||||
});
|
||||
const value = await item.getValue();
|
||||
const meta = await item.getMeta();
|
||||
|
||||
expect(value).toEqual(1);
|
||||
expect(meta).toEqual({ v: 2 });
|
||||
expect(migrate).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should set the version and run migrations if a storage value already exists and v2 is added', async () => {
|
||||
const key = 'local:key';
|
||||
|
||||
// Mimic as if an initial value was set by a previous version. We expect
|
||||
// this value to be migrated and doubled to V2.
|
||||
await storage.setItem(key, 1);
|
||||
|
||||
const migrate = vi.fn((n: number) => n * 2);
|
||||
const item = storage.defineItem<number>(key, {
|
||||
init: () => 1,
|
||||
version: 2,
|
||||
migrations: {
|
||||
2: migrate,
|
||||
},
|
||||
});
|
||||
const value = await item.getValue();
|
||||
const meta = await item.getMeta();
|
||||
|
||||
expect(value).toEqual(2);
|
||||
expect(meta).toEqual({ v: 2 });
|
||||
expect(migrate).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should call onMigrationComplete callback function if defined', async () => {
|
||||
await fakeBrowser.storage.local.set({
|
||||
count: 2,
|
||||
|
||||
+135
-21
@@ -17,6 +17,7 @@ function createStorage(): WxtStorage {
|
||||
sync: createDriver('sync'),
|
||||
managed: createDriver('managed'),
|
||||
};
|
||||
|
||||
const getDriver = (area: StorageArea) => {
|
||||
const driver = drivers[area];
|
||||
if (driver == null) {
|
||||
@@ -25,14 +26,17 @@ function createStorage(): WxtStorage {
|
||||
}
|
||||
return driver;
|
||||
};
|
||||
|
||||
const resolveKey = (key: StorageItemKey) => {
|
||||
const deliminatorIndex = key.indexOf(':');
|
||||
const driverArea = key.substring(0, deliminatorIndex) as StorageArea;
|
||||
|
||||
const driverKey = key.substring(deliminatorIndex + 1);
|
||||
if (driverKey == null)
|
||||
if (driverKey == null) {
|
||||
throw Error(
|
||||
`Storage key should be in the form of "area:key", but received "${key}"`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
driverArea,
|
||||
@@ -40,17 +44,23 @@ function createStorage(): WxtStorage {
|
||||
driver: getDriver(driverArea),
|
||||
};
|
||||
};
|
||||
|
||||
const getMetaKey = (key: string) => key + '$';
|
||||
|
||||
const mergeMeta = (oldMeta: any, newMeta: any): any => {
|
||||
const newFields = { ...oldMeta };
|
||||
|
||||
Object.entries(newMeta).forEach(([key, value]) => {
|
||||
if (value == null) delete newFields[key];
|
||||
else newFields[key] = value;
|
||||
});
|
||||
|
||||
return newFields;
|
||||
};
|
||||
|
||||
const getValueOrFallback = (value: any, fallback: any) =>
|
||||
value ?? fallback ?? null;
|
||||
|
||||
const getMetaValue = (properties: any) =>
|
||||
typeof properties === 'object' && !Array.isArray(properties)
|
||||
? properties
|
||||
@@ -64,11 +74,13 @@ function createStorage(): WxtStorage {
|
||||
const res = await driver.getItem<any>(driverKey);
|
||||
return getValueOrFallback(res, opts?.fallback ?? opts?.defaultValue);
|
||||
};
|
||||
|
||||
const getMeta = async (driver: WxtStorageDriver, driverKey: string) => {
|
||||
const metaKey = getMetaKey(driverKey);
|
||||
const res = await driver.getItem<any>(metaKey);
|
||||
return getMetaValue(res);
|
||||
};
|
||||
|
||||
const setItem = async (
|
||||
driver: WxtStorageDriver,
|
||||
driverKey: string,
|
||||
@@ -76,6 +88,7 @@ function createStorage(): WxtStorage {
|
||||
) => {
|
||||
await driver.setItem(driverKey, value ?? null);
|
||||
};
|
||||
|
||||
const setMeta = async (
|
||||
driver: WxtStorageDriver,
|
||||
driverKey: string,
|
||||
@@ -85,23 +98,27 @@ function createStorage(): WxtStorage {
|
||||
const existingFields = getMetaValue(await driver.getItem(metaKey));
|
||||
await driver.setItem(metaKey, mergeMeta(existingFields, properties));
|
||||
};
|
||||
|
||||
const removeItem = async (
|
||||
driver: WxtStorageDriver,
|
||||
driverKey: string,
|
||||
opts: RemoveItemOptions | undefined,
|
||||
) => {
|
||||
await driver.removeItem(driverKey);
|
||||
|
||||
if (opts?.removeMeta) {
|
||||
const metaKey = getMetaKey(driverKey);
|
||||
await driver.removeItem(metaKey);
|
||||
}
|
||||
};
|
||||
|
||||
const removeMeta = async (
|
||||
driver: WxtStorageDriver,
|
||||
driverKey: string,
|
||||
properties: string | string[] | undefined,
|
||||
) => {
|
||||
const metaKey = getMetaKey(driverKey);
|
||||
|
||||
if (properties == null) {
|
||||
await driver.removeItem(metaKey);
|
||||
} else {
|
||||
@@ -110,19 +127,19 @@ function createStorage(): WxtStorage {
|
||||
await driver.setItem(metaKey, newFields);
|
||||
}
|
||||
};
|
||||
|
||||
const watch = (
|
||||
driver: WxtStorageDriver,
|
||||
driverKey: string,
|
||||
cb: WatchCallback<any>,
|
||||
) => {
|
||||
return driver.watch(driverKey, cb);
|
||||
};
|
||||
) => driver.watch(driverKey, cb);
|
||||
|
||||
const storage: WxtStorage = {
|
||||
return {
|
||||
getItem: async (key, opts) => {
|
||||
const { driver, driverKey } = resolveKey(key);
|
||||
return await getItem(driver, driverKey, opts);
|
||||
},
|
||||
|
||||
getItems: async (keys) => {
|
||||
const areaToKeyMap = new Map<StorageArea, string[]>();
|
||||
const keyToOptsMap = new Map<string, GetItemOptions<any> | undefined>();
|
||||
@@ -131,6 +148,7 @@ function createStorage(): WxtStorage {
|
||||
keys.forEach((key) => {
|
||||
let keyStr: StorageItemKey;
|
||||
let opts: GetItemOptions<any> | undefined;
|
||||
|
||||
if (typeof key === 'string') {
|
||||
// key: string
|
||||
keyStr = key;
|
||||
@@ -143,9 +161,11 @@ function createStorage(): WxtStorage {
|
||||
keyStr = key.key;
|
||||
opts = key.options;
|
||||
}
|
||||
|
||||
orderedKeys.push(keyStr);
|
||||
const { driverArea, driverKey } = resolveKey(keyStr);
|
||||
const areaKeys = areaToKeyMap.get(driverArea) ?? [];
|
||||
|
||||
areaToKeyMap.set(driverArea, areaKeys.concat(driverKey));
|
||||
keyToOptsMap.set(keyStr, opts);
|
||||
});
|
||||
@@ -154,6 +174,7 @@ function createStorage(): WxtStorage {
|
||||
await Promise.all(
|
||||
Array.from(areaToKeyMap.entries()).map(async ([driverArea, keys]) => {
|
||||
const driverResults = await drivers[driverArea].getItems(keys);
|
||||
|
||||
driverResults.forEach((driverResult) => {
|
||||
const key = `${driverArea}:${driverResult.key}` as StorageItemKey;
|
||||
const opts = keyToOptsMap.get(key);
|
||||
@@ -161,6 +182,7 @@ function createStorage(): WxtStorage {
|
||||
driverResult.value,
|
||||
opts?.fallback ?? opts?.defaultValue,
|
||||
);
|
||||
|
||||
resultsMap.set(key, value);
|
||||
});
|
||||
}),
|
||||
@@ -171,14 +193,17 @@ function createStorage(): WxtStorage {
|
||||
value: resultsMap.get(key),
|
||||
}));
|
||||
},
|
||||
|
||||
getMeta: async (key) => {
|
||||
const { driver, driverKey } = resolveKey(key);
|
||||
return await getMeta(driver, driverKey);
|
||||
},
|
||||
|
||||
getMetas: async (args) => {
|
||||
const keys = args.map((arg) => {
|
||||
const key = typeof arg === 'string' ? arg : arg.key;
|
||||
const { driverArea, driverKey } = resolveKey(key);
|
||||
|
||||
return {
|
||||
key,
|
||||
driverArea,
|
||||
@@ -186,6 +211,7 @@ function createStorage(): WxtStorage {
|
||||
driverMetaKey: getMetaKey(driverKey),
|
||||
};
|
||||
});
|
||||
|
||||
const areaToDriverMetaKeysMap = keys.reduce<
|
||||
Partial<Record<StorageArea, (typeof keys)[number][]>>
|
||||
>((map, key) => {
|
||||
@@ -211,10 +237,12 @@ function createStorage(): WxtStorage {
|
||||
meta: resultsMap[key.key],
|
||||
}));
|
||||
},
|
||||
|
||||
setItem: async (key, value) => {
|
||||
const { driver, driverKey } = resolveKey(key);
|
||||
await setItem(driver, driverKey, value);
|
||||
},
|
||||
|
||||
setItems: async (items) => {
|
||||
const areaToKeyValueMap: Partial<
|
||||
Record<StorageArea, Array<{ key: string; value: any }>>
|
||||
@@ -229,6 +257,7 @@ function createStorage(): WxtStorage {
|
||||
value: item.value,
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
Object.entries(areaToKeyValueMap).map(async ([driverArea, values]) => {
|
||||
const driver = getDriver(driverArea as StorageArea);
|
||||
@@ -236,10 +265,12 @@ function createStorage(): WxtStorage {
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
setMeta: async (key, properties) => {
|
||||
const { driver, driverKey } = resolveKey(key);
|
||||
await setMeta(driver, driverKey, properties);
|
||||
},
|
||||
|
||||
setMetas: async (items) => {
|
||||
const areaToMetaUpdatesMap: Partial<
|
||||
Record<StorageArea, { key: string; properties: any }[]>
|
||||
@@ -278,16 +309,19 @@ function createStorage(): WxtStorage {
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
removeItem: async (key, opts) => {
|
||||
const { driver, driverKey } = resolveKey(key);
|
||||
await removeItem(driver, driverKey, opts);
|
||||
},
|
||||
|
||||
removeItems: async (keys) => {
|
||||
const areaToKeysMap: Partial<Record<StorageArea, string[]>> = {};
|
||||
|
||||
keys.forEach((key) => {
|
||||
let keyStr: StorageItemKey;
|
||||
let opts: RemoveItemOptions | undefined;
|
||||
|
||||
if (typeof key === 'string') {
|
||||
// key: string
|
||||
keyStr = key;
|
||||
@@ -303,9 +337,11 @@ function createStorage(): WxtStorage {
|
||||
keyStr = key.key;
|
||||
opts = key.options;
|
||||
}
|
||||
|
||||
const { driverArea, driverKey } = resolveKey(keyStr);
|
||||
areaToKeysMap[driverArea] ??= [];
|
||||
areaToKeysMap[driverArea].push(driverKey);
|
||||
|
||||
if (opts?.removeMeta) {
|
||||
areaToKeysMap[driverArea].push(getMetaKey(driverKey));
|
||||
}
|
||||
@@ -318,36 +354,45 @@ function createStorage(): WxtStorage {
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
clear: async (base) => {
|
||||
const driver = getDriver(base);
|
||||
await driver.clear();
|
||||
},
|
||||
|
||||
removeMeta: async (key, properties) => {
|
||||
const { driver, driverKey } = resolveKey(key);
|
||||
await removeMeta(driver, driverKey, properties);
|
||||
},
|
||||
|
||||
snapshot: async (base, opts) => {
|
||||
const driver = getDriver(base);
|
||||
const data = await driver.snapshot();
|
||||
|
||||
opts?.excludeKeys?.forEach((key) => {
|
||||
delete data[key];
|
||||
delete data[getMetaKey(key)];
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
restoreSnapshot: async (base, data) => {
|
||||
const driver = getDriver(base);
|
||||
await driver.restoreSnapshot(data);
|
||||
},
|
||||
|
||||
watch: (key, cb) => {
|
||||
const { driver, driverKey } = resolveKey(key);
|
||||
return watch(driver, driverKey, cb);
|
||||
},
|
||||
|
||||
unwatch() {
|
||||
Object.values(drivers).forEach((driver) => {
|
||||
driver.unwatch();
|
||||
});
|
||||
},
|
||||
|
||||
defineItem: (key, opts?: WxtStorageItemOptions<any>) => {
|
||||
const { driver, driverKey } = resolveKey(key);
|
||||
|
||||
@@ -357,12 +402,14 @@ function createStorage(): WxtStorage {
|
||||
onMigrationComplete,
|
||||
debug = false,
|
||||
} = opts ?? {};
|
||||
|
||||
if (targetVersion < 1) {
|
||||
throw Error(
|
||||
'Storage item version cannot be less than 1. Initial versions should be set to 1, not 0.',
|
||||
);
|
||||
}
|
||||
const migrate = async () => {
|
||||
|
||||
const migrate: WxtStorageItem<any, any>['migrate'] = async () => {
|
||||
const driverMetaKey = getMetaKey(driverKey);
|
||||
const [{ value }, { value: meta }] = await driver.getItems([
|
||||
driverKey,
|
||||
@@ -376,11 +423,12 @@ function createStorage(): WxtStorage {
|
||||
`Version downgrade detected (v${currentVersion} -> v${targetVersion}) for "${key}"`,
|
||||
);
|
||||
}
|
||||
|
||||
if (currentVersion === targetVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (debug === true) {
|
||||
if (debug) {
|
||||
console.debug(
|
||||
`[@wxt-dev/storage] Running storage migration for ${key}: v${currentVersion} -> v${targetVersion}`,
|
||||
);
|
||||
@@ -395,7 +443,7 @@ function createStorage(): WxtStorage {
|
||||
migratedValue =
|
||||
(await migrations?.[migrateToVersion]?.(migratedValue)) ??
|
||||
migratedValue;
|
||||
if (debug === true) {
|
||||
if (debug) {
|
||||
console.debug(
|
||||
`[@wxt-dev/storage] Storage migration processed for version: v${migrateToVersion}`,
|
||||
);
|
||||
@@ -411,14 +459,16 @@ function createStorage(): WxtStorage {
|
||||
{ key: driverMetaKey, value: { ...meta, v: targetVersion } },
|
||||
]);
|
||||
|
||||
if (debug === true) {
|
||||
if (debug) {
|
||||
console.debug(
|
||||
`[@wxt-dev/storage] Storage migration completed for ${key} v${targetVersion}`,
|
||||
{ migratedValue },
|
||||
);
|
||||
}
|
||||
|
||||
onMigrationComplete?.(migratedValue, targetVersion);
|
||||
};
|
||||
|
||||
const migrationsDone =
|
||||
opts?.migrations == null
|
||||
? Promise.resolve()
|
||||
@@ -441,6 +491,9 @@ function createStorage(): WxtStorage {
|
||||
|
||||
const newValue = await opts.init();
|
||||
await driver.setItem<any>(driverKey, newValue);
|
||||
if (value == null && targetVersion > 1) {
|
||||
await setMeta(driver, driverKey, { v: targetVersion });
|
||||
}
|
||||
return newValue;
|
||||
});
|
||||
|
||||
@@ -449,62 +502,75 @@ function createStorage(): WxtStorage {
|
||||
|
||||
return {
|
||||
key,
|
||||
|
||||
get defaultValue() {
|
||||
return getFallback();
|
||||
},
|
||||
get fallback() {
|
||||
return getFallback();
|
||||
},
|
||||
|
||||
getValue: async () => {
|
||||
await migrationsDone;
|
||||
|
||||
if (opts?.init) {
|
||||
return await getOrInitValue();
|
||||
} else {
|
||||
return await getItem(driver, driverKey, opts);
|
||||
}
|
||||
},
|
||||
|
||||
getMeta: async () => {
|
||||
await migrationsDone;
|
||||
|
||||
return await getMeta(driver, driverKey);
|
||||
},
|
||||
|
||||
setValue: async (value) => {
|
||||
await migrationsDone;
|
||||
|
||||
return await setItem(driver, driverKey, value);
|
||||
},
|
||||
|
||||
setMeta: async (properties) => {
|
||||
await migrationsDone;
|
||||
|
||||
return await setMeta(driver, driverKey, properties);
|
||||
},
|
||||
|
||||
removeValue: async (opts) => {
|
||||
await migrationsDone;
|
||||
|
||||
return await removeItem(driver, driverKey, opts);
|
||||
},
|
||||
|
||||
removeMeta: async (properties) => {
|
||||
await migrationsDone;
|
||||
|
||||
return await removeMeta(driver, driverKey, properties);
|
||||
},
|
||||
|
||||
watch: (cb) =>
|
||||
watch(driver, driverKey, (newValue, oldValue) =>
|
||||
cb(newValue ?? getFallback(), oldValue ?? getFallback()),
|
||||
),
|
||||
|
||||
migrate,
|
||||
};
|
||||
},
|
||||
};
|
||||
return storage;
|
||||
}
|
||||
|
||||
function createDriver(storageArea: StorageArea): WxtStorageDriver {
|
||||
const getStorageArea = () => {
|
||||
if (browser.runtime == null) {
|
||||
throw Error(
|
||||
[
|
||||
"'wxt/storage' must be loaded in a web extension environment",
|
||||
'\n - If thrown during a build, see https://github.com/wxt-dev/wxt/issues/371',
|
||||
" - If thrown during tests, mock 'wxt/browser' correctly. See https://wxt.dev/guide/go-further/testing.html\n",
|
||||
].join('\n'),
|
||||
);
|
||||
throw Error(`'wxt/storage' must be loaded in a web extension environment
|
||||
|
||||
- If thrown during a build, see https://github.com/wxt-dev/wxt/issues/371
|
||||
- If thrown during tests, mock 'wxt/browser' correctly. See https://wxt.dev/guide/go-further/testing.html
|
||||
`);
|
||||
}
|
||||
|
||||
if (browser.storage == null) {
|
||||
throw Error(
|
||||
"You must add the 'storage' permission to your manifest to use 'wxt/storage'",
|
||||
@@ -514,18 +580,23 @@ function createDriver(storageArea: StorageArea): WxtStorageDriver {
|
||||
const area = browser.storage[storageArea];
|
||||
if (area == null)
|
||||
throw Error(`"browser.storage.${storageArea}" is undefined`);
|
||||
|
||||
return area;
|
||||
};
|
||||
|
||||
const watchListeners = new Set<(changes: StorageAreaChanges) => void>();
|
||||
|
||||
return {
|
||||
getItem: async (key) => {
|
||||
const res = await getStorageArea().get<Record<string, any>>(key);
|
||||
return res[key];
|
||||
},
|
||||
|
||||
getItems: async (keys) => {
|
||||
const result = await getStorageArea().get(keys);
|
||||
return keys.map((key) => ({ key, value: result[key] ?? null }));
|
||||
},
|
||||
|
||||
setItem: async (key, value) => {
|
||||
if (value == null) {
|
||||
await getStorageArea().remove(key);
|
||||
@@ -533,6 +604,7 @@ function createDriver(storageArea: StorageArea): WxtStorageDriver {
|
||||
await getStorageArea().set({ [key]: value });
|
||||
}
|
||||
},
|
||||
|
||||
setItems: async (values) => {
|
||||
const map = values.reduce<Record<string, unknown>>(
|
||||
(map, { key, value }) => {
|
||||
@@ -541,40 +613,51 @@ function createDriver(storageArea: StorageArea): WxtStorageDriver {
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
await getStorageArea().set(map);
|
||||
},
|
||||
|
||||
removeItem: async (key) => {
|
||||
await getStorageArea().remove(key);
|
||||
},
|
||||
|
||||
removeItems: async (keys) => {
|
||||
await getStorageArea().remove(keys);
|
||||
},
|
||||
|
||||
clear: async () => {
|
||||
await getStorageArea().clear();
|
||||
},
|
||||
|
||||
snapshot: async () => {
|
||||
return await getStorageArea().get();
|
||||
},
|
||||
|
||||
restoreSnapshot: async (data) => {
|
||||
await getStorageArea().set(data);
|
||||
},
|
||||
|
||||
watch(key, cb) {
|
||||
const listener = (changes: StorageAreaChanges) => {
|
||||
const change = changes[key] as {
|
||||
newValue?: any;
|
||||
oldValue?: any | null;
|
||||
} | null;
|
||||
if (change == null) return;
|
||||
if (dequal(change.newValue, change.oldValue)) return;
|
||||
|
||||
if (change == null || dequal(change.newValue, change.oldValue)) return;
|
||||
|
||||
cb(change.newValue ?? null, change.oldValue ?? null);
|
||||
};
|
||||
|
||||
getStorageArea().onChanged.addListener(listener);
|
||||
watchListeners.add(listener);
|
||||
|
||||
return () => {
|
||||
getStorageArea().onChanged.removeListener(listener);
|
||||
watchListeners.delete(listener);
|
||||
};
|
||||
},
|
||||
|
||||
unwatch() {
|
||||
watchListeners.forEach((listener) => {
|
||||
getStorageArea().onChanged.removeListener(listener);
|
||||
@@ -615,6 +698,7 @@ export interface WxtStorage {
|
||||
| { key: StorageItemKey; options?: GetItemOptions<any> }
|
||||
>,
|
||||
): Promise<Array<{ key: StorageItemKey; value: any }>>;
|
||||
|
||||
/**
|
||||
* Return an object containing metadata about the key. Object is stored at `key + "$"`. If value
|
||||
* is not an object, it returns an empty object.
|
||||
@@ -623,15 +707,17 @@ export interface WxtStorage {
|
||||
* await storage.getMeta("local:installDate");
|
||||
*/
|
||||
getMeta<T extends Record<string, unknown>>(key: StorageItemKey): Promise<T>;
|
||||
|
||||
/**
|
||||
* Get the metadata of multiple storage items.
|
||||
*
|
||||
* @param items List of keys or items to get the metadata of.
|
||||
* @param keys List of keys or items to get the metadata of.
|
||||
* @returns An array containing storage keys and their metadata.
|
||||
*/
|
||||
getMetas(
|
||||
keys: Array<StorageItemKey | WxtStorageItem<any, any>>,
|
||||
): Promise<Array<{ key: StorageItemKey; meta: any }>>;
|
||||
|
||||
/**
|
||||
* Set a value in storage. Setting a value to `null` or `undefined` is equivalent to calling
|
||||
* `removeItem`.
|
||||
@@ -640,6 +726,7 @@ export interface WxtStorage {
|
||||
* await storage.setItem<number>("local:installDate", Date.now());
|
||||
*/
|
||||
setItem<T>(key: StorageItemKey, value: T | null): Promise<void>;
|
||||
|
||||
/**
|
||||
* Set multiple values in storage. If a value is set to `null` or `undefined`, the key is removed.
|
||||
*
|
||||
@@ -655,6 +742,7 @@ export interface WxtStorage {
|
||||
| { item: WxtStorageItem<any, any>; value: any }
|
||||
>,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Sets metadata properties. If some properties are already set, but are not included in the
|
||||
* `properties` parameter, they will not be removed.
|
||||
@@ -666,10 +754,11 @@ export interface WxtStorage {
|
||||
key: StorageItemKey,
|
||||
properties: T | null,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Set the metadata of multiple storage items.
|
||||
*
|
||||
* @param items List of storage keys or items and metadata to set for each.
|
||||
* @param metas List of storage keys or items and metadata to set for each.
|
||||
*/
|
||||
setMetas(
|
||||
metas: Array<
|
||||
@@ -677,6 +766,7 @@ export interface WxtStorage {
|
||||
| { item: WxtStorageItem<any, any>; meta: Record<string, any> }
|
||||
>,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Removes an item from storage.
|
||||
*
|
||||
@@ -684,6 +774,7 @@ export interface WxtStorage {
|
||||
* await storage.removeItem("local:installDate");
|
||||
*/
|
||||
removeItem(key: StorageItemKey, opts?: RemoveItemOptions): Promise<void>;
|
||||
|
||||
/**
|
||||
* Remove a list of keys from storage.
|
||||
*/
|
||||
@@ -715,6 +806,7 @@ export interface WxtStorage {
|
||||
key: StorageItemKey,
|
||||
properties?: string | string[],
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Return all the items in storage.
|
||||
*/
|
||||
@@ -722,15 +814,18 @@ export interface WxtStorage {
|
||||
base: StorageArea,
|
||||
opts?: SnapshotOptions,
|
||||
): Promise<Record<string, unknown>>;
|
||||
|
||||
/**
|
||||
* Restores the results of `snapshot`. If new properties have been saved since the snapshot, they are
|
||||
* not overridden. Only values existing in the snapshot are overridden.
|
||||
*/
|
||||
restoreSnapshot(base: StorageArea, data: any): Promise<void>;
|
||||
|
||||
/**
|
||||
* Watch for changes to a specific key in storage.
|
||||
*/
|
||||
watch<T>(key: StorageItemKey, cb: WatchCallback<T | null>): Unwatch;
|
||||
|
||||
/**
|
||||
* Remove all watch listeners.
|
||||
*/
|
||||
@@ -786,42 +881,52 @@ export interface WxtStorageItem<
|
||||
* The storage key passed when creating the storage item.
|
||||
*/
|
||||
key: StorageItemKey;
|
||||
|
||||
/**
|
||||
* @deprecated Renamed to fallback, use it instead.
|
||||
*/
|
||||
defaultValue: TValue;
|
||||
|
||||
/**
|
||||
* The value provided by the `fallback` option.
|
||||
*/
|
||||
fallback: TValue;
|
||||
|
||||
/**
|
||||
* Get the latest value from storage.
|
||||
*/
|
||||
getValue(): Promise<TValue>;
|
||||
|
||||
/**
|
||||
* Get metadata.
|
||||
*/
|
||||
getMeta(): Promise<NullablePartial<TMetadata>>;
|
||||
|
||||
/**
|
||||
* Set the value in storage.
|
||||
*/
|
||||
setValue(value: TValue): Promise<void>;
|
||||
|
||||
/**
|
||||
* Set metadata properties.
|
||||
*/
|
||||
setMeta(properties: NullablePartial<TMetadata>): Promise<void>;
|
||||
|
||||
/**
|
||||
* Remove the value from storage.
|
||||
*/
|
||||
removeValue(opts?: RemoveItemOptions): Promise<void>;
|
||||
|
||||
/**
|
||||
* Remove all metadata or certain properties from metadata.
|
||||
*/
|
||||
removeMeta(properties?: string[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Listen for changes to the value in storage.
|
||||
*/
|
||||
watch(cb: WatchCallback<TValue>): Unwatch;
|
||||
|
||||
/**
|
||||
* If there are migrations defined on the storage item, migrate to the latest version.
|
||||
*
|
||||
@@ -839,6 +944,7 @@ export interface GetItemOptions<T> {
|
||||
* @deprecated Renamed to `fallback`, use it instead.
|
||||
*/
|
||||
defaultValue?: T;
|
||||
|
||||
/**
|
||||
* Default value returned when `getItem` would otherwise return `null`.
|
||||
*/
|
||||
@@ -867,10 +973,12 @@ export interface WxtStorageItemOptions<T> {
|
||||
* @deprecated Renamed to `fallback`, use it instead.
|
||||
*/
|
||||
defaultValue?: T;
|
||||
|
||||
/**
|
||||
* Default value returned when `getValue` would otherwise return `null`.
|
||||
*/
|
||||
fallback?: T;
|
||||
|
||||
/**
|
||||
* If passed, a value in storage will be initialized immediately after
|
||||
* defining the storage item. This function returns the value that will be
|
||||
@@ -878,20 +986,24 @@ export interface WxtStorageItemOptions<T> {
|
||||
* already exist.
|
||||
*/
|
||||
init?: () => T | Promise<T>;
|
||||
|
||||
/**
|
||||
* Provide a version number for the storage item to enable migrations. When changing the version
|
||||
* in the future, migration functions will be ran on application startup.
|
||||
*/
|
||||
version?: number;
|
||||
|
||||
/**
|
||||
* A map of version numbers to the functions used to migrate the data to that version.
|
||||
*/
|
||||
migrations?: Record<number, (oldValue: any) => any>;
|
||||
|
||||
/**
|
||||
* Print debug logs, such as migration process.
|
||||
* @default false
|
||||
*/
|
||||
debug?: boolean;
|
||||
|
||||
/**
|
||||
* A callback function that runs on migration complete.
|
||||
*/
|
||||
@@ -909,10 +1021,12 @@ export type StorageAreaChanges = {
|
||||
type NullablePartial<T> = {
|
||||
[key in keyof T]+?: T[key] | undefined | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback called when a value in storage is changed.
|
||||
*/
|
||||
export type WatchCallback<T> = (newValue: T, oldValue: T) => void;
|
||||
|
||||
/**
|
||||
* Call to remove a watch listener
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineConfig } from 'wxt';
|
||||
import { presetUno } from 'unocss';
|
||||
import { presetWind3 } from 'unocss';
|
||||
|
||||
export default defineConfig({
|
||||
srcDir: 'src',
|
||||
@@ -53,7 +53,7 @@ export default defineConfig({
|
||||
],
|
||||
},
|
||||
},
|
||||
presets: [presetUno()],
|
||||
presets: [presetWind3()],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,68 @@
|
||||
# Changelog
|
||||
|
||||
## v0.20.16
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.15...wxt-v0.20.16)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Revert "rename `name` to `names` prop of `rollup` to fix deprecated warning " ([#2106](https://github.com/wxt-dev/wxt/pull/2106))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Speed up E2E tests by skipping `pnpm install` when possible ([#2113](https://github.com/wxt-dev/wxt/pull/2113))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v0.20.15
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.14...wxt-v0.20.15)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Export `normalizePath` from `wxt` module ([#2080](https://github.com/wxt-dev/wxt/pull/2080))
|
||||
- **popup:** Add Firefox `default_area` and `theme_icons` support ([#2097](https://github.com/wxt-dev/wxt/pull/2097))
|
||||
- Add log `--level` cli option ([#1973](https://github.com/wxt-dev/wxt/pull/1973))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Content script is incorrectly invalidated when injected multiple times ([#2035](https://github.com/wxt-dev/wxt/pull/2035))
|
||||
- Use `uiContainer` to apply position when creating shadow root ui ([#2036](https://github.com/wxt-dev/wxt/pull/2036))
|
||||
- Make `ExtensionRunner#closeBrowser` optional ([#2092](https://github.com/wxt-dev/wxt/pull/2092))
|
||||
- Rename `name` to `names` prop of `rollup` to fix deprecated warning ([#2106](https://github.com/wxt-dev/wxt/pull/2106))
|
||||
|
||||
### 💅 Refactors
|
||||
|
||||
- Standardize file existence checks to `pathExists` ([#2083](https://github.com/wxt-dev/wxt/pull/2083))
|
||||
- Cleanup esm ([#1950](https://github.com/wxt-dev/wxt/pull/1950))
|
||||
- `async`/`await` cleanup ([#2088](https://github.com/wxt-dev/wxt/pull/2088))
|
||||
- Cleanup unnecssary checks ([#2089](https://github.com/wxt-dev/wxt/pull/2089))
|
||||
- Simplify import paths ([#2093](https://github.com/wxt-dev/wxt/pull/2093))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Fix JSDoc ([6ae5fb5e](https://github.com/wxt-dev/wxt/commit/6ae5fb5e))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Cleanup comment line lengths ([#2090](https://github.com/wxt-dev/wxt/pull/2090))
|
||||
- Cleanup `@ts-*` comments ([#2094](https://github.com/wxt-dev/wxt/pull/2094))
|
||||
- Update JSDoc to use `@internal` ([#2091](https://github.com/wxt-dev/wxt/pull/2091))
|
||||
- Fix typos ([#2086](https://github.com/wxt-dev/wxt/pull/2086))
|
||||
- Remove unused code ([#2087](https://github.com/wxt-dev/wxt/pull/2087))
|
||||
- Simplify imports of `cli/__tests__/index.ts` ([#2104](https://github.com/wxt-dev/wxt/pull/2104))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Nick Doan ([@nickbar01234](https://github.com/nickbar01234))
|
||||
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Wotan-allfather <wotan-ai@proton.me>
|
||||
- Namu ([@namuorg](https://github.com/namuorg))
|
||||
- Eli ([@lishaduck](https://github.com/lishaduck))
|
||||
|
||||
## v0.20.14
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.13...wxt-v0.20.14)
|
||||
@@ -31,7 +94,7 @@
|
||||
|
||||
- Dareka ([@darekadareka](https://github.com/darekadareka))
|
||||
- Mark ([@mjfaga](https://github.com/mjfaga))
|
||||
- Ariasuni <aria@ariasuni.eu>
|
||||
- ariasuni ([@ariasuni](https://github.com/ariasuni))
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
|
||||
- Bohuslavsemenov ([@bohuslavsemenov](https://github.com/bohuslavsemenov))
|
||||
|
||||
@@ -30,8 +30,8 @@ describe('Analysis', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(await project.fileExists('stats.html')).toBe(true);
|
||||
expect(await project.fileExists('.output/chrome-mv3/stats-0.json')).toBe(
|
||||
expect(await project.pathExists('stats.html')).toBe(true);
|
||||
expect(await project.pathExists('.output/chrome-mv3/stats-0.json')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
@@ -52,9 +52,9 @@ describe('Analysis', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(await project.fileExists('stats.html')).toBe(true);
|
||||
expect(await project.fileExists('stats-0.json')).toBe(true);
|
||||
expect(await project.fileExists('stats-1.json')).toBe(true);
|
||||
expect(await project.pathExists('stats.html')).toBe(true);
|
||||
expect(await project.pathExists('stats-0.json')).toBe(true);
|
||||
expect(await project.pathExists('stats-1.json')).toBe(true);
|
||||
});
|
||||
|
||||
it('should support customizing the stats output directory', async () => {
|
||||
@@ -73,7 +73,7 @@ describe('Analysis', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(await project.fileExists('stats/bundle.html')).toBe(true);
|
||||
expect(await project.pathExists('stats/bundle.html')).toBe(true);
|
||||
});
|
||||
|
||||
it('should place artifacts next to the custom output file', async () => {
|
||||
@@ -93,9 +93,9 @@ describe('Analysis', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(await project.fileExists('stats/bundle.html')).toBe(true);
|
||||
expect(await project.fileExists('stats/bundle-0.json')).toBe(true);
|
||||
expect(await project.fileExists('stats/bundle-1.json')).toBe(true);
|
||||
expect(await project.pathExists('stats/bundle.html')).toBe(true);
|
||||
expect(await project.pathExists('stats/bundle-0.json')).toBe(true);
|
||||
expect(await project.pathExists('stats/bundle-1.json')).toBe(true);
|
||||
});
|
||||
|
||||
it('should open the stats in the browser when requested', async () => {
|
||||
|
||||
@@ -142,7 +142,7 @@ describe('Auto Imports', () => {
|
||||
|
||||
await project.prepare();
|
||||
|
||||
expect(await project.fileExists('.wxt/types/imports.d.ts')).toBe(false);
|
||||
expect(await project.pathExists('.wxt/types/imports.d.ts')).toBe(false);
|
||||
});
|
||||
|
||||
it('should only include imports-module.d.ts in the the project', async () => {
|
||||
@@ -280,10 +280,10 @@ describe('Auto Imports', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(await project.fileExists('.wxt/eslint-auto-imports.mjs')).toBe(
|
||||
expect(await project.pathExists('.wxt/eslint-auto-imports.mjs')).toBe(
|
||||
false,
|
||||
);
|
||||
expect(await project.fileExists('.wxt/eslintrc-auto-import.json')).toBe(
|
||||
expect(await project.pathExists('.wxt/eslintrc-auto-import.json')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -77,7 +77,7 @@ describe('Module Helpers', () => {
|
||||
|
||||
await project.build(config);
|
||||
|
||||
expect(await project.fileExists('.output/chrome-mv3/injected.js')).toBe(
|
||||
expect(await project.pathExists('.output/chrome-mv3/injected.js')).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
@@ -118,10 +118,10 @@ describe('Module Helpers', () => {
|
||||
fileName: 'module.txt',
|
||||
});
|
||||
await expect(
|
||||
project.fileExists('.output/chrome-mv3/module.txt'),
|
||||
project.pathExists('.output/chrome-mv3/module.txt'),
|
||||
).resolves.toBe(true);
|
||||
await expect(
|
||||
project.fileExists('.output/chrome-mv3/example/generated.txt'),
|
||||
project.pathExists('.output/chrome-mv3/example/generated.txt'),
|
||||
).resolves.toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ describe('Output Directory Structure', () => {
|
||||
|
||||
await project.build({ browser: 'firefox' });
|
||||
|
||||
expect(await project.fileExists('.output/firefox-mv2/background.js')).toBe(
|
||||
expect(await project.pathExists('.output/firefox-mv2/background.js')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
@@ -146,7 +146,7 @@ describe('Output Directory Structure', () => {
|
||||
|
||||
await project.build({ browser: 'chrome' });
|
||||
|
||||
expect(await project.fileExists('.output/firefox-mv2/background.js')).toBe(
|
||||
expect(await project.pathExists('.output/firefox-mv2/background.js')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
@@ -174,7 +174,7 @@ describe('Output Directory Structure', () => {
|
||||
|
||||
await project.build();
|
||||
|
||||
expect(await project.fileExists('stats.html')).toBe(true);
|
||||
expect(await project.pathExists('stats.html')).toBe(true);
|
||||
});
|
||||
|
||||
it('should support JavaScript entrypoints', async () => {
|
||||
@@ -210,14 +210,14 @@ describe('Output Directory Structure', () => {
|
||||
----------------------------------------
|
||||
{"manifest_version":3,"name":"E2E Extension","description":"Example description","version":"0.0.0","background":{"service_worker":"background.js"},"content_scripts":[{"matches":["*://*.google.com/*"],"js":["content-scripts/content.js"]},{"matches":["*://*.duckduckgo.com/*"],"js":["content-scripts/named.js"]}]}"
|
||||
`);
|
||||
expect(await project.fileExists('.output/chrome-mv3/background.js'));
|
||||
expect(await project.pathExists('.output/chrome-mv3/background.js'));
|
||||
expect(
|
||||
await project.fileExists('.output/chrome-mv3/content-scripts/content.js'),
|
||||
await project.pathExists('.output/chrome-mv3/content-scripts/content.js'),
|
||||
);
|
||||
expect(
|
||||
await project.fileExists('.output/chrome-mv3/content-scripts/named.js'),
|
||||
await project.pathExists('.output/chrome-mv3/content-scripts/named.js'),
|
||||
);
|
||||
expect(await project.fileExists('.output/chrome-mv3/unlisted.js'));
|
||||
expect(await project.pathExists('.output/chrome-mv3/unlisted.js'));
|
||||
});
|
||||
|
||||
it('should support CSS entrypoints', async () => {
|
||||
@@ -300,7 +300,7 @@ describe('Output Directory Structure', () => {
|
||||
|
||||
await project.build();
|
||||
|
||||
expect(await project.fileExists('dist/chrome-mv3/manifest.json')).toBe(
|
||||
expect(await project.pathExists('dist/chrome-mv3/manifest.json')).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
@@ -335,7 +335,7 @@ describe('Output Directory Structure', () => {
|
||||
await project.build({
|
||||
vite: () => ({
|
||||
build: {
|
||||
// Make output for snapshot readible
|
||||
// Make output for the snapshot readable
|
||||
minify: false,
|
||||
},
|
||||
}),
|
||||
@@ -408,7 +408,7 @@ describe('Output Directory Structure', () => {
|
||||
await project.build({
|
||||
vite: () => ({
|
||||
build: {
|
||||
// Make output for snapshot readible
|
||||
// Make output for the snapshot readable
|
||||
minify: false,
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -31,7 +31,7 @@ describe('React', () => {
|
||||
await project.build();
|
||||
|
||||
expect(
|
||||
await project.fileExists('.output/chrome-mv3/content-scripts/demo.js'),
|
||||
await project.pathExists('.output/chrome-mv3/content-scripts/demo.js'),
|
||||
).toBe(true);
|
||||
expect(await project.serializeFile('.output/chrome-mv3/manifest.json'))
|
||||
.toMatchInlineSnapshot(`
|
||||
|
||||
@@ -20,7 +20,7 @@ describe('Remote Code', () => {
|
||||
);
|
||||
expect(output).not.toContain(url);
|
||||
expect(
|
||||
await project.fileExists(`.wxt/cache/${encodeURIComponent(url)}`),
|
||||
await project.pathExists(`.wxt/cache/${encodeURIComponent(url)}`),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('User Config', () => {
|
||||
await project.build({ configFile: 'test.config.ts' });
|
||||
|
||||
expect(
|
||||
await project.fileExists('.custom-output/chrome-mv3/background.js'),
|
||||
await project.pathExists('.custom-output/chrome-mv3/background.js'),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
@@ -129,13 +129,13 @@ describe('User Config', () => {
|
||||
await project.build();
|
||||
|
||||
expect(
|
||||
await project.fileExists('.output/test-chrome-mv3-production-build'),
|
||||
await project.pathExists('.output/test-chrome-mv3-production-build'),
|
||||
).toBe(true);
|
||||
|
||||
await project.build({ mode: 'development' });
|
||||
|
||||
expect(
|
||||
await project.fileExists('.output/test-chrome-mv3-development-dev-build'),
|
||||
await project.pathExists('.output/test-chrome-mv3-development-dev-build'),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ describe('Zipping', () => {
|
||||
browser: 'firefox',
|
||||
zip: { downloadPackages: ['flatten'] },
|
||||
});
|
||||
expect(await project.fileExists('.output/')).toBe(true);
|
||||
expect(await project.pathExists('.output/')).toBe(true);
|
||||
|
||||
await extract(sourcesZip, { dir: unzipDir });
|
||||
// Update package json wxt path
|
||||
@@ -51,7 +51,7 @@ describe('Zipping', () => {
|
||||
}),
|
||||
).resolves.not.toHaveProperty('exitCode');
|
||||
|
||||
await expect(project.fileExists(unzipDir, '.output')).resolves.toBe(true);
|
||||
await expect(project.pathExists(unzipDir, '.output')).resolves.toBe(true);
|
||||
expect(
|
||||
await project.serializeFile(
|
||||
project.resolvePath(unzipDir, 'package.json'),
|
||||
@@ -95,8 +95,8 @@ describe('Zipping', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(await project.fileExists(artifactZip)).toBe(true);
|
||||
expect(await project.fileExists(sourcesZip)).toBe(true);
|
||||
expect(await project.pathExists(artifactZip)).toBe(true);
|
||||
expect(await project.pathExists(sourcesZip)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not zip hidden files into sources by default', async () => {
|
||||
@@ -117,8 +117,8 @@ describe('Zipping', () => {
|
||||
browser: 'firefox',
|
||||
});
|
||||
await extract(sourcesZip, { dir: unzipDir });
|
||||
expect(await project.fileExists(unzipDir, '.env')).toBe(false);
|
||||
expect(await project.fileExists(unzipDir, '.hidden-dir/file')).toBe(false);
|
||||
expect(await project.pathExists(unzipDir, '.env')).toBe(false);
|
||||
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 () => {
|
||||
@@ -142,8 +142,8 @@ describe('Zipping', () => {
|
||||
},
|
||||
});
|
||||
await extract(sourcesZip, { dir: unzipDir });
|
||||
expect(await project.fileExists(unzipDir, '.hidden-dir/file')).toBe(false);
|
||||
expect(await project.fileExists(unzipDir, '.hidden-dir/nested/file')).toBe(
|
||||
expect(await project.pathExists(unzipDir, '.hidden-dir/file')).toBe(false);
|
||||
expect(await project.pathExists(unzipDir, '.hidden-dir/nested/file')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
@@ -171,12 +171,12 @@ describe('Zipping', () => {
|
||||
},
|
||||
});
|
||||
await extract(sourcesZip, { dir: unzipDir });
|
||||
expect(await project.fileExists(unzipDir, '.env')).toBe(true);
|
||||
expect(await project.fileExists(unzipDir, '.hidden-dir/file')).toBe(true);
|
||||
expect(await project.fileExists(unzipDir, '.hidden-dir/nested/file1')).toBe(
|
||||
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,
|
||||
);
|
||||
expect(await project.fileExists(unzipDir, '.hidden-dir/nested/file2')).toBe(
|
||||
expect(await project.pathExists(unzipDir, '.hidden-dir/nested/file2')).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
@@ -210,10 +210,10 @@ describe('Zipping', () => {
|
||||
});
|
||||
await extract(sourcesZip, { dir: unzipDir });
|
||||
expect(
|
||||
await project.fileExists(unzipDir, 'entrypoints/not-firefox.content.ts'),
|
||||
await project.pathExists(unzipDir, 'entrypoints/not-firefox.content.ts'),
|
||||
).toBe(false);
|
||||
expect(
|
||||
await project.fileExists(unzipDir, 'entrypoints/all.content.ts'),
|
||||
await project.pathExists(unzipDir, 'entrypoints/all.content.ts'),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
@@ -234,7 +234,7 @@ describe('Zipping', () => {
|
||||
browser,
|
||||
});
|
||||
|
||||
expect(await project.fileExists(sourcesZip)).toBe(true);
|
||||
expect(await project.pathExists(sourcesZip)).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -258,7 +258,7 @@ describe('Zipping', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(await project.fileExists(sourcesZip)).toBe(true);
|
||||
expect(await project.pathExists(sourcesZip)).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -282,7 +282,7 @@ describe('Zipping', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(await project.fileExists(sourcesZip)).toBe(false);
|
||||
expect(await project.pathExists(sourcesZip)).toBe(false);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -305,6 +305,6 @@ describe('Zipping', () => {
|
||||
});
|
||||
|
||||
await extract(sourcesZip, { dir: unzipDir });
|
||||
expect(await project.fileExists(unzipDir, 'manifest.json')).toBe(true);
|
||||
expect(await project.pathExists(unzipDir, 'manifest.json')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
+24
-19
@@ -1,7 +1,8 @@
|
||||
import { dirname, relative, resolve } from 'path';
|
||||
import fs, { mkdir } from 'fs-extra';
|
||||
import glob from 'fast-glob';
|
||||
import fs, { mkdir } from 'fs-extra';
|
||||
import merge from 'lodash.merge';
|
||||
import spawn from 'nano-spawn';
|
||||
import { dirname, relative, resolve } from 'path';
|
||||
import {
|
||||
InlineConfig,
|
||||
UserConfig,
|
||||
@@ -10,8 +11,7 @@ import {
|
||||
prepare,
|
||||
zip,
|
||||
} from '../src';
|
||||
import { normalizePath } from '../src/core/utils/paths';
|
||||
import merge from 'lodash.merge';
|
||||
import { normalizePath } from '../src/core/utils';
|
||||
|
||||
// Run "pnpm wxt" to use the "wxt" dev script, not the "wxt" binary from the
|
||||
// wxt package. This uses the TS files instead of the compiled JS package
|
||||
@@ -24,6 +24,7 @@ export class TestProject {
|
||||
files: Array<[string, string]> = [];
|
||||
config: UserConfig | undefined;
|
||||
readonly root: string;
|
||||
readonly hasCustomDependencies: boolean;
|
||||
|
||||
constructor(packageJson: any = {}) {
|
||||
// We can't put each test's project inside e2e/dist directly, otherwise the wxt.config.ts
|
||||
@@ -31,6 +32,13 @@ export class TestProject {
|
||||
// end to make each test's path unique.
|
||||
const id = Math.random().toString(32).substring(3);
|
||||
this.root = resolve(E2E_DIR, 'dist', id);
|
||||
|
||||
this.hasCustomDependencies =
|
||||
Object.keys({
|
||||
...packageJson.dependencies,
|
||||
...packageJson.devDependencies,
|
||||
}).length > 0;
|
||||
|
||||
this.files.push([
|
||||
'package.json',
|
||||
JSON.stringify(
|
||||
@@ -119,9 +127,14 @@ export class TestProject {
|
||||
await fs.writeFile(filePath, content ?? '', 'utf-8');
|
||||
}
|
||||
|
||||
await spawn('pnpm', ['--ignore-workspace', 'i', '--ignore-scripts'], {
|
||||
cwd: this.root,
|
||||
});
|
||||
// Only install dependencies if the project has custom ones - otherwise the
|
||||
// project will reuse the ones in `packages/wxt/node_modules`!
|
||||
if (this.hasCustomDependencies) {
|
||||
await spawn('pnpm', ['--ignore-workspace', 'i', '--ignore-scripts'], {
|
||||
cwd: this.root,
|
||||
});
|
||||
}
|
||||
|
||||
await mkdir(resolve(this.root, 'public'), { recursive: true }).catch(
|
||||
() => {},
|
||||
);
|
||||
@@ -138,14 +151,6 @@ export class TestProject {
|
||||
return this.serializeDir('.output', ignoreContentsOfFilenames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all the files from the test project's `.wxt` directory and combine them into a string
|
||||
* that can be used in a snapshot.
|
||||
*/
|
||||
serializeWxtDir(): Promise<string> {
|
||||
return this.serializeDir(resolve(this.root, '.wxt/types'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deeply print the filename and contents of all files in a directory.
|
||||
*
|
||||
@@ -185,13 +190,13 @@ export class TestProject {
|
||||
].join(`\n${''.padEnd(40, '-')}\n`);
|
||||
}
|
||||
|
||||
fileExists(...path: string[]): Promise<boolean> {
|
||||
return fs.exists(this.resolvePath(...path));
|
||||
pathExists(...path: string[]): Promise<boolean> {
|
||||
return fs.pathExists(this.resolvePath(...path));
|
||||
}
|
||||
|
||||
async getOutputManifest(
|
||||
getOutputManifest(
|
||||
path: string = '.output/chrome-mv3/manifest.json',
|
||||
): Promise<any> {
|
||||
return await fs.readJson(this.resolvePath(path));
|
||||
return fs.readJson(this.resolvePath(path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wxt",
|
||||
"type": "module",
|
||||
"version": "0.20.14",
|
||||
"version": "0.20.16",
|
||||
"description": "⚡ Next-gen Web Extension Framework",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -62,6 +62,14 @@
|
||||
"vite-node": "^3.2.4 || ^5.0.0",
|
||||
"web-ext-run": "^0.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"eslint": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^10.2.0",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
@@ -69,6 +77,7 @@
|
||||
"@types/node": "^20.17.6",
|
||||
"@types/normalize-path": "^3.0.2",
|
||||
"@types/prompts": "^2.4.9",
|
||||
"eslint": "^9.38.0",
|
||||
"extract-zip": "^2.0.1",
|
||||
"happy-dom": "^20.5.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
@@ -78,7 +87,6 @@
|
||||
"vitest": "^4.0.18",
|
||||
"vitest-plugin-random-seed": "^1.1.2"
|
||||
},
|
||||
"peerDependenciesMeta": {},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wxt-dev/wxt.git"
|
||||
|
||||
@@ -49,7 +49,7 @@ export default defineWxtModule({
|
||||
// Only create global types when user has enabled auto-imports
|
||||
entries.push(await getImportsDeclarationEntry(unimport));
|
||||
|
||||
if (wxt.config.imports.eslintrc.enabled === false) return;
|
||||
if (!wxt.config.imports.eslintrc.enabled) return;
|
||||
|
||||
// Only generate ESLint config if that feature is enabled
|
||||
entries.push(
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { describe, it, vi, beforeEach, expect } from 'vitest';
|
||||
import { build } from '../../core/build';
|
||||
import { createServer } from '../../core/create-server';
|
||||
import { zip } from '../../core/zip';
|
||||
import { prepare } from '../../core/prepare';
|
||||
import { clean } from '../../core/clean';
|
||||
import { initialize } from '../../core/initialize';
|
||||
import {
|
||||
build,
|
||||
createServer,
|
||||
zip,
|
||||
prepare,
|
||||
clean,
|
||||
initialize,
|
||||
} from '../../core';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import consola from 'consola';
|
||||
import consola, { LogLevels } from 'consola';
|
||||
|
||||
vi.mock('../../core/build');
|
||||
const buildMock = vi.mocked(build);
|
||||
@@ -137,6 +139,21 @@ describe('CLI', () => {
|
||||
expect(createServerMock).toBeCalledWith({
|
||||
debug: true,
|
||||
});
|
||||
expect(consola.level).toBe(LogLevels.debug);
|
||||
});
|
||||
|
||||
it('should set log --level', async () => {
|
||||
mockArgv('--level', 'warn');
|
||||
await importCli();
|
||||
|
||||
expect(consola.level).toBe(LogLevels.warn);
|
||||
});
|
||||
|
||||
it('--debug should override --level', async () => {
|
||||
mockArgv('--debug', '--level', 'silent');
|
||||
await importCli();
|
||||
|
||||
expect(consola.level).toBe(LogLevels.debug);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -229,6 +246,21 @@ describe('CLI', () => {
|
||||
expect(buildMock).toBeCalledWith({
|
||||
debug: true,
|
||||
});
|
||||
expect(consola.level).toBe(LogLevels.debug);
|
||||
});
|
||||
|
||||
it('should set log --level', async () => {
|
||||
mockArgv('build', '--level', 'warn');
|
||||
await importCli();
|
||||
|
||||
expect(consola.level).toBe(LogLevels.warn);
|
||||
});
|
||||
|
||||
it('--debug should override --level', async () => {
|
||||
mockArgv('build', '--debug', '--level', 'silent');
|
||||
await importCli();
|
||||
|
||||
expect(consola.level).toBe(LogLevels.debug);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -310,6 +342,21 @@ describe('CLI', () => {
|
||||
debug: true,
|
||||
zip: {},
|
||||
});
|
||||
expect(consola.level).toBe(LogLevels.debug);
|
||||
});
|
||||
|
||||
it('should set log --level', async () => {
|
||||
mockArgv('zip', '--level', 'warn');
|
||||
await importCli();
|
||||
|
||||
expect(consola.level).toBe(LogLevels.warn);
|
||||
});
|
||||
|
||||
it('--debug should override --level', async () => {
|
||||
mockArgv('zip', '--debug', '--level', 'silent');
|
||||
await importCli();
|
||||
|
||||
expect(consola.level).toBe(LogLevels.debug);
|
||||
});
|
||||
|
||||
it('should pass undefined for zipSources when --sources is not passed', async () => {
|
||||
@@ -379,6 +426,21 @@ describe('CLI', () => {
|
||||
expect(prepareMock).toBeCalledWith({
|
||||
debug: true,
|
||||
});
|
||||
expect(consola.level).toBe(LogLevels.debug);
|
||||
});
|
||||
|
||||
it('should set log --level', async () => {
|
||||
mockArgv('prepare', '--level', 'warn');
|
||||
await importCli();
|
||||
|
||||
expect(consola.level).toBe(LogLevels.warn);
|
||||
});
|
||||
|
||||
it('--debug should override --level', async () => {
|
||||
mockArgv('prepare', '--debug', '--level', 'silent');
|
||||
await importCli();
|
||||
|
||||
expect(consola.level).toBe(LogLevels.debug);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -413,6 +475,21 @@ describe('CLI', () => {
|
||||
expect(cleanMock).toBeCalledWith({
|
||||
debug: true,
|
||||
});
|
||||
expect(consola.level).toBe(LogLevels.debug);
|
||||
});
|
||||
|
||||
it('should set log --level', async () => {
|
||||
mockArgv('clean', '--level', 'warn');
|
||||
await importCli();
|
||||
|
||||
expect(consola.level).toBe(LogLevels.warn);
|
||||
});
|
||||
|
||||
it('--debug should override --level', async () => {
|
||||
mockArgv('clean', '--debug', '--level', 'silent');
|
||||
await importCli();
|
||||
|
||||
expect(consola.level).toBe(LogLevels.debug);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CAC, Command } from 'cac';
|
||||
import consola, { LogLevels } from 'consola';
|
||||
import consola, { LogLevels, LogType } from 'consola';
|
||||
import { filterTruthy, toArray } from '../core/utils/arrays';
|
||||
import { printHeader } from '../core/utils/log';
|
||||
import { formatDuration } from '../core/utils/time';
|
||||
@@ -19,8 +19,13 @@ export function wrapAction(
|
||||
},
|
||||
) {
|
||||
return async (...args: any[]) => {
|
||||
// Enable consola's debug mode globally at the start of all commands when the `--debug` flag is
|
||||
// passed
|
||||
const level: LogType | undefined = args.find((arg) => arg?.level)?.level;
|
||||
if (level && Object.keys(LogLevels).includes(level)) {
|
||||
consola.level = LogLevels[level];
|
||||
}
|
||||
|
||||
// Enable consola's debug mode globally at the start of all commands when
|
||||
// the `--debug` flag is passed
|
||||
const isDebug = !!args.find((arg) => arg?.debug);
|
||||
if (isDebug) {
|
||||
consola.level = LogLevels.debug;
|
||||
@@ -40,9 +45,7 @@ export function wrapAction(
|
||||
consola.fail(
|
||||
`Command failed after ${formatDuration(Date.now() - startTime)}`,
|
||||
);
|
||||
if (err instanceof ValidationError) {
|
||||
// Don't log these errors, they've already been logged
|
||||
} else {
|
||||
if (!(err instanceof ValidationError)) {
|
||||
consola.error(err);
|
||||
}
|
||||
process.exit(1);
|
||||
|
||||
@@ -9,6 +9,10 @@ import {
|
||||
const cli = cac('wxt');
|
||||
|
||||
cli.option('--debug', 'enable debug mode');
|
||||
cli.option(
|
||||
'--level <level>',
|
||||
'specify log level ("silent" | "fatal" | "error" | "warn" | "log" | "info" | "success" | "fail" | "ready" | "start" | "box" | "debug" | "trace" | "verbose")',
|
||||
);
|
||||
|
||||
// DEV
|
||||
cli
|
||||
|
||||
@@ -27,7 +27,7 @@ import { installSourcemapsSupport } from 'vite-node/source-map';
|
||||
import { createExtensionEnvironment } from '../../utils/environments';
|
||||
import { dirname, extname, join, relative } from 'node:path';
|
||||
import fs from 'fs-extra';
|
||||
import { normalizePath } from '../../utils/paths';
|
||||
import { normalizePath } from '../../utils';
|
||||
|
||||
export async function createViteBuilder(
|
||||
wxtConfig: ResolvedConfig,
|
||||
@@ -453,7 +453,7 @@ async function moveHtmlFiles(
|
||||
);
|
||||
|
||||
// TODO: Optimize and only delete old path directories
|
||||
removeEmptyDirs(config.outDir);
|
||||
await removeEmptyDirs(config.outDir);
|
||||
|
||||
return movedChunks;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
fakeDevServer,
|
||||
fakeResolvedConfig,
|
||||
} from '../../../../utils/testing/fake-objects';
|
||||
import { normalizePath } from '../../../../utils/paths';
|
||||
import { normalizePath } from '../../../../utils';
|
||||
import { resolve } from 'node:path';
|
||||
import { parseHTML } from 'linkedom';
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export function bundleAnalysis(config: ResolvedConfig): vite.Plugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated FOR TESTING ONLY.
|
||||
* @internal FOR TESTING ONLY.
|
||||
*/
|
||||
export function resetBundleIncrement() {
|
||||
increment = 0;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ResolvedConfig, WxtDevServer } from '../../../../types';
|
||||
import { getEntrypointName } from '../../../utils/entrypoints';
|
||||
import { parseHTML } from 'linkedom';
|
||||
import { dirname, relative, resolve } from 'node:path';
|
||||
import { normalizePath } from '../../../utils/paths';
|
||||
import { normalizePath } from '../../../utils';
|
||||
import { hash } from 'ohash';
|
||||
|
||||
// Stored outside the plugin to effect all instances of the devHtmlPrerender plugin.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Plugin } from 'vite';
|
||||
import { ResolvedConfig, WxtDevServer } from '../../../../types';
|
||||
import type { Plugin } from 'vite';
|
||||
import type { ResolvedConfig, WxtDevServer } from '../../../../types';
|
||||
|
||||
/**
|
||||
* Defines global constants about the dev server. Helps scripts connect to the server's web socket.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Plugin } from 'vite';
|
||||
import { ResolvedConfig } from '../../../../types';
|
||||
import type { Plugin } from 'vite';
|
||||
import type { ResolvedConfig } from '../../../../types';
|
||||
import { fetchCached } from '../../../utils/network';
|
||||
|
||||
/**
|
||||
@@ -15,12 +15,12 @@ export function download(config: ResolvedConfig): Plugin {
|
||||
resolveId(id) {
|
||||
if (id.startsWith('url:')) return '\0' + id;
|
||||
},
|
||||
async load(id) {
|
||||
load(id) {
|
||||
if (!id.startsWith('\0url:')) return;
|
||||
|
||||
// Load file from network or cache
|
||||
const url = id.replace('\0url:', '');
|
||||
return await fetchCached(url, config);
|
||||
return fetchCached(url, config);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export function extensionApiMock(config: ResolvedConfig): vite.PluginOption {
|
||||
],
|
||||
},
|
||||
ssr: {
|
||||
// Inline all WXT modules subdependencies can be mocked
|
||||
// Inline all WXT modules sub-dependencies can be mocked
|
||||
noExternal: ['wxt'],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Plugin } from 'vite';
|
||||
import type { Plugin } from 'vite';
|
||||
import { VIRTUAL_NOOP_BACKGROUND_MODULE_ID } from '../../../utils/constants';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ResolvedConfig } from '../../../../types';
|
||||
import * as vite from 'vite';
|
||||
import { normalizePath } from '../../../utils/paths';
|
||||
import type * as vite from 'vite';
|
||||
import { normalizePath } from '../../../utils';
|
||||
import { removeMainFunctionCode } from '../../../utils/transform';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { exists } from 'fs-extra';
|
||||
import { pathExists } from 'fs-extra';
|
||||
import { resolve } from 'node:path';
|
||||
import type * as vite from 'vite';
|
||||
import { ResolvedConfig } from '../../../../types';
|
||||
@@ -25,7 +25,7 @@ export function resolveAppConfig(config: ResolvedConfig): vite.Plugin {
|
||||
async resolveId(id) {
|
||||
if (id !== virtualModuleId) return;
|
||||
|
||||
return (await exists(appConfigFile))
|
||||
return (await pathExists(appConfigFile))
|
||||
? appConfigFile
|
||||
: resolvedVirtualModuleId;
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Plugin } from 'vite';
|
||||
import type { Plugin } from 'vite';
|
||||
import { ResolvedConfig } from '../../../../types';
|
||||
import { normalizePath } from '../../../utils/paths';
|
||||
import { normalizePath } from '../../../utils';
|
||||
import {
|
||||
VirtualModuleId,
|
||||
virtualModuleNames,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { parseHTML } from 'linkedom';
|
||||
import type * as vite from 'vite';
|
||||
import { normalizePath } from '../../../utils/paths';
|
||||
import { normalizePath } from '../../../utils';
|
||||
import { ResolvedConfig } from '../../../../types';
|
||||
|
||||
/**
|
||||
|
||||
@@ -126,7 +126,7 @@ async function createServerInternal(): Promise<WxtDevServer> {
|
||||
async stop() {
|
||||
wasStopped = true;
|
||||
keyboardShortcuts.stop();
|
||||
await runner.closeBrowser();
|
||||
await runner.closeBrowser?.();
|
||||
await builderServer.close();
|
||||
await wxt.hooks.callHook('server:closed', wxt, server);
|
||||
|
||||
@@ -150,7 +150,7 @@ async function createServerInternal(): Promise<WxtDevServer> {
|
||||
server.ws.send('wxt:reload-extension');
|
||||
},
|
||||
async restartBrowser() {
|
||||
await runner.closeBrowser();
|
||||
await runner.closeBrowser?.();
|
||||
keyboardShortcuts.stop();
|
||||
await wxt.reloadConfig();
|
||||
runner = await createExtensionRunner();
|
||||
|
||||
@@ -3,7 +3,7 @@ import fs from 'fs-extra';
|
||||
import { dirname, relative, resolve } from 'node:path';
|
||||
import { getEntrypointBundlePath, isHtmlEntrypoint } from './utils/entrypoints';
|
||||
import { getEntrypointGlobals, getGlobals } from './utils/globals';
|
||||
import { normalizePath } from './utils/paths';
|
||||
import { normalizePath } from './utils';
|
||||
import path from 'node:path';
|
||||
import { Message, parseI18nMessages } from './utils/i18n';
|
||||
import { writeFileIfDifferent, getPublicFiles } from './utils/fs';
|
||||
@@ -136,7 +136,7 @@ declare module "wxt/browser" {
|
||||
'messages.json',
|
||||
);
|
||||
let messages: Message[];
|
||||
if (await fs.exists(defaultLocalePath)) {
|
||||
if (await fs.pathExists(defaultLocalePath)) {
|
||||
const content = JSON.parse(await fs.readFile(defaultLocalePath, 'utf-8'));
|
||||
messages = parseI18nMessages(content);
|
||||
} else {
|
||||
@@ -166,7 +166,8 @@ declare module "wxt/browser" {
|
||||
};
|
||||
|
||||
const overrides = [
|
||||
// Generate individual overloads for each message so JSDoc contains description and base translation.
|
||||
// Generate individual overloads for each message so JSDoc contains
|
||||
// description and base translation.
|
||||
...messages.map((message) =>
|
||||
renderGetMessageOverload(
|
||||
`"${message.name}"`,
|
||||
@@ -174,7 +175,8 @@ declare module "wxt/browser" {
|
||||
message.message,
|
||||
),
|
||||
),
|
||||
// Include a final union-based override so TS accepts valid string templates or concatenations
|
||||
// Include a final union-based override so TS accepts valid string
|
||||
// templates or concatenations.
|
||||
// ie: browser.i18n.getMessage(`some_enum_${enumValue}`)
|
||||
renderGetMessageOverload(
|
||||
messages.map((message) => `"${message.name}"`).join(' | '),
|
||||
|
||||
@@ -6,3 +6,4 @@ export * from './create-server';
|
||||
export * from './initialize';
|
||||
export * from './prepare';
|
||||
export * from './zip';
|
||||
export * from './utils';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { beforeAll, describe, expect, it } from 'vitest';
|
||||
import path from 'node:path';
|
||||
import { npm } from '../npm';
|
||||
import spawn from 'nano-spawn';
|
||||
import { exists } from 'fs-extra';
|
||||
import { pathExists } from 'fs-extra';
|
||||
|
||||
describe('NPM Package Management Utils', () => {
|
||||
describe('listDependencies', () => {
|
||||
@@ -41,7 +41,7 @@ describe('NPM Package Management Utils', () => {
|
||||
const actual = await npm.downloadDependency(id, downloadDir);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
expect(await exists(actual)).toBe(true);
|
||||
expect(await pathExists(actual)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ import consola, { LogLevels } from 'consola';
|
||||
import defu from 'defu';
|
||||
import { NullablyRequired } from './utils/types';
|
||||
import fs from 'fs-extra';
|
||||
import { normalizePath } from './utils/paths';
|
||||
import { normalizePath } from './utils';
|
||||
import glob from 'fast-glob';
|
||||
import { builtinModules } from '../builtin-modules';
|
||||
import { getEslintVersion } from './utils/eslint';
|
||||
@@ -532,17 +532,10 @@ async function getUnimportEslintOptions(
|
||||
* Returns the path to `node_modules/wxt`.
|
||||
*/
|
||||
function resolveWxtModuleDir() {
|
||||
// TODO: Drop the __filename expression once we're fully running in ESM
|
||||
// (see https://github.com/wxt-dev/wxt/issues/277)
|
||||
const importer =
|
||||
typeof __filename === 'string'
|
||||
? pathToFileURL(__filename).href
|
||||
: import.meta.url;
|
||||
|
||||
// TODO: Switch to import.meta.resolve() once the parent argument is unflagged
|
||||
// (e.g. --experimental-import-meta-resolve) and all Node.js versions we support
|
||||
// have it.
|
||||
const url = esmResolve('wxt', importer);
|
||||
const url = esmResolve('wxt', import.meta.url);
|
||||
|
||||
// esmResolve() returns the "wxt/dist/index.mjs" file, not the package's root
|
||||
// directory, which we want to return from this function.
|
||||
@@ -550,7 +543,7 @@ function resolveWxtModuleDir() {
|
||||
}
|
||||
|
||||
async function isDirMissing(dir: string) {
|
||||
return !(await fs.exists(dir));
|
||||
return !(await fs.pathExists(dir));
|
||||
}
|
||||
|
||||
function logMissingDir(logger: Logger, name: string, expected: string) {
|
||||
@@ -580,8 +573,8 @@ export async function mergeBuilderConfig(
|
||||
if (vite) {
|
||||
return {
|
||||
vite: async (env) => {
|
||||
const resolvedInlineConfig = (await inlineConfig.vite?.(env)) ?? {};
|
||||
const resolvedUserConfig = (await userConfig.vite?.(env)) ?? {};
|
||||
const [resolvedInlineConfig = {}, resolvedUserConfig = {}] =
|
||||
await Promise.all([inlineConfig.vite?.(env), userConfig.vite?.(env)]);
|
||||
return vite.mergeConfig(resolvedUserConfig, resolvedInlineConfig);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('createExtensionRunner', () => {
|
||||
});
|
||||
|
||||
it('should return a WSL runner when `is-wsl` is true', async () => {
|
||||
isWslMock.mockResolvedValueOnce(true);
|
||||
isWslMock.mockReturnValueOnce(true);
|
||||
setFakeWxt({
|
||||
config: {
|
||||
browser: 'chrome',
|
||||
@@ -51,7 +51,7 @@ describe('createExtensionRunner', () => {
|
||||
});
|
||||
|
||||
it('should return a manual runner when `runner.disabled` is true', async () => {
|
||||
isWslMock.mockResolvedValueOnce(false);
|
||||
isWslMock.mockReturnValueOnce(false);
|
||||
setFakeWxt({
|
||||
config: {
|
||||
browser: 'chrome',
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { ExtensionRunner } from '../../types';
|
||||
import { createWslRunner } from './wsl';
|
||||
import { createWebExtRunner } from './web-ext';
|
||||
import { createSafariRunner } from './safari';
|
||||
import { createManualRunner } from './manual';
|
||||
import type { ExtensionRunner } from '../../types';
|
||||
import { isWsl } from '../utils/wsl';
|
||||
import { wxt } from '../wxt';
|
||||
import { createManualRunner } from './manual';
|
||||
import { createSafariRunner } from './safari';
|
||||
import { createWebExtRunner } from './web-ext';
|
||||
import { createWslRunner } from './wsl';
|
||||
|
||||
export async function createExtensionRunner(): Promise<ExtensionRunner> {
|
||||
if (wxt.config.browser === 'safari') return createSafariRunner();
|
||||
|
||||
if (await isWsl()) return createWslRunner();
|
||||
if (isWsl()) return createWslRunner();
|
||||
if (wxt.config.runnerConfig.config?.disabled) return createManualRunner();
|
||||
|
||||
return createWebExtRunner();
|
||||
|
||||
@@ -15,8 +15,5 @@ export function createManualRunner(): ExtensionRunner {
|
||||
)}" as an unpacked extension manually`,
|
||||
);
|
||||
},
|
||||
async closeBrowser() {
|
||||
// noop
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,8 +15,5 @@ export function createSafariRunner(): ExtensionRunner {
|
||||
)}" as an unpacked extension manually`,
|
||||
);
|
||||
},
|
||||
async closeBrowser() {
|
||||
// noop
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ export function createWebExtRunner(): ExtensionRunner {
|
||||
},
|
||||
|
||||
async closeBrowser() {
|
||||
return await runner?.exit();
|
||||
await runner?.exit();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,8 +15,5 @@ export function createWslRunner(): ExtensionRunner {
|
||||
)}" as an unpacked extension manually`,
|
||||
);
|
||||
},
|
||||
async closeBrowser() {
|
||||
// noop
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -106,6 +106,88 @@ describe('Manifest Utils', () => {
|
||||
expect(actual[expectedType]).toEqual(expected);
|
||||
},
|
||||
);
|
||||
|
||||
it('should include default_area for Firefox in mv3', async () => {
|
||||
const popup = fakePopupEntrypoint({
|
||||
options: {
|
||||
// @ts-expect-error: Force this to be undefined
|
||||
mv2Key: null,
|
||||
defaultArea: 'navbar',
|
||||
},
|
||||
outputDir: outDir,
|
||||
skipped: false,
|
||||
});
|
||||
const buildOutput = fakeBuildOutput();
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 3,
|
||||
outDir,
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[popup],
|
||||
buildOutput,
|
||||
);
|
||||
|
||||
expect((actual.action as any).default_area).toBe('navbar');
|
||||
});
|
||||
|
||||
it('should include theme_icons for Firefox in mv3', async () => {
|
||||
const themeIcons = [
|
||||
{ light: '/icon-light-16.png', dark: '/icon-dark-16.png', size: 16 },
|
||||
{ light: '/icon-light-32.png', dark: '/icon-dark-32.png', size: 32 },
|
||||
];
|
||||
const popup = fakePopupEntrypoint({
|
||||
options: {
|
||||
// @ts-expect-error: Force this to be undefined
|
||||
mv2Key: null,
|
||||
themeIcons,
|
||||
},
|
||||
outputDir: outDir,
|
||||
skipped: false,
|
||||
});
|
||||
const buildOutput = fakeBuildOutput();
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 3,
|
||||
outDir,
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[popup],
|
||||
buildOutput,
|
||||
);
|
||||
|
||||
expect((actual.action as any).theme_icons).toEqual(themeIcons);
|
||||
});
|
||||
|
||||
it('should include default_area for Firefox in mv2', async () => {
|
||||
const popup = fakePopupEntrypoint({
|
||||
options: {
|
||||
// @ts-expect-error: Force this to be undefined
|
||||
mv2Key: null,
|
||||
defaultArea: 'menupanel',
|
||||
},
|
||||
outputDir: outDir,
|
||||
skipped: false,
|
||||
});
|
||||
const buildOutput = fakeBuildOutput();
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 2,
|
||||
outDir,
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[popup],
|
||||
buildOutput,
|
||||
);
|
||||
|
||||
expect((actual.browser_action as any).default_area).toBe('menupanel');
|
||||
});
|
||||
});
|
||||
|
||||
describe('action without popup', () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { DevModeChange, detectDevChanges } from '../../../utils/building';
|
||||
import { DevModeChange, detectDevChanges } from '../detect-dev-changes';
|
||||
import {
|
||||
fakeBackgroundEntrypoint,
|
||||
fakeContentScriptEntrypoint,
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
fakeOutputChunk,
|
||||
fakeWxt,
|
||||
setFakeWxt,
|
||||
} from '../../../utils/testing/fake-objects';
|
||||
} from '../../testing/fake-objects';
|
||||
import { BuildOutput, BuildStepOutput } from '../../../../types';
|
||||
import { setWxtForTesting } from '../../../wxt';
|
||||
|
||||
|
||||
@@ -13,11 +13,8 @@ import { resolve } from 'path';
|
||||
import { findEntrypoints } from '../find-entrypoints';
|
||||
import fs from 'fs-extra';
|
||||
import glob from 'fast-glob';
|
||||
import {
|
||||
fakeResolvedConfig,
|
||||
setFakeWxt,
|
||||
} from '../../../utils/testing/fake-objects';
|
||||
import { unnormalizePath } from '../../../utils/paths';
|
||||
import { fakeResolvedConfig, setFakeWxt } from '../../testing/fake-objects';
|
||||
import { unnormalizePath } from '../../paths';
|
||||
import { wxt } from '../../../wxt';
|
||||
|
||||
vi.mock('fast-glob');
|
||||
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
EntrypointGroup,
|
||||
OutputFile,
|
||||
} from '../../../types';
|
||||
import { every, some } from '../../utils/arrays';
|
||||
import { normalizePath } from '../../utils/paths';
|
||||
import { every, some } from '../arrays';
|
||||
import { normalizePath } from '../paths';
|
||||
import { wxt } from '../../wxt';
|
||||
|
||||
/**
|
||||
@@ -207,10 +207,6 @@ interface ExtensionReload extends RebuildChange {
|
||||
type: 'extension-reload';
|
||||
}
|
||||
|
||||
// interface BrowserRestart extends RebuildChange {
|
||||
// type: 'browser-restart';
|
||||
// }
|
||||
|
||||
interface ContentScriptReload extends RebuildChange {
|
||||
type: 'content-script-reload';
|
||||
changedSteps: BuildStepOutput[];
|
||||
|
||||
@@ -21,9 +21,9 @@ import {
|
||||
isHtmlEntrypoint,
|
||||
isJsEntrypoint,
|
||||
resolvePerBrowserOptions,
|
||||
} from '../../utils/entrypoints';
|
||||
import { VIRTUAL_NOOP_BACKGROUND_MODULE_ID } from '../../utils/constants';
|
||||
import { CSS_EXTENSIONS_PATTERN } from '../../utils/paths';
|
||||
} from '../entrypoints';
|
||||
import { VIRTUAL_NOOP_BACKGROUND_MODULE_ID } from '../constants';
|
||||
import { CSS_EXTENSIONS_PATTERN } from '../paths';
|
||||
import pc from 'picocolors';
|
||||
import { wxt } from '../../wxt';
|
||||
import { camelCase } from 'scule';
|
||||
@@ -77,9 +77,8 @@ export async function findEntrypoints(): Promise<Entrypoint[]> {
|
||||
(entry) =>
|
||||
entry.name === name && entry.inputPath.endsWith('index.html'),
|
||||
);
|
||||
if (hasIndexHtml) return false;
|
||||
|
||||
return true;
|
||||
return !hasIndexHtml;
|
||||
});
|
||||
|
||||
await wxt.hooks.callHook('entrypoints:found', wxt, entrypointInfos);
|
||||
@@ -184,8 +183,7 @@ async function importEntrypoints(infos: EntrypointInfo[]) {
|
||||
await Promise.all([
|
||||
// HTML
|
||||
...htmlInfos.map(async (info) => {
|
||||
const res = await importHtmlEntrypoint(info);
|
||||
resMap[info.inputPath] = res;
|
||||
resMap[info.inputPath] = await importHtmlEntrypoint(info);
|
||||
}),
|
||||
// JS
|
||||
(async () => {
|
||||
@@ -268,7 +266,7 @@ async function getPopupEntrypoint(
|
||||
info: EntrypointInfo,
|
||||
options: Record<string, any>,
|
||||
): Promise<PopupEntrypoint> {
|
||||
const stictOptions: PopupEntrypoint['options'] = resolvePerBrowserOptions(
|
||||
const strictOptions: PopupEntrypoint['options'] = resolvePerBrowserOptions(
|
||||
{
|
||||
browserStyle: options.browserStyle,
|
||||
exclude: options.exclude,
|
||||
@@ -276,16 +274,20 @@ async function getPopupEntrypoint(
|
||||
defaultIcon: options.defaultIcon,
|
||||
defaultTitle: options.title,
|
||||
mv2Key: options.type,
|
||||
defaultArea: options.defaultArea,
|
||||
},
|
||||
wxt.config.browser,
|
||||
);
|
||||
if (stictOptions.mv2Key && stictOptions.mv2Key !== 'page_action')
|
||||
stictOptions.mv2Key = 'browser_action';
|
||||
if (strictOptions.mv2Key && strictOptions.mv2Key !== 'page_action')
|
||||
strictOptions.mv2Key = 'browser_action';
|
||||
|
||||
// themeIcons is an array of objects, not a per-browser option
|
||||
const themeIcons = options.themeIcons;
|
||||
|
||||
return {
|
||||
type: 'popup',
|
||||
name: 'popup',
|
||||
options: stictOptions,
|
||||
options: { ...strictOptions, themeIcons },
|
||||
inputPath: info.inputPath,
|
||||
outputDir: wxt.config.outDir,
|
||||
};
|
||||
|
||||
@@ -3,10 +3,10 @@ import { BuildOutput, Entrypoint } from '../../../types';
|
||||
import pc from 'picocolors';
|
||||
import fs from 'fs-extra';
|
||||
import { groupEntrypoints } from './group-entrypoints';
|
||||
import { formatDuration } from '../../utils/time';
|
||||
import { printBuildSummary } from '../../utils/log';
|
||||
import { formatDuration } from '../time';
|
||||
import { printBuildSummary } from '../log';
|
||||
import glob from 'fast-glob';
|
||||
import { unnormalizePath } from '../../utils/paths';
|
||||
import { unnormalizePath } from '../paths';
|
||||
import { rebuild } from './rebuild';
|
||||
import { relative } from 'node:path';
|
||||
import {
|
||||
@@ -87,7 +87,7 @@ export async function internalBuild(): Promise<BuildOutput> {
|
||||
} else {
|
||||
wxt.logger.info(`Opening ${pc.yellow(statsPath)} in browser...`);
|
||||
const { default: open } = await import('open');
|
||||
open(wxt.config.analysis.outputFile);
|
||||
await open(wxt.config.analysis.outputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,12 +133,12 @@ function printValidationResults({
|
||||
}, new Map<Entrypoint, ValidationResult[]>());
|
||||
|
||||
Array.from(entrypointErrors.entries()).forEach(([entrypoint, errors]) => {
|
||||
wxt.logger.log(relative(cwd, entrypoint.inputPath));
|
||||
console.log();
|
||||
wxt.logger.log(relative(cwd, entrypoint.inputPath) + '\n');
|
||||
|
||||
errors.forEach((err) => {
|
||||
const type = err.type === 'error' ? pc.red('ERROR') : pc.yellow('WARN');
|
||||
const recieved = pc.dim(`(recieved: ${JSON.stringify(err.value)})`);
|
||||
wxt.logger.log(` - ${type} ${err.message} ${recieved}`);
|
||||
const received = pc.dim(`(received: ${JSON.stringify(err.value)})`);
|
||||
wxt.logger.log(` - ${type} ${err.message} ${received}`);
|
||||
});
|
||||
console.log();
|
||||
});
|
||||
|
||||
@@ -9,8 +9,8 @@ export function defineCommand<TArgs extends any[]>(
|
||||
},
|
||||
) {
|
||||
return async (...args: TArgs) => {
|
||||
// Enable consola's debug mode globally at the start of all commands when the `--debug` flag is
|
||||
// passed
|
||||
// Enable consola's debug mode globally at the start of all commands when
|
||||
// the `--debug` flag is passed
|
||||
const isDebug = !!args.find((arg) => arg?.debug);
|
||||
if (isDebug) {
|
||||
consola.level = LogLevels.debug;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
export async function getEslintVersion(): Promise<string[]> {
|
||||
try {
|
||||
const require = (await import('node:module')).default.createRequire(
|
||||
import.meta.url,
|
||||
);
|
||||
const { ESLint } = require('eslint');
|
||||
const { ESLint } = await import('eslint');
|
||||
return ESLint.version?.split('.') ?? [];
|
||||
} catch {
|
||||
// Return an empty version when there's an error importing ESLint
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function writeFileIfDifferent(
|
||||
* `config.publicDir`.
|
||||
*/
|
||||
export async function getPublicFiles(): Promise<string[]> {
|
||||
if (!(await fs.exists(wxt.config.publicDir))) return [];
|
||||
if (!(await fs.pathExists(wxt.config.publicDir))) return [];
|
||||
|
||||
const files = await glob('**/*', { cwd: wxt.config.publicDir });
|
||||
return files.map(unnormalizePath);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { normalizePath } from './paths';
|
||||
@@ -3,6 +3,5 @@ import { version } from '../../../version';
|
||||
import { consola } from 'consola';
|
||||
|
||||
export function printHeader() {
|
||||
console.log();
|
||||
consola.log(`${pc.gray('WXT')} ${pc.gray(pc.bold(version))}`);
|
||||
consola.log(`\n${pc.gray('WXT')} ${pc.gray(pc.bold(version))}`);
|
||||
}
|
||||
|
||||
@@ -281,6 +281,12 @@ function addEntrypoints(
|
||||
if (popup.options.browserStyle)
|
||||
// @ts-expect-error: Not typed by @wxt-dev/browser, but supported by Firefox
|
||||
options.browser_style = popup.options.browserStyle;
|
||||
if (popup.options.defaultArea)
|
||||
// @ts-expect-error: Not typed by @wxt-dev/browser, but supported by Firefox
|
||||
options.default_area = popup.options.defaultArea;
|
||||
if (popup.options.themeIcons)
|
||||
// @ts-expect-error: Not typed by @wxt-dev/browser, but supported by Firefox
|
||||
options.theme_icons = popup.options.themeIcons;
|
||||
if (manifest.manifest_version === 3) {
|
||||
manifest.action = {
|
||||
...manifest.action,
|
||||
@@ -459,13 +465,13 @@ function discoverIcons(
|
||||
}
|
||||
|
||||
function addDevModeCsp(manifest: Browser.runtime.Manifest): void {
|
||||
let permissonUrl = wxt.server?.origin;
|
||||
if (permissonUrl) {
|
||||
const permissionUrlInstance = new URL(permissonUrl);
|
||||
let permissionUrl = wxt.server?.origin;
|
||||
if (permissionUrl) {
|
||||
const permissionUrlInstance = new URL(permissionUrl);
|
||||
permissionUrlInstance.port = '';
|
||||
permissonUrl = permissionUrlInstance.toString();
|
||||
permissionUrl = permissionUrlInstance.toString();
|
||||
}
|
||||
const permission = `${permissonUrl}*`;
|
||||
const permission = `${permissionUrl}*`;
|
||||
const allowedCsp = wxt.server?.origin ?? 'http://localhost:*';
|
||||
|
||||
if (manifest.manifest_version === 3) {
|
||||
@@ -669,7 +675,7 @@ function convertCspToMv2(manifest: Browser.runtime.Manifest): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure all resources are in MV3 format. If not, add a wanring
|
||||
* Make sure all resources are in MV3 format. If not, add a warning.
|
||||
*/
|
||||
function validateMv3WebAccessibleResources(
|
||||
manifest: Browser.runtime.Manifest,
|
||||
|
||||
@@ -4,13 +4,7 @@ import { withTimeout } from './time';
|
||||
|
||||
function isOffline(): Promise<boolean> {
|
||||
const isOffline = new Promise<boolean>((res) => {
|
||||
dns.resolve('google.com', (err) => {
|
||||
if (err == null) {
|
||||
res(false);
|
||||
} else {
|
||||
res(true);
|
||||
}
|
||||
});
|
||||
dns.resolve('google.com', (err) => res(err != null));
|
||||
});
|
||||
return withTimeout(isOffline, 1e3).catch(() => true);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import systemPath from 'node:path';
|
||||
import normalize from 'normalize-path';
|
||||
|
||||
/**
|
||||
* Converts system paths to normalized bundler path. On windows and unix, this returns paths with /
|
||||
* instead of \.
|
||||
* Converts system paths to normalized bundler path.
|
||||
* On Windows, this returns paths with `/` instead of `\`.
|
||||
*/
|
||||
export function normalizePath(path: string): string {
|
||||
return normalize(path);
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
OptionsEntrypoint,
|
||||
PopupEntrypoint,
|
||||
OutputChunk,
|
||||
OutputFile,
|
||||
OutputAsset,
|
||||
BuildOutput,
|
||||
BuildStepOutput,
|
||||
@@ -25,7 +24,7 @@ import {
|
||||
} from '../../../types';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { vi } from 'vitest';
|
||||
import { setWxtForTesting } from '../../../core/wxt';
|
||||
import { setWxtForTesting } from '../../wxt';
|
||||
import type { Browser } from '@wxt-dev/browser';
|
||||
|
||||
faker.seed(import.meta.test.SEED);
|
||||
@@ -146,6 +145,10 @@ export const fakePopupEntrypoint = fakeObjectCreator<PopupEntrypoint>(() => ({
|
||||
'page_action',
|
||||
undefined,
|
||||
]),
|
||||
// Firefox-specific options - kept undefined by default to avoid breaking existing tests
|
||||
browserStyle: undefined,
|
||||
defaultArea: undefined,
|
||||
themeIcons: undefined,
|
||||
},
|
||||
skipped: faker.datatype.boolean(),
|
||||
}));
|
||||
@@ -204,10 +207,6 @@ export const fakeOutputAsset = fakeObjectCreator<OutputAsset>(() => ({
|
||||
fileName: fakeFileName(),
|
||||
}));
|
||||
|
||||
export function fakeOutputFile(): OutputFile {
|
||||
return faker.helpers.arrayElement([fakeOutputAsset(), fakeOutputChunk()]);
|
||||
}
|
||||
|
||||
export const fakeManifest = fakeObjectCreator<Browser.runtime.Manifest>(() => ({
|
||||
manifest_version: faker.helpers.arrayElement([2, 3]),
|
||||
name: faker.string.alphanumeric(),
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// TODO: Someone smarter than me should just mock this module instead.
|
||||
import isWsl_ from 'is-wsl';
|
||||
|
||||
/**
|
||||
* Returns true when running on WSL or WSL2.
|
||||
*/
|
||||
export async function isWsl(): Promise<boolean> {
|
||||
const { default: isWsl } = await import('is-wsl'); // ESM only, requires dynamic import
|
||||
return isWsl;
|
||||
export function isWsl(): boolean {
|
||||
return isWsl_;
|
||||
}
|
||||
|
||||
@@ -26,8 +26,10 @@ export async function registerWxt(
|
||||
|
||||
const hooks = createHooks<WxtHooks>();
|
||||
const config = await resolveConfig(inlineConfig, command);
|
||||
const builder = await createViteBuilder(config, hooks, () => wxt.server);
|
||||
const pm = await createWxtPackageManager(config.root);
|
||||
const [builder, pm] = await Promise.all([
|
||||
createViteBuilder(config, hooks, () => wxt.server),
|
||||
createWxtPackageManager(config.root),
|
||||
]);
|
||||
|
||||
wxt = {
|
||||
config,
|
||||
|
||||
@@ -4,12 +4,12 @@ import fs from 'fs-extra';
|
||||
import { safeFilename } from './utils/strings';
|
||||
import { getPackageJson } from './utils/package';
|
||||
import { formatDuration } from './utils/time';
|
||||
import { printFileList } from './utils/log/printFileList';
|
||||
import { printFileList } from './utils/log';
|
||||
import { findEntrypoints, internalBuild } from './utils/building';
|
||||
import { registerWxt, wxt } from './wxt';
|
||||
import JSZip from 'jszip';
|
||||
import glob from 'fast-glob';
|
||||
import { normalizePath } from './utils/paths';
|
||||
import { normalizePath } from './utils';
|
||||
import { minimatchMultiple } from './utils/minimatch-multiple';
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
WxtModuleOptions,
|
||||
WxtModuleSetup,
|
||||
} from './types';
|
||||
import * as vite from 'vite';
|
||||
import type * as vite from 'vite';
|
||||
import glob from 'fast-glob';
|
||||
import { resolve } from 'node:path';
|
||||
import type { UnimportOptions } from 'unimport';
|
||||
@@ -109,7 +109,10 @@ export function addViteConfig(
|
||||
wxt.hooks.hook('config:resolved', (wxt) => {
|
||||
const userVite = wxt.config.vite;
|
||||
wxt.config.vite = async (env) => {
|
||||
const fromUser = await userVite(env);
|
||||
const [vite, fromUser] = await Promise.all([
|
||||
import('vite'),
|
||||
userVite(env),
|
||||
]);
|
||||
const fromModule = viteConfig(env) ?? {};
|
||||
return vite.mergeConfig(fromModule, fromUser);
|
||||
};
|
||||
@@ -167,11 +170,8 @@ export function addImportPreset(
|
||||
preset: UnimportOptions['presets'][0],
|
||||
): void {
|
||||
wxt.hooks.hook('config:resolved', (wxt) => {
|
||||
// In older versions of WXT, `wxt.config.imports` could be false
|
||||
if (!wxt.config.imports) return;
|
||||
|
||||
wxt.config.imports.presets ??= [];
|
||||
// De-dupelicate built-in named presets
|
||||
// De-duplicate built-in named presets
|
||||
if (wxt.config.imports.presets.includes(preset)) return;
|
||||
|
||||
wxt.config.imports.presets.push(preset);
|
||||
|
||||
@@ -21,7 +21,7 @@ export interface InlineConfig {
|
||||
* Directory containing all source code. Set to `"src"` to move all source code to a `src/`
|
||||
* directory.
|
||||
*
|
||||
* After changing, don't forget to move the `public/` and `entrypoints/` directories into the new
|
||||
* After changing, remember to move the `public/` and `entrypoints/` directories into the new
|
||||
* source dir.
|
||||
*
|
||||
* @default config.root
|
||||
@@ -648,6 +648,19 @@ export interface IsolatedWorldContentScriptEntrypointOptions extends BaseContent
|
||||
world?: 'ISOLATED';
|
||||
}
|
||||
|
||||
/**
|
||||
* Firefox theme icon definition for light/dark mode support.
|
||||
* @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_action#theme_icons
|
||||
*/
|
||||
export interface ThemeIcon {
|
||||
/** Path to the icon shown when the browser uses a light theme. */
|
||||
light: string;
|
||||
/** Path to the icon shown when the browser uses a dark theme. */
|
||||
dark: string;
|
||||
/** Icon size in pixels. */
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface PopupEntrypointOptions extends BaseEntrypointOptions {
|
||||
/**
|
||||
* Defaults to "browser_action" to be equivalent to MV3's "action" key
|
||||
@@ -656,6 +669,18 @@ export interface PopupEntrypointOptions extends BaseEntrypointOptions {
|
||||
defaultIcon?: Record<string, string>;
|
||||
defaultTitle?: PerBrowserOption<string>;
|
||||
browserStyle?: PerBrowserOption<boolean>;
|
||||
/**
|
||||
* Firefox only. Defines the part of the browser in which the button is initially placed.
|
||||
* @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/action#default_area
|
||||
*/
|
||||
defaultArea?: PerBrowserOption<
|
||||
'navbar' | 'menupanel' | 'tabstrip' | 'personaltoolbar'
|
||||
>;
|
||||
/**
|
||||
* Firefox only. Icons for light and dark themes.
|
||||
* @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/action#theme_icons
|
||||
*/
|
||||
themeIcons?: ThemeIcon[];
|
||||
}
|
||||
|
||||
export interface OptionsEntrypointOptions extends BaseEntrypointOptions {
|
||||
@@ -1403,7 +1428,9 @@ export interface FsCache {
|
||||
|
||||
export interface ExtensionRunner {
|
||||
openBrowser(): Promise<void>;
|
||||
closeBrowser(): Promise<void>;
|
||||
|
||||
closeBrowser?(): Promise<void>;
|
||||
|
||||
/** Whether or not this runner actually opens the browser. */
|
||||
canOpen?(): boolean;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('Content Script Context', () => {
|
||||
const onInvalidated = vi.fn();
|
||||
|
||||
ctx.onInvalidated(onInvalidated);
|
||||
// @ts-ignore
|
||||
// @ts-expect-error Deleting `runtime.id` to simulate disconnection
|
||||
delete fakeBrowser.runtime.id;
|
||||
const isValid = ctx.isValid;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @module wxt/utils/content-script-context */
|
||||
import { ContentScriptDefinition } from '../types';
|
||||
import { browser } from 'wxt/browser';
|
||||
import { logger } from '../utils/internal/logger';
|
||||
import { logger } from './internal/logger';
|
||||
import {
|
||||
WxtLocationChangeEvent,
|
||||
getUniqueEventName,
|
||||
@@ -43,23 +43,19 @@ export class ContentScriptContext implements AbortController {
|
||||
'wxt:content-script-started',
|
||||
);
|
||||
|
||||
private isTopFrame = window.self === window.top;
|
||||
private id: string;
|
||||
private abortController: AbortController;
|
||||
private locationWatcher = createLocationWatcher(this);
|
||||
private receivedMessageIds = new Set<string>();
|
||||
|
||||
constructor(
|
||||
private readonly contentScriptName: string,
|
||||
public readonly options?: Omit<ContentScriptDefinition, 'main'>,
|
||||
) {
|
||||
this.id = Math.random().toString(36).slice(2);
|
||||
this.abortController = new AbortController();
|
||||
|
||||
if (this.isTopFrame) {
|
||||
this.listenForNewerScripts({ ignoreFirstEvent: true });
|
||||
this.stopOldScripts();
|
||||
} else {
|
||||
this.listenForNewerScripts();
|
||||
}
|
||||
this.stopOldScripts();
|
||||
this.listenForNewerScripts();
|
||||
}
|
||||
|
||||
get signal() {
|
||||
@@ -243,43 +239,56 @@ export class ContentScriptContext implements AbortController {
|
||||
}
|
||||
|
||||
stopOldScripts() {
|
||||
// Use postMessage so it get's sent to all the frames of the page.
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(ContentScriptContext.SCRIPT_STARTED_MESSAGE_TYPE, {
|
||||
detail: {
|
||||
contentScriptName: this.contentScriptName,
|
||||
messageId: this.id,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// Send message using `window.postMessage` for backwards compatibility to invalidate old versions before WXT changed to `document.dispatchEvent`
|
||||
// TODO: Remove this once WXT version using `document.dispatchEvent` has been released for a while
|
||||
window.postMessage(
|
||||
{
|
||||
type: ContentScriptContext.SCRIPT_STARTED_MESSAGE_TYPE,
|
||||
contentScriptName: this.contentScriptName,
|
||||
messageId: Math.random().toString(36).slice(2),
|
||||
messageId: this.id,
|
||||
},
|
||||
'*',
|
||||
);
|
||||
}
|
||||
|
||||
verifyScriptStartedEvent(event: MessageEvent) {
|
||||
const isScriptStartedEvent =
|
||||
event.data?.type === ContentScriptContext.SCRIPT_STARTED_MESSAGE_TYPE;
|
||||
verifyScriptStartedEvent(event: CustomEvent) {
|
||||
const isSameContentScript =
|
||||
event.data?.contentScriptName === this.contentScriptName;
|
||||
const isNotDuplicate = !this.receivedMessageIds.has(event.data?.messageId);
|
||||
return isScriptStartedEvent && isSameContentScript && isNotDuplicate;
|
||||
event.detail?.contentScriptName === this.contentScriptName;
|
||||
// Handle case where website dispatches the event again for some reason
|
||||
const isFromSelf = event.detail?.messageId === this.id;
|
||||
return isSameContentScript && !isFromSelf;
|
||||
}
|
||||
|
||||
listenForNewerScripts(options?: { ignoreFirstEvent?: boolean }) {
|
||||
let isFirst = true;
|
||||
|
||||
const cb = (event: MessageEvent) => {
|
||||
if (this.verifyScriptStartedEvent(event)) {
|
||||
this.receivedMessageIds.add(event.data.messageId);
|
||||
|
||||
const wasFirst = isFirst;
|
||||
isFirst = false;
|
||||
if (wasFirst && options?.ignoreFirstEvent) return;
|
||||
|
||||
this.notifyInvalidated();
|
||||
listenForNewerScripts() {
|
||||
const cb: EventListener = (event) => {
|
||||
if (
|
||||
!(event instanceof CustomEvent) ||
|
||||
!this.verifyScriptStartedEvent(event)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.notifyInvalidated();
|
||||
};
|
||||
|
||||
addEventListener('message', cb);
|
||||
this.onInvalidated(() => removeEventListener('message', cb));
|
||||
document.addEventListener(
|
||||
ContentScriptContext.SCRIPT_STARTED_MESSAGE_TYPE,
|
||||
cb,
|
||||
);
|
||||
this.onInvalidated(() =>
|
||||
document.removeEventListener(
|
||||
ContentScriptContext.SCRIPT_STARTED_MESSAGE_TYPE,
|
||||
cb,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export function createIframeUi<TMounted>(
|
||||
iframe.src = browser.runtime.getURL(options.page);
|
||||
wrapper.appendChild(iframe);
|
||||
|
||||
let mounted: TMounted | undefined = undefined;
|
||||
let mounted: TMounted | undefined;
|
||||
const mount = () => {
|
||||
applyPosition(wrapper, iframe, options);
|
||||
options.onBeforeMount?.(wrapper, iframe);
|
||||
|
||||
@@ -14,7 +14,7 @@ export function createIntegratedUi<TMounted>(
|
||||
): IntegratedContentScriptUi<TMounted> {
|
||||
const wrapper = document.createElement(options.tag || 'div');
|
||||
|
||||
let mounted: TMounted | undefined = undefined;
|
||||
let mounted: TMounted | undefined;
|
||||
const mount = () => {
|
||||
applyPosition(wrapper, undefined, options);
|
||||
mountUi(wrapper, options);
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
isExist as mountDetector,
|
||||
isNotExist as removeDetector,
|
||||
} from '@1natsu/wait-element/detectors';
|
||||
import { logger } from '../../utils/internal/logger';
|
||||
import { logger } from '../internal/logger';
|
||||
|
||||
export function applyPosition(
|
||||
root: HTMLElement,
|
||||
@@ -108,7 +108,6 @@ export function mountUi(
|
||||
break;
|
||||
default:
|
||||
options.append(anchor, root);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +115,7 @@ export function createMountFunctions<TMounted>(
|
||||
baseFunctions: BaseMountFunctions,
|
||||
options: ContentScriptUiOptions<TMounted>,
|
||||
): MountFunctions {
|
||||
let autoMountInstance: AutoMount | undefined = undefined;
|
||||
let autoMountInstance: AutoMount | undefined;
|
||||
|
||||
const stopAutoMount = () => {
|
||||
autoMountInstance?.stopAutoMount();
|
||||
@@ -213,7 +212,7 @@ function autoMountUi(
|
||||
}
|
||||
}
|
||||
}
|
||||
observeElement(resolvedAnchor);
|
||||
void observeElement(resolvedAnchor);
|
||||
|
||||
return { stopAutoMount: _stopAutoMount };
|
||||
}
|
||||
|
||||
@@ -29,9 +29,6 @@ let ws: WxtWebSocket | undefined;
|
||||
|
||||
/**
|
||||
* Connect to the websocket and listen for messages.
|
||||
*
|
||||
* @param onMessage Optional callback that is called when a message is recieved and we've verified
|
||||
* it's structure is what we expect.
|
||||
*/
|
||||
export function getDevServerWebSocket(): WxtWebSocket {
|
||||
if (import.meta.env.COMMAND !== 'serve')
|
||||
|
||||
@@ -41,7 +41,7 @@ let result;
|
||||
try {
|
||||
initPlugins();
|
||||
result = definition.main();
|
||||
// @ts-expect-error: res shouldn't be a promise, but we're checking it anyways
|
||||
// @ts-expect-error: Res shouldn't be a promise, but we're checking it anyways
|
||||
if (result instanceof Promise) {
|
||||
console.warn(
|
||||
"The background's main() function return a promise, but it must be synchronous",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { exists, rm } from 'fs-extra';
|
||||
import { pathExists, rm } from 'fs-extra';
|
||||
|
||||
let setupHappened = false;
|
||||
|
||||
@@ -13,7 +13,7 @@ export async function setup() {
|
||||
globalThis.__ENTRYPOINT__ = 'test';
|
||||
|
||||
const e2eDistPath = './e2e/dist/';
|
||||
if (await exists(e2eDistPath)) {
|
||||
if (await pathExists(e2eDistPath)) {
|
||||
await rm(e2eDistPath, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+873
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,18 @@
|
||||
import {
|
||||
determineSemverChange,
|
||||
loadChangelogConfig,
|
||||
parseCommits,
|
||||
generateMarkDown,
|
||||
loadChangelogConfig,
|
||||
parseChangelogMarkdown,
|
||||
parseCommits,
|
||||
} from 'changelogen';
|
||||
import spawn from 'nano-spawn';
|
||||
import { getPkgTag, grabPackageDetails, listCommitsInDir } from './git';
|
||||
import { consola } from 'consola';
|
||||
import fs from 'fs-extra';
|
||||
import spawn from 'nano-spawn';
|
||||
import path from 'node:path';
|
||||
import { getPkgTag, grabPackageDetails, listCommitsInDir } from './git';
|
||||
|
||||
const pkg = process.argv[2];
|
||||
if (pkg == null) {
|
||||
if (!pkg) {
|
||||
throw Error(
|
||||
'Package name missing. Usage: tsx bump-package-version.ts <package-name>',
|
||||
);
|
||||
@@ -24,7 +24,8 @@ consola.info('Bumping:', { pkg, pkgDir, pkgName, currentVersion });
|
||||
// Get commits
|
||||
const config = await loadChangelogConfig(process.cwd());
|
||||
consola.info('Config:', config);
|
||||
const rawCommits = await listCommitsInDir(pkgDir, prevTag);
|
||||
const additionalDirs = pkg === 'wxt' ? ['docs'] : [];
|
||||
const rawCommits = await listCommitsInDir(pkgDir, prevTag, additionalDirs);
|
||||
const commits = parseCommits(rawCommits, config);
|
||||
|
||||
// Bump version
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
createGithubRelease,
|
||||
GithubRelease,
|
||||
loadChangelogConfig,
|
||||
parseChangelogMarkdown,
|
||||
} from 'changelogen';
|
||||
@@ -8,7 +9,7 @@ import { grabPackageDetails } from './git';
|
||||
import consola from 'consola';
|
||||
|
||||
const pkg = process.argv[2];
|
||||
if (pkg == null) {
|
||||
if (!pkg) {
|
||||
throw Error(
|
||||
'Package name missing. Usage: tsx create-github-release.ts <package-name>',
|
||||
);
|
||||
@@ -29,7 +30,6 @@ await createGithubRelease(config, {
|
||||
tag_name: prevTag,
|
||||
name: `${pkgName} v${currentVersion}`,
|
||||
body: releases[0].body,
|
||||
// @ts-expect-error: Not typed in changelogen, but present: https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#create-a-release
|
||||
make_latest: String(pkg === 'wxt'),
|
||||
});
|
||||
} as GithubRelease & { make_latest: string });
|
||||
consola.success('Created release');
|
||||
|
||||
+4
-2
@@ -25,15 +25,17 @@ export function getPkgTag(pkg: string, version: string | undefined) {
|
||||
export async function listCommitsInDir(
|
||||
dir: string,
|
||||
lastTag: string,
|
||||
additionalDirs: string[] = [],
|
||||
): Promise<RawGitCommit[]> {
|
||||
consola.info('Listing commits:', { lastTag, dir });
|
||||
const allDirs = [dir, ...additionalDirs];
|
||||
consola.info('Listing commits:', { lastTag, dirs: allDirs });
|
||||
const commits = await getGitDiff(lastTag);
|
||||
consola.info('All commits:', commits.length);
|
||||
consola.debug(commits);
|
||||
// commit.body contains all the files that were modified/added. So just check to make sure "\t" + "packages/storage" + "/" is in the body to include
|
||||
// '"\n\nM\tpackages/wxt/vitest.config.ts\n"'
|
||||
const filtered = commits.filter((commit) =>
|
||||
commit.body.includes(`\t${dir}/`),
|
||||
allDirs.some((dir) => commit.body.includes(`\t${dir}/`)),
|
||||
);
|
||||
consola.info('Filtered:', filtered.length);
|
||||
consola.debug(filtered);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user