Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c3009a48f | |||
| 6c6087249f | |||
| 539d482f71 | |||
| cab97bca1e | |||
| 6020083b89 | |||
| 947b24f1b0 | |||
| 2c5ba64b61 | |||
| faa4fcb718 | |||
| 8e9b67fea2 | |||
| 659bdd46a0 | |||
| edcc411d26 | |||
| e022423bad | |||
| 84e1fd44eb | |||
| b68d1ff828 | |||
| c31a5c30ed | |||
| 5b408c2bc2 | |||
| ca29419db6 | |||
| ab26bdc7c1 | |||
| d270250633 | |||
| d75f64df79 | |||
| 5faa5d7537 | |||
| 177223c625 | |||
| a64ff22917 | |||
| 0bf08329b0 | |||
| e3f00d2797 | |||
| 09de72895e | |||
| c0aa12089e | |||
| 0248f58785 | |||
| 247cd66f74 | |||
| 4a1bd41df9 | |||
| efcb593718 | |||
| a82c7cbf71 | |||
| db60e79a99 | |||
| 722eff145f | |||
| 282458748d | |||
| 26197f11a3 | |||
| 9d560f9798 | |||
| 786292c95e | |||
| e760ee444e | |||
| 78f91fefb6 | |||
| fda1e18f48 | |||
| c94168e2d1 | |||
| 3eb505dbde | |||
| fbe502fddf | |||
| c143a9978d | |||
| e3555b663d | |||
| df934d252c | |||
| 447d011b8f | |||
| abe1263d27 | |||
| 143b5ac81f | |||
| 87fbb1f4c3 | |||
| 62f11bf605 | |||
| 3ead54b58e | |||
| 580f4f3cfe | |||
| da0accebe8 | |||
| eb65d0b42a | |||
| c2784135b0 | |||
| 61d54bd1f9 | |||
| 0bd94fce3b | |||
| 2a97c06443 | |||
| 8213aa776a | |||
| 026f78254d | |||
| 0ebb013ba6 | |||
| c5f78d0c8d | |||
| 8771676e69 | |||
| aebbbb0685 | |||
| 68183b648e | |||
| 45809c0198 | |||
| 13163c9ba1 | |||
| aedbba3f3d |
@@ -1,5 +1,9 @@
|
||||
name: Basic Setup
|
||||
description: Install PNPM, Node, and dependencies
|
||||
inputs:
|
||||
install:
|
||||
default: 'true'
|
||||
description: Whether or not to run 'pnpm install'
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
@@ -11,5 +15,6 @@ runs:
|
||||
node-version: 18
|
||||
cache: pnpm
|
||||
- name: Install Dependencies
|
||||
if: ${{ inputs.install == 'true' }}
|
||||
shell: bash
|
||||
run: pnpm install
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: '/' # Location of package manifests
|
||||
directories:
|
||||
- /
|
||||
- packages/*
|
||||
schedule:
|
||||
interval: 'monthly'
|
||||
- package-ecosystem: 'github-actions'
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
name: Continuous Publish
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm buildc all
|
||||
- run: pnpx pkg-pr-new publish --compact './packages/*'
|
||||
@@ -0,0 +1,29 @@
|
||||
name: vhs
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'docs/tapes/*.tape'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
vhs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
with:
|
||||
install: 'false'
|
||||
- name: Preinstall WXT
|
||||
run: |
|
||||
pnpm store add wxt@latest
|
||||
pnpm dlx wxt@latest --version
|
||||
- uses: charmbracelet/vhs-action@v2.1.0
|
||||
with:
|
||||
path: 'docs/tapes/init-demo.tape'
|
||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
commit_message: 'docs: Update `wxt init` GIF'
|
||||
# https://github.com/charmbracelet/vhs#output
|
||||
file_pattern: 'docs/assets/*.gif'
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
# Contributing
|
||||
|
||||
Everyone is welcome to contribute to WXT!
|
||||
Everyone is welcome to contribute to **WXT**!
|
||||
|
||||
If you are changing the docs or fixing a bug, feel free to fork and open a PR.
|
||||
|
||||
@@ -139,7 +139,7 @@ Releases are done with GitHub actions:
|
||||
|
||||
Use [`taze`](https://www.npmjs.com/package/taze) to upgrade dependencies throughout the entire monorepo.
|
||||
|
||||
```ts
|
||||
```sh
|
||||
pnpm dlx taze -r
|
||||
```
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<h1 align="center">
|
||||
<img style="vertical-align:middle" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo">
|
||||
<img align="top" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo">
|
||||
<span>WXT</span>
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm version" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<span> </span>
|
||||
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm" src="https://img.shields.io/npm/dm/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="downloads" src="https://img.shields.io/npm/dm/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<span> </span>
|
||||
<a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE" target="_blank"><img alt="NPM" src="https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE" target="_blank"><img alt="license | MIT" src="https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<span> </span>
|
||||
<a href="https://codecov.io/github/wxt-dev/wxt" target="_blank"><img alt="Codecov" src="https://img.shields.io/codecov/c/github/wxt-dev/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<a href="https://codecov.io/github/wxt-dev/wxt" target="_blank"><img alt="coverage" src="https://img.shields.io/codecov/c/github/wxt-dev/wxt?labelColor=black&color=%234fa048"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -43,10 +43,21 @@ https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84
|
||||
|
||||
Bootstrap a new project:
|
||||
|
||||
<!-- automd:pm-x version="latest" name="wxt" args="init" -->
|
||||
|
||||
```sh
|
||||
pnpm dlx wxt@latest init <project-name>
|
||||
# npm
|
||||
npx wxt@latest init
|
||||
|
||||
# pnpm
|
||||
pnpm dlx wxt@latest init
|
||||
|
||||
# bun
|
||||
bunx wxt@latest init
|
||||
```
|
||||
|
||||
<!-- /automd -->
|
||||
|
||||
Or see the [installation guide](https://wxt.dev/guide/installation.html) to get started with WXT.
|
||||
|
||||
## Features
|
||||
@@ -66,6 +77,13 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
|
||||
|
||||
## Contributors
|
||||
|
||||
<!-- automd:contributors author="aklinker1" license="MIT" github="wxt-dev/wxt" -->
|
||||
|
||||
Published under the [MIT](https://github.com/wxt-dev/wxt/blob/main/LICENSE) license.
|
||||
Made by [@aklinker1](https://github.com/aklinker1) and [community](https://github.com/wxt-dev/wxt/graphs/contributors) 💛
|
||||
<br><br>
|
||||
<a href="https://github.com/wxt-dev/wxt/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=wxt-dev/wxt" />
|
||||
<img src="https://contrib.rocks/image?repo=wxt-dev/wxt" />
|
||||
</a>
|
||||
|
||||
<!-- /automd -->
|
||||
|
||||
@@ -38,6 +38,12 @@ const chromeExtensionIds = [
|
||||
'cfkdcideecefncbglkhneoflfnmhoicc', // mindful - stay focused on your goals
|
||||
'lnhejcpclabmbgpiiomjbhalblnnbffg', // 1Proompt
|
||||
'fonflmjnjbkigocpoommgmhljdpljain', // NiceTab - https://github.com/web-dahuyou/NiceTab
|
||||
'fcffekbnfcfdemeekijbbmgmkognnmkd', // Draftly for LinkedIn
|
||||
'nkndldfehcidpejfkokbeghpnlbppdmo', // YouTube Summarized - Summarize any YouTube video
|
||||
'dbichmdlbjdeplpkhcejgkakobjbjalc', // 社媒助手 - https://github.com/iszhouhua/social-media-copilot
|
||||
'opepfpjeogkbgeigkbepobceinnfmjdd', // Dofollow Links for SEO
|
||||
'pdnenlnelpdomajfejgapbdpmjkfpjkp', // ChatGPT Writer: Use AI on Any Site (GPT-4o, Claude, Gemini, and More)
|
||||
'jobnhifpphkgoelnhnopgkdhbdkiadmj', // discord message translator
|
||||
];
|
||||
|
||||
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { resolve, join } from 'node:path';
|
||||
import { resolve } from 'node:path';
|
||||
import consola from 'consola';
|
||||
import { execaCommand } from 'execa';
|
||||
import spawn from 'nano-spawn';
|
||||
|
||||
const cliDir = resolve('packages/wxt/src/cli/commands');
|
||||
const cliDirGlob = resolve(cliDir, '**');
|
||||
@@ -37,7 +37,8 @@ export default {
|
||||
};
|
||||
|
||||
async function getHelp(command: string): Promise<string> {
|
||||
const res = await execaCommand(command + ' --help', {
|
||||
const args = command.split(' ');
|
||||
const res = await spawn(args[0], [...args.slice(1), '--help'], {
|
||||
cwd: 'packages/wxt',
|
||||
});
|
||||
return res.stdout;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 136 KiB |
@@ -25,6 +25,10 @@ npx wxt@latest init <project-name>
|
||||
npx wxt@latest init <project-name>
|
||||
```
|
||||
|
||||
### Demo
|
||||
|
||||

|
||||
|
||||
:::
|
||||
|
||||
There are several starting templates available.
|
||||
|
||||
@@ -9,7 +9,7 @@ head:
|
||||
|
||||
## Overview
|
||||
|
||||
WXT is a free and open source framework for building web extensions in an conventional, intuitive, and safe way **_for all browsers_**.
|
||||
WXT is a free and open source framework for building web extensions in a conventional, intuitive, and safe way **_for all browsers_**.
|
||||
|
||||
WXT is based on [Nuxt](https://nuxt.com), and aims to provide the same great DX with TypeScript, auto-imports, and an opinionated project structure.
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export default defineContentScript({
|
||||
excludeMatches: undefined | [],
|
||||
includeGlobs: undefined | [],
|
||||
excludeGlobs: undefined | [],
|
||||
allFrames: undefined | [],
|
||||
allFrames: undefined | true | false,
|
||||
runAt: undefined | 'document_start' | 'document_end' | 'document_idle',
|
||||
matchAboutBlank: undefined | true | false,
|
||||
matchOriginAsFallback: undefined | true | false,
|
||||
|
||||
@@ -56,7 +56,8 @@ When `permissions`/`host_permissions` change during an update, depending on what
|
||||
You can test if your permission changes will result in a disabled extension:
|
||||
|
||||
- Chromium: Use [Google's Extension Update Testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool)
|
||||
- Firefox/Safari: Everyone breaks something in production eventually... 🫡 Good luck soldier
|
||||
- Firefox: See their [Test Permission Requests](https://extensionworkshop.com/documentation/develop/test-permission-requests/) page
|
||||
- Safari: Everyone breaks something in production eventually... 🫡 Good luck soldier
|
||||
|
||||
## Update Event
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ export default defineWxtModule({
|
||||
name: 'changelog',
|
||||
// Point to the "modules/changelog.html" file
|
||||
inputPath: resolve(__dirname, 'changelog.html'),
|
||||
outputDir: wxt.config.outputDir,
|
||||
outputDir: wxt.config.outDir,
|
||||
options: {},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -100,7 +100,7 @@ Usually, this means each entrypoint should be a directory with it's own files in
|
||||
|
||||
## Configuring Routers
|
||||
|
||||
Lots of frameworks come with routers for building a multi-page app using the URL's path. Chrome extensions don't don't work like this. Since HTML files are static, `chrome-extension://{id}/popup.html`, there's no way to change the entire path for routing.
|
||||
Lots of frameworks come with routers for building a multi-page app using the URL's path. Chrome extensions don't work like this. Since HTML files are static, `chrome-extension://{id}/popup.html`, there's no way to change the entire path for routing.
|
||||
|
||||
Instead, you need to configure the router to run in "hash" mode, where the routing information is apart of the URL's hash, not the path (ie: `popup.html#/` and `popup.html#/account/settings`).
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ pnpm i wxt@latest
|
||||
|
||||
If there was a major version change, follow the steps below to fix breaking changes.
|
||||
|
||||
## v0.18.0 → v0.19.0
|
||||
## v0.18.5 → v0.19.0
|
||||
|
||||
### `vite-node` Entrypoint Loader
|
||||
|
||||
@@ -86,6 +86,27 @@ WXT no longer ships with Common JS support. If you're using CJS, here's your mig
|
||||
|
||||
Vite also provides steps for migrating to ESM. Check them out for more details: https://vitejs.dev/guide/migration#deprecate-cjs-node-api
|
||||
|
||||
## v0.18.0 → v0.18.5
|
||||
|
||||
> When this version was released, it was not considered a breaking change... but it should have been.
|
||||
|
||||
### New `modules/` Directory
|
||||
|
||||
WXT now recognizes the `modules/` directory as a folder containing [WXT modules](/guide/go-further/reusable-modules).
|
||||
|
||||
If you already have `<srcDir>/modules` or `<srcDir>/Modules` directory, `wxt prepare` and other commands will fail.
|
||||
|
||||
You have two options:
|
||||
|
||||
1. [Recommended] Keep your files where they are and tell WXT to look in a different folder:
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
modulesDir: 'wxt-modules', // defaults to "modules"
|
||||
});
|
||||
```
|
||||
2. Rename your `modules` directory to something else.
|
||||
|
||||
## v0.17.0 → v0.18.0
|
||||
|
||||
### Automatic MV3 `host_permissions` to MV2 `permissions`
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# VHS documentation
|
||||
#
|
||||
# You can view all VHS documentation on the command line with `vhs manual`.
|
||||
# Or see https://github.com/charmbracelet/vhs#vhs-command-reference
|
||||
|
||||
# Output file
|
||||
Output docs/assets/init-demo.gif
|
||||
|
||||
# The tools we will use
|
||||
Require pnpm
|
||||
|
||||
|
||||
# === Scene ====
|
||||
Set Width 1400
|
||||
Set Height 800
|
||||
|
||||
# The maximum FPS for GIF is `50` FPS.
|
||||
Set Framerate 50
|
||||
|
||||
# Terminal theme with WXT brand colors (which was taken from the website)
|
||||
# Based on the standard charmbracelet/vhs theme:
|
||||
# https://github.com/charmbracelet/vhs/blob/88e634f4a10bbe305b6aea9a12b4d8dc3dd7f31c/style.go#L7-L28
|
||||
Set Theme {"background": "#1a1b1a", "foreground": "#dddddd", "black": "#282a2e", "brightBlack": "#4d4d4d", "red": "#D74E6F", "brightRed": "#FE5F86", "green": "#67d45e", "brightGreen": "#67d45e", "yellow": "#D3E561", "brightYellow": "#EBFF71", "blue": "#8056FF", "brightBlue": "#9B79FF", "magenta": "#ED61D7", "brightMagenta": "#FF7AEA", "cyan": "#04D7D7", "brightCyan": "#00FEFE", "white": "#bfbfbf", "brightWhite": "#e6e6e6", "indigo": "#5B56E0"}
|
||||
Set FontSize 32
|
||||
|
||||
# Terminal settings
|
||||
Set Shell "bash"
|
||||
|
||||
# Terminal prompt. It looks like "● mycommand ..."
|
||||
Env PS1 "\e[0;32m●\e[0m "
|
||||
|
||||
|
||||
# ===== Preparation =====
|
||||
# Steps to prepare the recording environment, ensuring a clean and isolated setup.
|
||||
|
||||
Hide
|
||||
# Create a temporary folder for demo
|
||||
Type 'vhs_sandbox="$(mktemp -d)"' Enter
|
||||
Type 'cd "$vhs_sandbox"' Enter
|
||||
Type 'clear' Enter
|
||||
Show
|
||||
|
||||
|
||||
# ===== Actions =====
|
||||
|
||||
Type 'pnpm dlx wxt@latest init .' Sleep 1s Enter
|
||||
Sleep 3.25s
|
||||
Down@750ms Enter@750ms # Select `vue` template
|
||||
Sleep 1.25s
|
||||
Down@750ms Enter@750ms # Select `pnpm` as a package manager
|
||||
Sleep 5s
|
||||
|
||||
|
||||
# ===== Cleaning =====
|
||||
|
||||
Hide
|
||||
# Delete the temporary folder
|
||||
Type 'rm -rf "$vhs_sandbox"' Enter
|
||||
Show
|
||||
+12
-17
@@ -4,7 +4,7 @@
|
||||
"engines": {
|
||||
"node": ">=18.20.3"
|
||||
},
|
||||
"packageManager": "pnpm@9.6.0",
|
||||
"packageManager": "pnpm@9.12.0",
|
||||
"scripts": {
|
||||
"check": "check && pnpm -r --sequential run check",
|
||||
"test": "pnpm -r --sequential run test run",
|
||||
@@ -17,18 +17,18 @@
|
||||
"docs:preview": "pnpm -s docs:gen && vitepress preview docs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.0.11",
|
||||
"@aklinker1/check": "^1.3.1",
|
||||
"@aklinker1/buildc": "^1.1.4",
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@vitest/coverage-v8": "^2.0.4",
|
||||
"changelogen": "^0.5.5",
|
||||
"@vitest/coverage-v8": "^2.1.2",
|
||||
"changelogen": "^0.5.7",
|
||||
"consola": "^3.2.3",
|
||||
"dependency-graph": "^1.0.0",
|
||||
"execa": "^9.3.0",
|
||||
"fast-glob": "^3.3.2",
|
||||
"fs-extra": "^11.2.0",
|
||||
"hasha": "^6.0.0",
|
||||
"lint-staged": "^15.2.7",
|
||||
"lint-staged": "^15.2.10",
|
||||
"nano-spawn": "^0.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.3.3",
|
||||
"simple-git-hooks": "^2.11.1",
|
||||
@@ -36,12 +36,12 @@
|
||||
"typedoc": "^0.25.4",
|
||||
"typedoc-plugin-markdown": "4.0.0-next.23",
|
||||
"typedoc-vitepress-theme": "1.0.0-next.3",
|
||||
"typescript": "^5.5.4",
|
||||
"vitepress": "^1.3.1",
|
||||
"vitest-mock-extended": "^2.0.0",
|
||||
"vue": "^3.4.34",
|
||||
"typescript": "^5.6.2",
|
||||
"vitepress": "^1.3.4",
|
||||
"vitest-mock-extended": "^2.0.2",
|
||||
"vue": "^3.5.11",
|
||||
"wxt": "workspace:*",
|
||||
"yaml": "^2.5.0"
|
||||
"yaml": "^2.5.1"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "pnpm lint-staged"
|
||||
@@ -56,10 +56,5 @@
|
||||
"search-insights"
|
||||
]
|
||||
}
|
||||
},
|
||||
"changelog": {
|
||||
"excludeAuthors": [
|
||||
"aaronklinker1@gmail.com"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,15 +45,16 @@
|
||||
"wxt": ">=0.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.3.1",
|
||||
"publint": "^0.2.9",
|
||||
"typescript": "^5.5.4",
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"oxlint": "^0.9.9",
|
||||
"publint": "^0.2.11",
|
||||
"typescript": "^5.6.2",
|
||||
"unbuild": "^2.0.0",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"defu": "^6.1.4",
|
||||
"fs-extra": "^11.2.0",
|
||||
"sharp": "^0.33.4"
|
||||
"sharp": "^0.33.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# Changelog
|
||||
|
||||
## v0.2.0
|
||||
|
||||
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) • [compare changes](https://github.com/wxt-dev/wxt/compare/i18n-v0.1.1...i18n-v0.2.0)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- ⚠️ Remove invalid options argument ([#1048](https://github.com/wxt-dev/wxt/pull/1048))
|
||||
|
||||
#### ⚠️ Breaking Changes
|
||||
|
||||
- ⚠️ Remove invalid options argument ([#1048](https://github.com/wxt-dev/wxt/pull/1048))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
|
||||
## v0.1.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/i18n-v0.1.0...i18n-v0.1.1)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Friendly error messages for `null` and `undefined` values inside message files ([#1041](https://github.com/wxt-dev/wxt/pull/1041))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Add `oxlint` for linting ([#947](https://github.com/wxt-dev/wxt/pull/947))
|
||||
- Upgrade all non-major dependencies ([#1040](https://github.com/wxt-dev/wxt/pull/1040))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Windmillcode0 <shieldmousetower734@gmail.com>
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@wxt-dev/i18n",
|
||||
"description": "Type-safe wrapper around browser.i18n.getMessage with additional features",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -38,13 +38,14 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.3.1",
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"@types/chrome": "^0.0.268",
|
||||
"@types/node": "^20.14.2",
|
||||
"publint": "^0.2.8",
|
||||
"typescript": "^5.4.5",
|
||||
"@types/node": "^20.16.10",
|
||||
"oxlint": "^0.9.9",
|
||||
"publint": "^0.2.11",
|
||||
"typescript": "^5.6.2",
|
||||
"unbuild": "^2.0.0",
|
||||
"vitest": "^1.6.0",
|
||||
"vitest": "^2.0.0",
|
||||
"vitest-plugin-random-seed": "^1.1.0",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
|
||||
@@ -161,4 +161,17 @@ describe('Built Tools', () => {
|
||||
);
|
||||
expect(mockWriteFile).toBeCalledWith('output.d.ts', expectedDts, 'utf8');
|
||||
});
|
||||
|
||||
it('should throw an error if messages file contains null or undefined', async () => {
|
||||
const invalidFileContent = stringifyYAML({
|
||||
simple: 'example',
|
||||
invalidField: null,
|
||||
});
|
||||
|
||||
mockReadFile.mockResolvedValue(invalidFileContent);
|
||||
|
||||
await expect(parseMessagesFile('invalid.yml')).rejects.toThrowError(
|
||||
'Messages file should not contain `null` (found at "invalidField")',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { createI18n } from '../index';
|
||||
import { GetMessageOptions } from '../types';
|
||||
|
||||
const getMessageMock = vi.fn();
|
||||
|
||||
@@ -28,7 +27,7 @@ describe('createI18n', () => {
|
||||
|
||||
expect(actual).toBe(expectedValue);
|
||||
expect(getMessageMock).toBeCalledTimes(1);
|
||||
expect(getMessageMock).toBeCalledWith(expectedKey, undefined);
|
||||
expect(getMessageMock).toBeCalledWith(expectedKey);
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -55,28 +54,13 @@ describe('createI18n', () => {
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
expect(getMessageMock).toBeCalledTimes(1);
|
||||
expect(getMessageMock).toBeCalledWith(key, [String(count)], undefined);
|
||||
expect(getMessageMock).toBeCalledWith(key, [String(count)]);
|
||||
},
|
||||
);
|
||||
|
||||
it('should allow overriding the plural substitutions', () => {
|
||||
const i18n = createI18n();
|
||||
i18n.t('key', 3, ['custom']);
|
||||
expect(getMessageMock).toBeCalledWith('key', ['custom'], undefined);
|
||||
});
|
||||
|
||||
it('should pass options into browser.i18n.getMessage', () => {
|
||||
const i18n = createI18n();
|
||||
const options: GetMessageOptions = {
|
||||
escapeLt: true,
|
||||
};
|
||||
|
||||
i18n.t('key', options);
|
||||
i18n.t('key', [''], options);
|
||||
i18n.t('key', 1, options);
|
||||
i18n.t('key', 1, [''], options);
|
||||
getMessageMock.mock.calls.forEach((call) => {
|
||||
expect(call.pop()).toEqual(options);
|
||||
});
|
||||
expect(getMessageMock).toBeCalledWith('key', ['custom']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,13 +22,9 @@ describe('I18n Types', () => {
|
||||
describe('t', () => {
|
||||
it('should allow passing any combination of arguments', () => {
|
||||
i18n.t('any');
|
||||
i18n.t('any', { escapeLt: true });
|
||||
i18n.t('any', ['one']);
|
||||
i18n.t('any', ['one'], { escapeLt: true });
|
||||
i18n.t('any', ['one', 'two']);
|
||||
i18n.t('any', ['one', 'two'], { escapeLt: true });
|
||||
i18n.t('any', n, ['one', 'two']);
|
||||
i18n.t('any', n, ['one', 'two'], { escapeLt: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -46,7 +42,6 @@ describe('I18n Types', () => {
|
||||
describe('t', () => {
|
||||
it('should only allow passing valid combinations of arguments', () => {
|
||||
i18n.t('simple');
|
||||
i18n.t('simple', { escapeLt: true });
|
||||
// @ts-expect-error
|
||||
i18n.t('simple', []);
|
||||
// @ts-expect-error
|
||||
@@ -55,7 +50,6 @@ describe('I18n Types', () => {
|
||||
i18n.t('simple', n);
|
||||
|
||||
i18n.t('simpleSub1', ['one']);
|
||||
i18n.t('simpleSub1', ['one'], { escapeLt: true });
|
||||
// @ts-expect-error
|
||||
i18n.t('simpleSub1');
|
||||
// @ts-expect-error
|
||||
@@ -66,7 +60,6 @@ describe('I18n Types', () => {
|
||||
i18n.t('simpleSub1', n);
|
||||
|
||||
i18n.t('simpleSub2', ['one', 'two']);
|
||||
i18n.t('simpleSub2', ['one', 'two'], { escapeLt: true });
|
||||
// @ts-expect-error
|
||||
i18n.t('simpleSub2');
|
||||
// @ts-expect-error
|
||||
@@ -77,7 +70,6 @@ describe('I18n Types', () => {
|
||||
i18n.t('simpleSub2', n);
|
||||
|
||||
i18n.t('plural', n);
|
||||
i18n.t('plural', n, { escapeLt: true });
|
||||
// @ts-expect-error
|
||||
i18n.t('plural');
|
||||
// @ts-expect-error
|
||||
@@ -88,10 +80,8 @@ describe('I18n Types', () => {
|
||||
i18n.t('plural', n, ['sub']);
|
||||
|
||||
i18n.t('pluralSub1', n);
|
||||
i18n.t('pluralSub1', n, { escapeLt: true });
|
||||
i18n.t('pluralSub1', n, undefined, { escapeLt: true });
|
||||
i18n.t('pluralSub1', n, undefined);
|
||||
i18n.t('pluralSub1', n, ['one']);
|
||||
i18n.t('pluralSub1', n, ['one'], { escapeLt: true });
|
||||
// @ts-expect-error
|
||||
i18n.t('pluralSub1');
|
||||
// @ts-expect-error
|
||||
@@ -102,7 +92,6 @@ describe('I18n Types', () => {
|
||||
i18n.t('pluralSub1', n, ['one', 'two']);
|
||||
|
||||
i18n.t('pluralSub2', n, ['one', 'two']);
|
||||
i18n.t('pluralSub2', n, ['one', 'two'], { escapeLt: true });
|
||||
// @ts-expect-error
|
||||
i18n.t('pluralSub2');
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -166,6 +166,11 @@ function _parseMessagesObject(
|
||||
];
|
||||
}
|
||||
case 'object':
|
||||
if ([null, undefined].includes(object)) {
|
||||
throw new Error(
|
||||
`Messages file should not contain \`${object}\` (found at "${path.join('.')}")`,
|
||||
);
|
||||
}
|
||||
if (Array.isArray(object))
|
||||
return object.flatMap((item, i) =>
|
||||
_parseMessagesObject(path.concat(String(i)), item, depth + 1),
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
DefaultI18nStructure,
|
||||
I18n,
|
||||
Substitution,
|
||||
GetMessageOptions,
|
||||
} from './types';
|
||||
|
||||
export function createI18n<
|
||||
@@ -16,7 +15,6 @@ export function createI18n<
|
||||
// Resolve args
|
||||
let sub: Substitution[] | undefined;
|
||||
let count: number | undefined;
|
||||
let options: GetMessageOptions | undefined;
|
||||
args.forEach((arg, i) => {
|
||||
if (arg == null) {
|
||||
// ignore nullish args
|
||||
@@ -24,8 +22,6 @@ export function createI18n<
|
||||
count = arg;
|
||||
} else if (Array.isArray(arg)) {
|
||||
sub = arg;
|
||||
} else if (typeof arg === 'object') {
|
||||
options = arg;
|
||||
} else {
|
||||
throw Error(
|
||||
`Unknown argument at index ${i}. Must be a number for pluralization, substitution array, or options object.`,
|
||||
@@ -43,18 +39,9 @@ export function createI18n<
|
||||
if (sub?.length) {
|
||||
// Convert all substitutions to strings
|
||||
const stringSubs = sub?.map((sub) => String(sub));
|
||||
message = chrome.i18n.getMessage(
|
||||
key.replaceAll('.', '_'),
|
||||
stringSubs,
|
||||
// @ts-ignore - @types/chrome doesn't type the options object, but it's there
|
||||
options,
|
||||
);
|
||||
message = chrome.i18n.getMessage(key.replaceAll('.', '_'), stringSubs);
|
||||
} else {
|
||||
message = chrome.i18n.getMessage(
|
||||
key.replaceAll('.', '_'),
|
||||
// @ts-ignore - @types/chrome doesn't type the options object, but it's there
|
||||
options,
|
||||
);
|
||||
message = chrome.i18n.getMessage(key.replaceAll('.', '_'));
|
||||
}
|
||||
if (!message) {
|
||||
console.warn(`[i18n] Message not found: "${key}"`);
|
||||
|
||||
@@ -29,7 +29,6 @@ export type TFunction<T extends I18nStructure> = {
|
||||
<K extends keyof T>(
|
||||
// prettier-ignore
|
||||
key: K & { [P in keyof T]: T[P] extends { plural: false; substitutions: 0 } ? P : never; }[keyof T],
|
||||
options?: GetMessageOptions,
|
||||
): string;
|
||||
|
||||
// Non-plural with substitutions
|
||||
@@ -39,7 +38,6 @@ export type TFunction<T extends I18nStructure> = {
|
||||
substitutions: T[K] extends I18nFeatures
|
||||
? SubstitutionTuple<T[K]['substitutions']>
|
||||
: never,
|
||||
options?: GetMessageOptions,
|
||||
): string;
|
||||
|
||||
// Plural with 1 substitution
|
||||
@@ -48,7 +46,6 @@ export type TFunction<T extends I18nStructure> = {
|
||||
key: K & { [P in keyof T]: T[P] extends { plural: true; substitutions: 1 } ? P : never; }[keyof T],
|
||||
n: number,
|
||||
substitutions?: SubstitutionTuple<1>,
|
||||
options?: GetMessageOptions,
|
||||
): string;
|
||||
|
||||
// Plural without substitutions
|
||||
@@ -56,7 +53,6 @@ export type TFunction<T extends I18nStructure> = {
|
||||
// prettier-ignore
|
||||
key: K & { [P in keyof T]: T[P] extends { plural: true; substitutions: 0 | 1 } ? P : never; }[keyof T],
|
||||
n: number,
|
||||
options?: GetMessageOptions,
|
||||
): string;
|
||||
|
||||
// Plural with substitutions
|
||||
@@ -67,7 +63,6 @@ export type TFunction<T extends I18nStructure> = {
|
||||
substitutions: T[K] extends I18nFeatures
|
||||
? SubstitutionTuple<T[K]['substitutions']>
|
||||
: never,
|
||||
options?: GetMessageOptions,
|
||||
): string;
|
||||
};
|
||||
|
||||
@@ -77,13 +72,4 @@ export interface I18n<T extends DefaultI18nStructure> {
|
||||
|
||||
export type Substitution = string | number;
|
||||
|
||||
export interface GetMessageOptions {
|
||||
/**
|
||||
* Escape `<` in translation to `<`. This applies only to the message itself, not to the placeholders. Developers might want to use this if the translation is used in an HTML context. Closure Templates used with Closure Compiler generate this automatically.
|
||||
*
|
||||
* See https://developer.chrome.com/docs/extensions/reference/api/i18n#type-getMessage-options
|
||||
*/
|
||||
escapeLt?: boolean;
|
||||
}
|
||||
|
||||
type SubstitutionCount = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
||||
|
||||
@@ -14,7 +14,7 @@ export function applyChromeMessagePlaceholders(message: ChromeMessage): string {
|
||||
export function getSubstitionCount(message: string): number {
|
||||
return (
|
||||
1 +
|
||||
new Array(MAX_SUBSTITUTIONS).findLastIndex((_, i) =>
|
||||
Array.from({ length: MAX_SUBSTITUTIONS }).findLastIndex((_, i) =>
|
||||
message.match(new RegExp(`(?<!\\$)\\$${i + 1}`)),
|
||||
)
|
||||
);
|
||||
|
||||
@@ -47,16 +47,16 @@
|
||||
"wxt": ">=0.18.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-react": "^4.3.1"
|
||||
"@vitejs/plugin-react": "^4.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.3.1",
|
||||
"@types/react": "^18.3.3",
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"publint": "^0.2.9",
|
||||
"publint": "^0.2.11",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"typescript": "^5.5.4",
|
||||
"typescript": "^5.6.2",
|
||||
"unbuild": "^2.0.0",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
|
||||
@@ -50,10 +50,10 @@
|
||||
"vite-plugin-solid": "^2.10.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.3.1",
|
||||
"publint": "^0.2.9",
|
||||
"solid-js": "^1.8.19",
|
||||
"typescript": "^5.5.4",
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"publint": "^0.2.11",
|
||||
"solid-js": "^1.9.1",
|
||||
"typescript": "^5.6.2",
|
||||
"unbuild": "^2.0.0",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
|
||||
@@ -45,12 +45,12 @@
|
||||
"wxt": ">=0.18.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.1"
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.3.1",
|
||||
"publint": "^0.2.9",
|
||||
"typescript": "^5.5.4",
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"publint": "^0.2.11",
|
||||
"typescript": "^5.6.2",
|
||||
"unbuild": "^2.0.0",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
|
||||
@@ -45,12 +45,12 @@
|
||||
"wxt": ">=0.18.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-vue": "^5.1.1"
|
||||
"@vitejs/plugin-vue": "^5.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.3.1",
|
||||
"publint": "^0.2.9",
|
||||
"typescript": "^5.5.4",
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"publint": "^0.2.11",
|
||||
"typescript": "^5.6.2",
|
||||
"unbuild": "^2.0.0",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chrome": "^0.0.269",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@wxt-dev/auto-icons": "workspace:*",
|
||||
"sass": "^1.77.8",
|
||||
"typescript": "^5.5.4",
|
||||
"vitest": "^2.0.4",
|
||||
"sass": "^1.79.4",
|
||||
"typescript": "^5.6.2",
|
||||
"vitest": "^2.1.2",
|
||||
"vitest-plugin-random-seed": "^1.1.0",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
src/core/utils/building/__tests__/test-entrypoints
|
||||
@@ -1,5 +1,114 @@
|
||||
# Changelog
|
||||
|
||||
## v0.19.11
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.10...wxt-v0.19.11)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- **zip:** Hooks ([#993](https://github.com/wxt-dev/wxt/pull/993))
|
||||
- **zip:** `wxt zip --sources` and auto sources for opera ([#1014](https://github.com/wxt-dev/wxt/pull/1014))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Reverse env files priority ([#1016](https://github.com/wxt-dev/wxt/pull/1016))
|
||||
- #1005 fixed, by updating type-definations to getItem method. ([#1007](https://github.com/wxt-dev/wxt/pull/1007), [#1005](https://github.com/wxt-dev/wxt/issues/1005))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Move some files around ([#996](https://github.com/wxt-dev/wxt/pull/996))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
|
||||
- Gurvir Singh ([@baraich](http://github.com/baraich))
|
||||
- Mezannic ([@mezannic](http://github.com/mezannic))
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
|
||||
## v0.19.10
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.9...wxt-v0.19.10)
|
||||
|
||||
### 🔥 Performance
|
||||
|
||||
- Reduce hypersensitive onChange of watcher ([#978](https://github.com/wxt-dev/wxt/pull/978))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Fix config manifest type ([#973](https://github.com/wxt-dev/wxt/pull/973))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Examples reference outDir vs. outputDir ([#982](https://github.com/wxt-dev/wxt/pull/982))
|
||||
- Improved docs and links ([#970](https://github.com/wxt-dev/wxt/pull/970))
|
||||
|
||||
### 🌊 Types
|
||||
|
||||
- Fix `ExtensionRunnerConfig.chromiumPref` type ([fda1e18](https://github.com/wxt-dev/wxt/commit/fda1e18))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- 1natsu ([@1natsu172](http://github.com/1natsu172))
|
||||
- Okinea Dev ([@okineadev](http://github.com/okineadev))
|
||||
- The-syndrome <meltdown-syndrome@proton.me>
|
||||
- Hikiko4ern ([@hikiko4ern](http://github.com/hikiko4ern))
|
||||
|
||||
## v0.19.9
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.8...wxt-v0.19.9)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- **modules:** Add `wxt.hook` alias for `wxt.hooks.hook` ([c5f78d0](https://github.com/wxt-dev/wxt/commit/c5f78d0))
|
||||
- Use `@types/chrome` for config manifest type ([#969](https://github.com/wxt-dev/wxt/pull/969))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Allow adding multiple hyphens in an entrypoint name ([#949](https://github.com/wxt-dev/wxt/pull/949))
|
||||
- Duplicate `BuildOutput.publicAssets` ([#951](https://github.com/wxt-dev/wxt/pull/951))
|
||||
- Properly overload `import.meta.env` with WXT's own environment globals ([#966](https://github.com/wxt-dev/wxt/pull/966))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Add docs around `importEntrypoint` to relevant functions ([143b5ac](https://github.com/wxt-dev/wxt/commit/143b5ac))
|
||||
|
||||
### 🌊 Types
|
||||
|
||||
- **modules:** Use `NestedHooks` instead of `Partial` for hooks object ([0ebb013](https://github.com/wxt-dev/wxt/commit/0ebb013))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Fix type error ([#946](https://github.com/wxt-dev/wxt/pull/946))
|
||||
- Add `oxlint` for linting ([#947](https://github.com/wxt-dev/wxt/pull/947))
|
||||
- **deps:** Bump ora from 8.0.1 to 8.1.0 ([#961](https://github.com/wxt-dev/wxt/pull/961))
|
||||
- **deps:** Bump unimport from 3.9.1 to 3.11.1 ([#960](https://github.com/wxt-dev/wxt/pull/960))
|
||||
- **deps:** Bump execa from 9.3.0 to 9.3.1 ([#957](https://github.com/wxt-dev/wxt/pull/957))
|
||||
- Cleanup leftover E2E test artifacts ([#968](https://github.com/wxt-dev/wxt/pull/968))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- 1natsu ([@1natsu172](http://github.com/1natsu172))
|
||||
|
||||
## v0.19.8
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.7...wxt-v0.19.8)
|
||||
|
||||
### 🔥 Performance
|
||||
|
||||
- Ignore output directories for all `vite.watcher` ([#924](https://github.com/wxt-dev/wxt/pull/924))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Fallback to GitHub API for listing templates when ungh is down ([#944](https://github.com/wxt-dev/wxt/pull/944))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **types:** Cleanup types in wxt/browser/chrome ([#932](https://github.com/wxt-dev/wxt/pull/932))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- 1natsu
|
||||
|
||||
## v0.19.7
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.6...wxt-v0.19.7)
|
||||
|
||||
+27
-8
@@ -1,16 +1,17 @@
|
||||
<!-- DO NOT EDIT, THIS FILE WAS GENERATED BY '../../scripts/generate-readmes.sh' -->
|
||||
<h1 align="center">
|
||||
<img style="vertical-align:middle" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo">
|
||||
<img align="top" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo">
|
||||
<span>WXT</span>
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm version" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<span> </span>
|
||||
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm" src="https://img.shields.io/npm/dm/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="downloads" src="https://img.shields.io/npm/dm/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<span> </span>
|
||||
<a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE" target="_blank"><img alt="NPM" src="https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE" target="_blank"><img alt="license | MIT" src="https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<span> </span>
|
||||
<a href="https://codecov.io/github/wxt-dev/wxt" target="_blank"><img alt="Codecov" src="https://img.shields.io/codecov/c/github/wxt-dev/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<a href="https://codecov.io/github/wxt-dev/wxt" target="_blank"><img alt="coverage" src="https://img.shields.io/codecov/c/github/wxt-dev/wxt?labelColor=black&color=%234fa048"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -18,7 +19,7 @@
|
||||
<br/>
|
||||
<span>⚡</span>
|
||||
<br/>
|
||||
<q><i>It's like Nuxt, but for Chrome Extensions</i></q>
|
||||
<q><i>It's like Nuxt, but for Web Extensions</i></q>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -43,10 +44,21 @@ https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84
|
||||
|
||||
Bootstrap a new project:
|
||||
|
||||
<!-- automd:pm-x version="latest" name="wxt" args="init" -->
|
||||
|
||||
```sh
|
||||
pnpm dlx wxt@latest init <project-name>
|
||||
# npm
|
||||
npx wxt@latest init
|
||||
|
||||
# pnpm
|
||||
pnpm dlx wxt@latest init
|
||||
|
||||
# bun
|
||||
bunx wxt@latest init
|
||||
```
|
||||
|
||||
<!-- /automd -->
|
||||
|
||||
Or see the [installation guide](https://wxt.dev/guide/installation.html) to get started with WXT.
|
||||
|
||||
## Features
|
||||
@@ -66,6 +78,13 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
|
||||
|
||||
## Contributors
|
||||
|
||||
<!-- automd:contributors author="aklinker1" license="MIT" github="wxt-dev/wxt" -->
|
||||
|
||||
Published under the [MIT](https://github.com/wxt-dev/wxt/blob/main/LICENSE) license.
|
||||
Made by [@aklinker1](https://github.com/aklinker1) and [community](https://github.com/wxt-dev/wxt/graphs/contributors) 💛
|
||||
<br><br>
|
||||
<a href="https://github.com/wxt-dev/wxt/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=wxt-dev/wxt" />
|
||||
<img src="https://contrib.rocks/image?repo=wxt-dev/wxt" />
|
||||
</a>
|
||||
|
||||
<!-- /automd -->
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
import { execaCommand } from 'execa';
|
||||
import spawn from 'nano-spawn';
|
||||
|
||||
describe('Auto Imports', () => {
|
||||
describe('imports: { ... }', () => {
|
||||
@@ -180,7 +180,7 @@ describe('Auto Imports', () => {
|
||||
await project.prepare({
|
||||
imports: { eslintrc: { enabled: version } },
|
||||
});
|
||||
return await execaCommand('pnpm eslint entrypoints/background.js', {
|
||||
return await spawn('pnpm', ['eslint', 'entrypoints/background.js'], {
|
||||
cwd: project.root,
|
||||
});
|
||||
}
|
||||
@@ -205,7 +205,8 @@ describe('Auto Imports', () => {
|
||||
);
|
||||
|
||||
await expect(runEslint(project, 9)).rejects.toMatchObject({
|
||||
message: expect.stringContaining(
|
||||
exitCode: 1,
|
||||
stdout: expect.stringContaining(
|
||||
"'defineBackground' is not defined",
|
||||
),
|
||||
});
|
||||
@@ -253,7 +254,8 @@ describe('Auto Imports', () => {
|
||||
);
|
||||
|
||||
await expect(runEslint(project, 8)).rejects.toMatchObject({
|
||||
message: expect.stringContaining(
|
||||
exitCode: 1,
|
||||
stdout: expect.stringContaining(
|
||||
"'defineBackground' is not defined",
|
||||
),
|
||||
});
|
||||
|
||||
@@ -14,6 +14,12 @@ const hooks: WxtHooks = {
|
||||
'entrypoints:grouped': vi.fn(),
|
||||
'vite:build:extendConfig': vi.fn(),
|
||||
'vite:devServer:extendConfig': vi.fn(),
|
||||
'zip:start': vi.fn(),
|
||||
'zip:extension:start': vi.fn(),
|
||||
'zip:extension:done': vi.fn(),
|
||||
'zip:sources:start': vi.fn(),
|
||||
'zip:sources:done': vi.fn(),
|
||||
'zip:done': vi.fn(),
|
||||
};
|
||||
|
||||
function expectHooksToBeCalled(
|
||||
@@ -53,6 +59,12 @@ describe('Hooks', () => {
|
||||
'entrypoints:resolved': true,
|
||||
'vite:build:extendConfig': false,
|
||||
'vite:devServer:extendConfig': false,
|
||||
'zip:start': false,
|
||||
'zip:extension:start': false,
|
||||
'zip:extension:done': false,
|
||||
'zip:sources:start': false,
|
||||
'zip:sources:done': false,
|
||||
'zip:done': false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -74,6 +86,12 @@ describe('Hooks', () => {
|
||||
'entrypoints:resolved': true,
|
||||
'vite:build:extendConfig': 1,
|
||||
'vite:devServer:extendConfig': false,
|
||||
'zip:start': false,
|
||||
'zip:extension:start': false,
|
||||
'zip:extension:done': false,
|
||||
'zip:sources:start': false,
|
||||
'zip:sources:done': false,
|
||||
'zip:done': false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -95,6 +113,39 @@ describe('Hooks', () => {
|
||||
'entrypoints:resolved': true,
|
||||
'vite:build:extendConfig': 1,
|
||||
'vite:devServer:extendConfig': false,
|
||||
'zip:start': true,
|
||||
'zip:extension:start': true,
|
||||
'zip:extension:done': true,
|
||||
'zip:sources:start': false,
|
||||
'zip:sources:done': false,
|
||||
'zip:done': true,
|
||||
});
|
||||
});
|
||||
|
||||
it('zip -b firefox should call hooks', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/popup.html', '<html></html>');
|
||||
|
||||
await project.zip({ hooks, browser: 'firefox' });
|
||||
|
||||
expectHooksToBeCalled({
|
||||
ready: true,
|
||||
'prepare:types': true,
|
||||
'prepare:publicPaths': true,
|
||||
'build:before': true,
|
||||
'build:done': true,
|
||||
'build:publicAssets': true,
|
||||
'build:manifestGenerated': true,
|
||||
'entrypoints:grouped': true,
|
||||
'entrypoints:resolved': true,
|
||||
'vite:build:extendConfig': 1,
|
||||
'vite:devServer:extendConfig': false,
|
||||
'zip:start': true,
|
||||
'zip:extension:start': true,
|
||||
'zip:extension:done': true,
|
||||
'zip:sources:start': true,
|
||||
'zip:sources:done': true,
|
||||
'zip:done': true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -122,6 +173,12 @@ describe('Hooks', () => {
|
||||
'entrypoints:resolved': true,
|
||||
'vite:build:extendConfig': 2,
|
||||
'vite:devServer:extendConfig': 1,
|
||||
'zip:start': false,
|
||||
'zip:extension:start': false,
|
||||
'zip:extension:done': false,
|
||||
'zip:sources:start': false,
|
||||
'zip:sources:done': false,
|
||||
'zip:done': false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { TestProject, WXT_PACKAGE_DIR } from '../utils';
|
||||
import { execaCommand } from 'execa';
|
||||
import spawn from 'nano-spawn';
|
||||
import glob from 'fast-glob';
|
||||
import { mkdir, writeJson } from 'fs-extra';
|
||||
|
||||
@@ -8,11 +8,15 @@ describe('Init command', () => {
|
||||
it('should download and create a template', async () => {
|
||||
const project = new TestProject();
|
||||
|
||||
await execaCommand(`pnpm -s wxt init ${project.root} -t vue --pm npm`, {
|
||||
env: { ...process.env, CI: 'true' },
|
||||
stdio: 'ignore',
|
||||
cwd: WXT_PACKAGE_DIR,
|
||||
});
|
||||
await spawn(
|
||||
'pnpm',
|
||||
['-s', 'wxt', 'init', project.root, '-t', 'vue', '--pm', 'npm'],
|
||||
{
|
||||
env: { CI: 'true' },
|
||||
stdio: 'ignore',
|
||||
cwd: WXT_PACKAGE_DIR,
|
||||
},
|
||||
);
|
||||
const files = await glob('**/*', {
|
||||
cwd: project.root,
|
||||
onlyFiles: true,
|
||||
@@ -51,11 +55,15 @@ describe('Init command', () => {
|
||||
await writeJson(project.resolvePath('package.json'), {});
|
||||
|
||||
await expect(() =>
|
||||
execaCommand(`pnpm -s wxt init ${project.root} -t vue --pm npm`, {
|
||||
env: { ...process.env, CI: 'true' },
|
||||
stdio: 'ignore',
|
||||
cwd: WXT_PACKAGE_DIR,
|
||||
}),
|
||||
spawn(
|
||||
'pnpm',
|
||||
['-s', 'wxt', 'init', project.root, '-t', 'vue', '--pm', 'npm'],
|
||||
{
|
||||
env: { CI: 'true' },
|
||||
stdio: 'ignore',
|
||||
cwd: WXT_PACKAGE_DIR,
|
||||
},
|
||||
),
|
||||
).rejects.toThrowError('Command failed with exit code 1:');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { test, expect } from 'vitest';
|
||||
import spawn from 'nano-spawn';
|
||||
import {
|
||||
NpmListDependency,
|
||||
NpmListProject,
|
||||
} from '../../src/core/package-managers/npm';
|
||||
|
||||
// Tests to ensure the total size of the WXT module is as small as possible
|
||||
// https://pkg-size.dev/wxt
|
||||
|
||||
test('Only one version of esbuild should be installed (each version is ~20mb of node_modules)', async () => {
|
||||
const { stdout } = await spawn('pnpm', [
|
||||
'why',
|
||||
'esbuild',
|
||||
'--prod',
|
||||
'--json',
|
||||
]);
|
||||
const projects: NpmListProject[] = JSON.parse(stdout);
|
||||
const esbuildVersions = new Set<string>();
|
||||
iterateDependencies(projects, (name, meta) => {
|
||||
if (name === 'esbuild') esbuildVersions.add(meta.version);
|
||||
});
|
||||
|
||||
expect([...esbuildVersions]).toHaveLength(1);
|
||||
});
|
||||
|
||||
function iterateDependencies(
|
||||
projects: NpmListProject[],
|
||||
cb: (name: string, meta: NpmListDependency) => void,
|
||||
) {
|
||||
const recurse = (dependencies: Record<string, NpmListDependency>) => {
|
||||
Object.entries(dependencies).forEach(([name, meta]) => {
|
||||
cb(name, meta);
|
||||
if (meta.dependencies) recurse(meta.dependencies);
|
||||
});
|
||||
};
|
||||
projects.forEach((project) => {
|
||||
if (project.dependencies) recurse(project.dependencies);
|
||||
});
|
||||
}
|
||||
@@ -362,7 +362,7 @@ describe('Output Directory Structure', () => {
|
||||
.toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/background.js
|
||||
----------------------------------------
|
||||
var _background = function() {
|
||||
var background = function() {
|
||||
"use strict";
|
||||
var _a, _b;
|
||||
function defineBackground(arg) {
|
||||
@@ -372,13 +372,13 @@ describe('Output Directory Structure', () => {
|
||||
function logHello(name) {
|
||||
console.log(\`Hello \${name}!\`);
|
||||
}
|
||||
_background;
|
||||
background;
|
||||
const definition = defineBackground({
|
||||
main() {
|
||||
logHello("background");
|
||||
}
|
||||
});
|
||||
_background;
|
||||
background;
|
||||
function initPlugins() {
|
||||
}
|
||||
// @ts-expect-error
|
||||
@@ -411,7 +411,7 @@ describe('Output Directory Structure', () => {
|
||||
const result$1 = result;
|
||||
return result$1;
|
||||
}();
|
||||
_background;
|
||||
background;
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -13,7 +13,6 @@ describe('TypeScript Project', () => {
|
||||
".wxt/types/globals.d.ts
|
||||
----------------------------------------
|
||||
// Generated by wxt
|
||||
export {}
|
||||
interface ImportMetaEnv {
|
||||
readonly MANIFEST_VERSION: 2 | 3;
|
||||
readonly BROWSER: string;
|
||||
|
||||
@@ -74,7 +74,7 @@ describe('User Config', () => {
|
||||
);
|
||||
|
||||
await project.build({
|
||||
// @ts-expect-error: Specifically setting an invalid field for the test - it should show up in the snapshot
|
||||
// Specifically setting an invalid field for the test - it should show up in the snapshot
|
||||
manifest: ({ manifestVersion, command }) => ({
|
||||
example_customization: [String(manifestVersion), command],
|
||||
}),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
import extract from 'extract-zip';
|
||||
import { execaCommand } from 'execa';
|
||||
import spawn from 'nano-spawn';
|
||||
import { readFile, writeFile } from 'fs-extra';
|
||||
|
||||
process.env.WXT_PNPM_IGNORE_WORKSPACE = 'true';
|
||||
@@ -41,13 +41,15 @@ describe('Zipping', () => {
|
||||
|
||||
// Build zipped extension
|
||||
await expect(
|
||||
execaCommand('pnpm i --ignore-workspace --frozen-lockfile false', {
|
||||
spawn('pnpm', ['i', '--ignore-workspace', '--frozen-lockfile', 'false'], {
|
||||
cwd: unzipDir,
|
||||
}),
|
||||
).resolves.toMatchObject({ exitCode: 0 });
|
||||
).resolves.not.toHaveProperty('exitCode');
|
||||
await expect(
|
||||
execaCommand('pnpm wxt build -b firefox', { cwd: unzipDir }),
|
||||
).resolves.toMatchObject({ exitCode: 0 });
|
||||
spawn('pnpm', ['wxt', 'build', '-b', 'firefox'], {
|
||||
cwd: unzipDir,
|
||||
}),
|
||||
).resolves.not.toHaveProperty('exitCode');
|
||||
|
||||
await expect(project.fileExists(unzipDir, '.output')).resolves.toBe(true);
|
||||
expect(
|
||||
@@ -178,4 +180,73 @@ describe('Zipping', () => {
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it.each(['firefox', 'opera'])(
|
||||
'should create sources zip for "%s" browser when sourcesZip is undefined',
|
||||
async (browser) => {
|
||||
const project = new TestProject({
|
||||
name: 'test',
|
||||
version: '1.0.0',
|
||||
});
|
||||
project.addFile(
|
||||
'entrypoints/background.ts',
|
||||
'export default defineBackground(() => {});',
|
||||
);
|
||||
const sourcesZip = project.resolvePath('.output/test-1.0.0-sources.zip');
|
||||
|
||||
await project.zip({
|
||||
browser,
|
||||
});
|
||||
|
||||
expect(await project.fileExists(sourcesZip)).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
it.each(['firefox', 'chrome'])(
|
||||
'should create sources zip for "%s" when sourcesZip is true',
|
||||
async (browser) => {
|
||||
const project = new TestProject({
|
||||
name: 'test',
|
||||
version: '1.0.0',
|
||||
});
|
||||
project.addFile(
|
||||
'entrypoints/background.ts',
|
||||
'export default defineBackground(() => {});',
|
||||
);
|
||||
const sourcesZip = project.resolvePath('.output/test-1.0.0-sources.zip');
|
||||
|
||||
await project.zip({
|
||||
browser,
|
||||
zip: {
|
||||
zipSources: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(await project.fileExists(sourcesZip)).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
it.each(['firefox', 'chrome'])(
|
||||
'should not create sources zip for "%s" when sourcesZip is false',
|
||||
async (browser) => {
|
||||
const project = new TestProject({
|
||||
name: 'test',
|
||||
version: '1.0.0',
|
||||
});
|
||||
project.addFile(
|
||||
'entrypoints/background.ts',
|
||||
'export default defineBackground(() => {});',
|
||||
);
|
||||
const sourcesZip = project.resolvePath('.output/test-1.0.0-sources.zip');
|
||||
|
||||
await project.zip({
|
||||
browser,
|
||||
zip: {
|
||||
zipSources: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(await project.fileExists(sourcesZip)).toBe(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { dirname, relative, resolve } from 'path';
|
||||
import fs, { mkdir } from 'fs-extra';
|
||||
import glob from 'fast-glob';
|
||||
import { execaCommand } from 'execa';
|
||||
import spawn from 'nano-spawn';
|
||||
import {
|
||||
InlineConfig,
|
||||
UserConfig,
|
||||
@@ -119,7 +119,7 @@ export class TestProject {
|
||||
await fs.writeFile(filePath, content ?? '', 'utf-8');
|
||||
}
|
||||
|
||||
await execaCommand('pnpm --ignore-workspace i --ignore-scripts', {
|
||||
await spawn('pnpm', ['--ignore-workspace', 'i', '--ignore-scripts'], {
|
||||
cwd: this.root,
|
||||
});
|
||||
await mkdir(resolve(this.root, 'public'), { recursive: true }).catch(
|
||||
|
||||
+25
-24
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wxt",
|
||||
"type": "module",
|
||||
"version": "0.19.7",
|
||||
"version": "0.19.11",
|
||||
"description": "Next gen framework for developing web extensions",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -82,22 +82,23 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@aklinker1/rollup-plugin-visualizer": "5.12.0",
|
||||
"@types/chrome": "^0.0.269",
|
||||
"@types/webextension-polyfill": "^0.10.7",
|
||||
"@webext-core/fake-browser": "^1.3.1",
|
||||
"@webext-core/isolated-element": "^1.1.2",
|
||||
"@webext-core/match-patterns": "^1.0.3",
|
||||
"async-mutex": "^0.5.0",
|
||||
"c12": "^1.11.1",
|
||||
"c12": "^1.11.2",
|
||||
"cac": "^6.7.14",
|
||||
"chokidar": "^3.6.0",
|
||||
"ci-info": "^4.0.0",
|
||||
"consola": "^3.2.3",
|
||||
"defu": "^6.1.4",
|
||||
"dequal": "^2.0.3",
|
||||
"esbuild": "^0.23.0",
|
||||
"execa": "^9.3.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"esbuild": "^0.21.5",
|
||||
"fast-glob": "^3.3.2",
|
||||
"filesize": "^10.1.4",
|
||||
"filesize": "^10.1.6",
|
||||
"fs-extra": "^11.2.0",
|
||||
"get-port": "^7.1.0",
|
||||
"giget": "^1.2.3",
|
||||
@@ -106,48 +107,48 @@
|
||||
"jiti": "^1.21.6",
|
||||
"json5": "^2.2.3",
|
||||
"jszip": "^3.10.1",
|
||||
"linkedom": "^0.18.4",
|
||||
"magicast": "^0.3.4",
|
||||
"linkedom": "^0.18.5",
|
||||
"magicast": "^0.3.5",
|
||||
"minimatch": "^10.0.1",
|
||||
"nano-spawn": "^0.1.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
"normalize-path": "^3.0.0",
|
||||
"nypm": "^0.3.9",
|
||||
"ohash": "^1.1.3",
|
||||
"nypm": "^0.3.12",
|
||||
"ohash": "^1.1.4",
|
||||
"open": "^10.1.0",
|
||||
"ora": "^8.0.1",
|
||||
"picocolors": "^1.0.1",
|
||||
"ora": "^8.1.0",
|
||||
"perfect-debounce": "^1.0.0",
|
||||
"picocolors": "^1.1.0",
|
||||
"prompts": "^2.4.2",
|
||||
"publish-browser-extension": "^2.1.3",
|
||||
"unimport": "^3.9.1",
|
||||
"vite": "^5.3.5",
|
||||
"vite-node": "^2.0.4",
|
||||
"publish-browser-extension": "^2.2.2",
|
||||
"scule": "^1.3.0",
|
||||
"unimport": "^3.13.1",
|
||||
"vite": "^5.4.8",
|
||||
"vite-node": "^2.1.2",
|
||||
"web-ext-run": "^0.2.1",
|
||||
"webextension-polyfill": "^0.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.3.1",
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"@faker-js/faker": "^8.4.1",
|
||||
"@types/chrome": "^0.0.269",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/lodash.merge": "^4.6.9",
|
||||
"@types/natural-compare": "^1.4.3",
|
||||
"@types/node": "^20.14.12",
|
||||
"@types/node": "^20.16.10",
|
||||
"@types/normalize-path": "^3.0.2",
|
||||
"@types/prompts": "^2.4.9",
|
||||
"extract-zip": "^2.0.1",
|
||||
"happy-dom": "^14.12.3",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"oxlint": "^0.9.9",
|
||||
"p-map": "^7.0.2",
|
||||
"publint": "^0.2.9",
|
||||
"publint": "^0.2.11",
|
||||
"tsx": "4.15.7",
|
||||
"typescript": "^5.5.4",
|
||||
"typescript": "^5.6.2",
|
||||
"unbuild": "^2.0.0",
|
||||
"vitest": "^2.0.4",
|
||||
"vitest": "^2.1.2",
|
||||
"vitest-plugin-random-seed": "^1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/chrome": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/chrome": {
|
||||
"optional": true
|
||||
|
||||
@@ -903,5 +903,24 @@ describe('Storage Utils', () => {
|
||||
// @ts-expect-error
|
||||
await storage.getItem('loca:test').catch(() => {});
|
||||
});
|
||||
|
||||
it('should return a nullable type when getItem is called without a fallback', async () => {
|
||||
const res = await storage.getItem<string>('local:test');
|
||||
expectTypeOf(res).toBeNullable();
|
||||
});
|
||||
|
||||
it('should return a non-null type when getItem is called with a fallback', async () => {
|
||||
const res = await storage.getItem('local:test', {
|
||||
fallback: 'test',
|
||||
});
|
||||
expectTypeOf(res).not.toBeNullable();
|
||||
});
|
||||
|
||||
it('should return a non-null type when getItem is called with a fallback and the first type parameter is passed', async () => {
|
||||
const res = await storage.getItem<string>('local:test', {
|
||||
fallback: 'test',
|
||||
});
|
||||
expectTypeOf(res).not.toBeNullable();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
/// <reference types="chrome" />
|
||||
/**
|
||||
* @module wxt/browser/chrome
|
||||
*/
|
||||
import type { WxtRuntime, WxtI18n } from './index';
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL
|
||||
*
|
||||
@@ -11,11 +6,11 @@ import type { WxtRuntime, WxtI18n } from './index';
|
||||
*
|
||||
* @module wxt/browser/chrome
|
||||
*/
|
||||
import type { WxtRuntime, WxtI18n } from './index';
|
||||
|
||||
export type Chrome = typeof chrome;
|
||||
export type WxtBrowser = Omit<Chrome, 'runtime' | 'i18n'> & {
|
||||
runtime: WxtRuntime & Omit<Chrome['runtime'], 'getURL'>;
|
||||
i18n: WxtI18n & Omit<Chrome['i18n'], 'getMessage'>;
|
||||
export type WxtBrowser = Omit<typeof chrome, 'runtime' | 'i18n'> & {
|
||||
runtime: WxtRuntime & Omit<(typeof chrome)['runtime'], 'getURL'>;
|
||||
i18n: WxtI18n & Omit<(typeof chrome)['i18n'], 'getMessage'>;
|
||||
};
|
||||
|
||||
export const browser: WxtBrowser =
|
||||
|
||||
@@ -11,13 +11,17 @@ export type AugmentedBrowser = Omit<Browser, 'runtime' | 'i18n'> & {
|
||||
i18n: WxtI18n & Omit<Browser['i18n'], 'getMessage'>;
|
||||
};
|
||||
|
||||
export interface WxtRuntime {
|
||||
// Overriden per-project
|
||||
}
|
||||
/**
|
||||
* This interface is empty because it is generated per-project when running `wxt prepare`. See:
|
||||
* - `.wxt/types/paths.d.ts`
|
||||
*/
|
||||
export interface WxtRuntime {}
|
||||
|
||||
export interface WxtI18n {
|
||||
// Overriden per-project
|
||||
}
|
||||
/**
|
||||
* This interface is empty because it is generated per-project when running `wxt prepare`. See:
|
||||
* - `.wxt/types/i18n.d.ts`
|
||||
*/
|
||||
export interface WxtI18n {}
|
||||
|
||||
export const browser: AugmentedBrowser = originalBrowser;
|
||||
|
||||
|
||||
@@ -237,7 +237,9 @@ describe('CLI', () => {
|
||||
mockArgv('zip');
|
||||
await importCli();
|
||||
|
||||
expect(zipMock).toBeCalledWith({});
|
||||
expect(zipMock).toBeCalledWith({
|
||||
zip: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should respect passing a custom root', async () => {
|
||||
@@ -246,6 +248,7 @@ describe('CLI', () => {
|
||||
|
||||
expect(zipMock).toBeCalledWith({
|
||||
root: 'path/to/root',
|
||||
zip: {},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -255,6 +258,7 @@ describe('CLI', () => {
|
||||
|
||||
expect(zipMock).toBeCalledWith({
|
||||
configFile: './path/to/config.ts',
|
||||
zip: {},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -264,6 +268,7 @@ describe('CLI', () => {
|
||||
|
||||
expect(zipMock).toBeCalledWith({
|
||||
mode: 'development',
|
||||
zip: {},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -273,6 +278,7 @@ describe('CLI', () => {
|
||||
|
||||
expect(zipMock).toBeCalledWith({
|
||||
browser: 'firefox',
|
||||
zip: {},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -282,6 +288,7 @@ describe('CLI', () => {
|
||||
|
||||
expect(zipMock).toBeCalledWith({
|
||||
manifestVersion: 2,
|
||||
zip: {},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -291,6 +298,7 @@ describe('CLI', () => {
|
||||
|
||||
expect(zipMock).toBeCalledWith({
|
||||
manifestVersion: 3,
|
||||
zip: {},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -300,6 +308,40 @@ describe('CLI', () => {
|
||||
|
||||
expect(zipMock).toBeCalledWith({
|
||||
debug: true,
|
||||
zip: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass undefined for zipSources when --sources is not passed', async () => {
|
||||
mockArgv('zip');
|
||||
await importCli();
|
||||
|
||||
expect(zipMock).toBeCalledWith({
|
||||
zip: {
|
||||
zipSources: undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass true for zipSources when --sources is passed', async () => {
|
||||
mockArgv('zip', '--sources');
|
||||
await importCli();
|
||||
|
||||
expect(zipMock).toBeCalledWith({
|
||||
zip: {
|
||||
zipSources: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass false for zipSources when --sources=false is passed', async () => {
|
||||
mockArgv('zip', '--sources=false');
|
||||
await importCli();
|
||||
|
||||
expect(zipMock).toBeCalledWith({
|
||||
zip: {
|
||||
zipSources: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import { printHeader } from '../core/utils/log';
|
||||
import { formatDuration } from '../core/utils/time';
|
||||
import { ValidationError } from '../core/utils/validation';
|
||||
import { registerWxt } from '../core/wxt';
|
||||
import spawn from 'nano-spawn';
|
||||
|
||||
/**
|
||||
* Wrap an action handler to add a timer, error handling, and maybe enable debug mode.
|
||||
@@ -87,8 +88,7 @@ export function createAliasedCommand(
|
||||
const args = process.argv.slice(
|
||||
process.argv.indexOf(aliasedCommand.name) + 1,
|
||||
);
|
||||
const { execa } = await import('execa');
|
||||
await execa(bin, args, {
|
||||
await spawn(bin, args, {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} catch {
|
||||
|
||||
@@ -95,6 +95,7 @@ cli
|
||||
.option('-b, --browser <browser>', 'specify a browser')
|
||||
.option('--mv3', 'target manifest v3')
|
||||
.option('--mv2', 'target manifest v2')
|
||||
.option('--sources', 'always create sources zip')
|
||||
.action(
|
||||
wrapAction(async (root, flags) => {
|
||||
await zip({
|
||||
@@ -104,6 +105,9 @@ cli
|
||||
manifestVersion: flags.mv3 ? 3 : flags.mv2 ? 2 : undefined,
|
||||
configFile: flags.config,
|
||||
debug: flags.debug,
|
||||
zip: {
|
||||
zipSources: flags.sources,
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -59,6 +59,11 @@ export async function createViteBuilder(
|
||||
config.build.sourcemap = 'inline';
|
||||
}
|
||||
|
||||
config.server ??= {};
|
||||
config.server.watch = {
|
||||
ignored: [`${wxtConfig.outBaseDir}/**`, `${wxtConfig.wxtDir}/**`],
|
||||
};
|
||||
|
||||
config.plugins ??= [];
|
||||
config.plugins.push(
|
||||
wxtPlugins.download(wxtConfig),
|
||||
@@ -287,9 +292,6 @@ export async function createViteBuilder(
|
||||
strictPort: true,
|
||||
host: info.hostname,
|
||||
origin: info.origin,
|
||||
watch: {
|
||||
ignored: [`${wxtConfig.outBaseDir}/**`, `${wxtConfig.wxtDir}/**`],
|
||||
},
|
||||
},
|
||||
};
|
||||
const baseConfig = await getBaseConfig();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { debounce } from 'perfect-debounce';
|
||||
import {
|
||||
BuildStepOutput,
|
||||
EntrypointGroup,
|
||||
@@ -144,7 +145,7 @@ function createFileReloader(server: WxtDevServer) {
|
||||
const fileChangedMutex = new Mutex();
|
||||
const changeQueue: Array<[string, string]> = [];
|
||||
|
||||
return async (event: string, path: string) => {
|
||||
const cb = async (event: string, path: string) => {
|
||||
changeQueue.push([event, path]);
|
||||
|
||||
await fileChangedMutex.runExclusive(async () => {
|
||||
@@ -211,11 +212,16 @@ function createFileReloader(server: WxtDevServer) {
|
||||
wxt.logger.success(`Reloaded: ${getFilenameList(rebuiltNames)}`);
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
} catch {
|
||||
// Catch build errors instead of crashing. Don't log error either, builder should have already logged it
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return debounce(cb, wxt.config.dev.server!.watchDebounce, {
|
||||
leading: true,
|
||||
trailing: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+8
-14
@@ -1,23 +1,18 @@
|
||||
import { Entrypoint, WxtDirEntry, WxtDirFileEntry } from '../../../types';
|
||||
import { Entrypoint, WxtDirEntry, WxtDirFileEntry } from '../types';
|
||||
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 { getEntrypointBundlePath, isHtmlEntrypoint } from './utils/entrypoints';
|
||||
import { getEntrypointGlobals, getGlobals } from './utils/globals';
|
||||
import { normalizePath } from './utils/paths';
|
||||
import path from 'node:path';
|
||||
import { Message, parseI18nMessages } from '../../utils/i18n';
|
||||
import { writeFileIfDifferent, getPublicFiles } from '../../utils/fs';
|
||||
import { wxt } from '../../wxt';
|
||||
import { Message, parseI18nMessages } from './utils/i18n';
|
||||
import { writeFileIfDifferent, getPublicFiles } from './utils/fs';
|
||||
import { wxt } from './wxt';
|
||||
|
||||
/**
|
||||
* Generate and write all the files inside the `InternalConfig.typesDir` directory.
|
||||
*/
|
||||
export async function generateTypesDir(
|
||||
entrypoints: Entrypoint[],
|
||||
): Promise<void> {
|
||||
export async function generateWxtDir(entrypoints: Entrypoint[]): Promise<void> {
|
||||
await fs.ensureDir(wxt.config.typesDir);
|
||||
|
||||
const entries: WxtDirEntry[] = [
|
||||
@@ -201,7 +196,6 @@ async function getGlobalsDeclarationEntry(): Promise<WxtDirFileEntry> {
|
||||
path: 'types/globals.d.ts',
|
||||
text: [
|
||||
'// Generated by wxt',
|
||||
'export {}',
|
||||
'interface ImportMetaEnv {',
|
||||
...globals.map((global) => ` readonly ${global.name}: ${global.type};`),
|
||||
'}',
|
||||
@@ -99,53 +99,74 @@ interface Template {
|
||||
}
|
||||
|
||||
async function listTemplates(): Promise<Template[]> {
|
||||
try {
|
||||
const res = await fetch('https://ungh.cc/repos/wxt-dev/wxt/files/main');
|
||||
if (res.status >= 300)
|
||||
throw Error(`Request failed with status ${res.status} ${res.statusText}`);
|
||||
const templates = await listTemplatesUngh().catch((err) => {
|
||||
consola.debug('Failed to load templates via ungh:', err);
|
||||
return listTemplatesGithub();
|
||||
});
|
||||
return templates.sort((l, r) => {
|
||||
const lWeight = TEMPLATE_SORT_WEIGHT[l.name] ?? Number.MAX_SAFE_INTEGER;
|
||||
const rWeight = TEMPLATE_SORT_WEIGHT[r.name] ?? Number.MAX_SAFE_INTEGER;
|
||||
const diff = lWeight - rWeight;
|
||||
if (diff !== 0) return diff;
|
||||
return l.name.localeCompare(r.name);
|
||||
});
|
||||
}
|
||||
|
||||
const data = (await res.json()) as {
|
||||
meta: {
|
||||
sha: string;
|
||||
};
|
||||
files: Array<{
|
||||
path: string;
|
||||
mode: string;
|
||||
sha: string;
|
||||
size: number;
|
||||
}>;
|
||||
async function listTemplatesUngh(): Promise<Template[]> {
|
||||
const res = await fetch('https://ungh.cc/repos/wxt-dev/wxt/files/main');
|
||||
if (res.status !== 200)
|
||||
throw Error(
|
||||
`Request failed with status ${res.status} ${res.statusText}: ${await res.text()}`,
|
||||
);
|
||||
|
||||
const data = (await res.json()) as {
|
||||
meta: {
|
||||
sha: string;
|
||||
};
|
||||
return data.files
|
||||
.map((item) => item.path.match(/templates\/(.+)\/package\.json/)?.[1])
|
||||
.filter((name) => name != null)
|
||||
.map((name) => ({ name: name!, path: `templates/${name}` }))
|
||||
.sort((l, r) => {
|
||||
const lWeight = TEMPLATE_SORT_WEIGHT[l.name] ?? Number.MAX_SAFE_INTEGER;
|
||||
const rWeight = TEMPLATE_SORT_WEIGHT[r.name] ?? Number.MAX_SAFE_INTEGER;
|
||||
const diff = lWeight - rWeight;
|
||||
if (diff !== 0) return diff;
|
||||
return l.name.localeCompare(r.name);
|
||||
});
|
||||
} catch (err) {
|
||||
consola.error(err);
|
||||
throw Error(`Failed to load templates`);
|
||||
}
|
||||
files: Array<{
|
||||
path: string;
|
||||
mode: string;
|
||||
sha: string;
|
||||
size: number;
|
||||
}>;
|
||||
};
|
||||
return data.files
|
||||
.map((item) => item.path.match(/templates\/(.+)\/package\.json/)?.[1])
|
||||
.filter((name) => name != null)
|
||||
.map((name) => ({ name: name!, path: `templates/${name}` }));
|
||||
}
|
||||
|
||||
async function listTemplatesGithub(): Promise<Template[]> {
|
||||
const res = await fetch(
|
||||
`https://api.github.com/repos/${REPO}/contents/templates`,
|
||||
{ headers: { Accept: 'application/vnd.github+json' } },
|
||||
);
|
||||
if (res.status !== 200)
|
||||
throw Error(
|
||||
`Request failed with status ${res.status} ${res.statusText}: ${await res.text()}`,
|
||||
);
|
||||
|
||||
// Schema is Example4 of https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#get-repository-content
|
||||
return (await res.json()) as Array<{
|
||||
name: string;
|
||||
path: string;
|
||||
sha: string;
|
||||
size: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
async function cloneProject({
|
||||
directory,
|
||||
template,
|
||||
packageManager,
|
||||
}: {
|
||||
directory: string;
|
||||
template: Template;
|
||||
packageManager: string;
|
||||
}) {
|
||||
const { default: ora } = await import('ora');
|
||||
const spinner = ora('Downloading template').start();
|
||||
try {
|
||||
// 1. Clone repo
|
||||
await downloadTemplate(`gh:wxt-dev/wxt/${template.path}`, {
|
||||
await downloadTemplate(`gh:${REPO}/${template.path}`, {
|
||||
dir: directory,
|
||||
force: true,
|
||||
});
|
||||
@@ -180,3 +201,5 @@ const TEMPLATE_SORT_WEIGHT: Record<string, number> = {
|
||||
vue: 1,
|
||||
react: 2,
|
||||
};
|
||||
|
||||
const REPO = 'wxt-dev/wxt';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
import path from 'node:path';
|
||||
import { npm } from '../npm';
|
||||
import { execaCommand } from 'execa';
|
||||
import spawn from 'nano-spawn';
|
||||
import { exists } from 'fs-extra';
|
||||
|
||||
describe('NPM Package Management Utils', () => {
|
||||
@@ -9,7 +9,7 @@ describe('NPM Package Management Utils', () => {
|
||||
const cwd = path.resolve(__dirname, 'fixtures/simple-npm-project');
|
||||
beforeAll(async () => {
|
||||
// NPM needs the modules installed for 'npm ls' to work
|
||||
await execaCommand('npm i', { cwd });
|
||||
await spawn('npm', ['i'], { cwd });
|
||||
}, 60e3);
|
||||
|
||||
it('should list direct dependencies', async () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
import path from 'node:path';
|
||||
import { pnpm } from '../pnpm';
|
||||
import { execaCommand } from 'execa';
|
||||
import spawn from 'nano-spawn';
|
||||
|
||||
process.env.WXT_PNPM_IGNORE_WORKSPACE = 'true';
|
||||
|
||||
@@ -10,7 +10,7 @@ describe('PNPM Package Management Utils', () => {
|
||||
const cwd = path.resolve(__dirname, 'fixtures/simple-pnpm-project');
|
||||
beforeAll(async () => {
|
||||
// PNPM needs the modules installed, or 'pnpm ls' will return a blank list.
|
||||
await execaCommand('pnpm i --ignore-workspace', { cwd });
|
||||
await spawn('pnpm', ['i', '--ignore-workspace'], { cwd });
|
||||
});
|
||||
|
||||
it('should list direct dependencies', async () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { dedupeDependencies, npm } from './npm';
|
||||
import { WxtPackageManagerImpl } from './types';
|
||||
import spawn from 'nano-spawn';
|
||||
|
||||
export const bun: WxtPackageManagerImpl = {
|
||||
overridesKey: 'overrides', // But also supports "resolutions"
|
||||
@@ -11,8 +12,7 @@ export const bun: WxtPackageManagerImpl = {
|
||||
if (options?.all) {
|
||||
args.push('--all');
|
||||
}
|
||||
const { execa } = await import('execa');
|
||||
const res = await execa('bun', args, { cwd: options?.cwd });
|
||||
const res = await spawn('bun', args, { cwd: options?.cwd });
|
||||
return dedupeDependencies(
|
||||
res.stdout
|
||||
.split('\n')
|
||||
|
||||
@@ -2,13 +2,13 @@ import { Dependency } from '../../types';
|
||||
import { WxtPackageManagerImpl } from './types';
|
||||
import path from 'node:path';
|
||||
import { ensureDir } from 'fs-extra';
|
||||
import spawn from 'nano-spawn';
|
||||
|
||||
export const npm: WxtPackageManagerImpl = {
|
||||
overridesKey: 'overrides',
|
||||
async downloadDependency(id, downloadDir) {
|
||||
await ensureDir(downloadDir);
|
||||
const { execa } = await import('execa');
|
||||
const res = await execa('npm', ['pack', id, '--json'], {
|
||||
const res = await spawn('npm', ['pack', id, '--json'], {
|
||||
cwd: downloadDir,
|
||||
});
|
||||
const packed: PackedDependency[] = JSON.parse(res.stdout);
|
||||
@@ -19,8 +19,7 @@ export const npm: WxtPackageManagerImpl = {
|
||||
if (options?.all) {
|
||||
args.push('--depth', 'Infinity');
|
||||
}
|
||||
const { execa } = await import('execa');
|
||||
const res = await execa('npm', args, { cwd: options?.cwd });
|
||||
const res = await spawn('npm', args, { cwd: options?.cwd });
|
||||
const project: NpmListProject = JSON.parse(res.stdout);
|
||||
|
||||
return flattenNpmListOutput([project]);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NpmListProject, flattenNpmListOutput, npm } from './npm';
|
||||
import { WxtPackageManagerImpl } from './types';
|
||||
import spawn from 'nano-spawn';
|
||||
|
||||
export const pnpm: WxtPackageManagerImpl = {
|
||||
overridesKey: 'resolutions', // "pnpm.overrides" has a higher priority, but I don't want to deal with nesting
|
||||
@@ -19,8 +20,7 @@ export const pnpm: WxtPackageManagerImpl = {
|
||||
) {
|
||||
args.push('--ignore-workspace');
|
||||
}
|
||||
const { execa } = await import('execa');
|
||||
const res = await execa('pnpm', args, { cwd: options?.cwd });
|
||||
const res = await spawn('pnpm', args, { cwd: options?.cwd });
|
||||
const projects: NpmListProject[] = JSON.parse(res.stdout);
|
||||
|
||||
return flattenNpmListOutput(projects);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Dependency } from '../../types';
|
||||
import { WxtPackageManagerImpl } from './types';
|
||||
import { dedupeDependencies, npm } from './npm';
|
||||
import spawn from 'nano-spawn';
|
||||
|
||||
export const yarn: WxtPackageManagerImpl = {
|
||||
overridesKey: 'resolutions',
|
||||
@@ -12,8 +13,7 @@ export const yarn: WxtPackageManagerImpl = {
|
||||
if (options?.all) {
|
||||
args.push('--depth', 'Infinity');
|
||||
}
|
||||
const { execa } = await import('execa');
|
||||
const res = await execa('yarn', args, { cwd: options?.cwd });
|
||||
const res = await spawn('yarn', args, { cwd: options?.cwd });
|
||||
const tree = res.stdout
|
||||
.split('\n')
|
||||
.map<JsonLine>((line) => JSON.parse(line))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { InlineConfig } from '../types';
|
||||
import { findEntrypoints, generateTypesDir } from './utils/building';
|
||||
import { findEntrypoints } from './utils/building';
|
||||
import { generateWxtDir } from './generate-wxt-dir';
|
||||
import { registerWxt, wxt } from './wxt';
|
||||
|
||||
export async function prepare(config: InlineConfig) {
|
||||
@@ -7,5 +8,5 @@ export async function prepare(config: InlineConfig) {
|
||||
wxt.logger.info('Generating types...');
|
||||
|
||||
const entrypoints = await findEntrypoints();
|
||||
await generateTypesDir(entrypoints);
|
||||
await generateWxtDir(entrypoints);
|
||||
}
|
||||
|
||||
+16
-8
@@ -14,17 +14,19 @@ import {
|
||||
WxtModuleWithMetadata,
|
||||
ResolvedEslintrc,
|
||||
Eslintrc,
|
||||
} from '../../../types';
|
||||
} from '../types';
|
||||
import path from 'node:path';
|
||||
import { createFsCache } from '../../utils/cache';
|
||||
import { createFsCache } from './utils/cache';
|
||||
import consola, { LogLevels } from 'consola';
|
||||
import defu from 'defu';
|
||||
import { NullablyRequired } from '../types';
|
||||
import { NullablyRequired } from './utils/types';
|
||||
import fs from 'fs-extra';
|
||||
import { normalizePath } from '../paths';
|
||||
import { normalizePath } from './utils/paths';
|
||||
import glob from 'fast-glob';
|
||||
import { builtinModules } from '../../../builtin-modules';
|
||||
import { getEslintVersion } from '../eslint';
|
||||
import { builtinModules } from '../builtin-modules';
|
||||
import { getEslintVersion } from './utils/eslint';
|
||||
import { safeStringToNumber } from './utils/number';
|
||||
import { loadEnv } from './utils/env';
|
||||
|
||||
/**
|
||||
* Given an inline config, discover the config file if necessary, merge the results, resolve any
|
||||
@@ -72,6 +74,8 @@ export async function resolveConfig(
|
||||
const mode = mergedConfig.mode ?? COMMAND_MODES[command];
|
||||
const env: ConfigEnv = { browser, command, manifestVersion, mode };
|
||||
|
||||
loadEnv(mode); // Load any environment variables used below
|
||||
|
||||
const root = path.resolve(
|
||||
inlineConfig.root ?? userConfig.root ?? process.cwd(),
|
||||
);
|
||||
@@ -86,7 +90,7 @@ export async function resolveConfig(
|
||||
if (await isDirMissing(entrypointsDir)) {
|
||||
logMissingDir(logger, 'Entrypoints', entrypointsDir);
|
||||
}
|
||||
const filterEntrypoints = !!mergedConfig.filterEntrypoints?.length
|
||||
const filterEntrypoints = mergedConfig.filterEntrypoints?.length
|
||||
? new Set(mergedConfig.filterEntrypoints)
|
||||
: undefined;
|
||||
const publicDir = path.resolve(srcDir, mergedConfig.publicDir ?? 'public');
|
||||
@@ -124,6 +128,7 @@ export async function resolveConfig(
|
||||
devServerConfig = {
|
||||
port,
|
||||
hostname: mergedConfig.dev?.server?.hostname ?? 'localhost',
|
||||
watchDebounce: safeStringToNumber(process.env.WXT_WATCH_DEBOUNCE) ?? 800,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -174,7 +179,7 @@ export async function resolveConfig(
|
||||
srcDir,
|
||||
typesDir,
|
||||
wxtDir,
|
||||
zip: resolveZipConfig(root, outBaseDir, mergedConfig),
|
||||
zip: resolveZipConfig(root, browser, outBaseDir, mergedConfig),
|
||||
transformManifest: mergedConfig.transformManifest,
|
||||
analysis: resolveAnalysisConfig(root, mergedConfig),
|
||||
userConfigMetadata: userConfigMetadata ?? {},
|
||||
@@ -253,6 +258,7 @@ async function mergeInlineConfig(
|
||||
|
||||
function resolveZipConfig(
|
||||
root: string,
|
||||
browser: string,
|
||||
outBaseDir: string,
|
||||
mergedConfig: InlineConfig,
|
||||
): NullablyRequired<ResolvedConfig['zip']> {
|
||||
@@ -265,6 +271,8 @@ function resolveZipConfig(
|
||||
includeSources: [],
|
||||
compressionLevel: 9,
|
||||
...mergedConfig.zip,
|
||||
zipSources:
|
||||
mergedConfig.zip?.zipSources ?? ['firefox', 'opera'].includes(browser),
|
||||
exclude: mergedConfig.zip?.exclude ?? [],
|
||||
excludeSources: [
|
||||
'**/node_modules',
|
||||
@@ -0,0 +1,18 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { safeStringToNumber } from '../number';
|
||||
|
||||
describe('Number Utils', () => {
|
||||
describe('safeStringToNumber', () => {
|
||||
it.each([
|
||||
{ arg: '1000', expected: 1000 },
|
||||
{ arg: '', expected: 0 },
|
||||
{ arg: '12abc', expected: null },
|
||||
{ arg: undefined, expected: null },
|
||||
])(
|
||||
'should be safely converted from string to number: safeStringToNumber($arg) -> $expected',
|
||||
({ arg, expected }) => {
|
||||
expect(safeStringToNumber(arg)).toBe(expected);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -21,9 +21,10 @@ describe('String utils', () => {
|
||||
|
||||
describe('safeVarName', () => {
|
||||
it.each([
|
||||
['Hello world!', '_hello_world'],
|
||||
['Hello world!', 'helloWorld'],
|
||||
['123', '_123'],
|
||||
['abc-123', '_abc_123'],
|
||||
['abc-123', 'abc123'],
|
||||
['abc-123-xyz', 'abc123Xyz'],
|
||||
['', '_'],
|
||||
[' ', '_'],
|
||||
['_', '_'],
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
export * from './build-entrypoints';
|
||||
export * from './detect-dev-changes';
|
||||
export * from './find-entrypoints';
|
||||
export * from './generate-wxt-dir';
|
||||
export * from './resolve-config';
|
||||
export * from './group-entrypoints';
|
||||
export * from './import-entrypoint';
|
||||
export * from './internal-build';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Manifest } from 'wxt/browser';
|
||||
import { BuildOutput, Entrypoint, EntrypointGroup } from '../../../types';
|
||||
import { generateTypesDir } from './generate-wxt-dir';
|
||||
import { generateWxtDir } from '../../generate-wxt-dir';
|
||||
import { buildEntrypoints } from './build-entrypoints';
|
||||
import { generateManifest, writeManifest } from '../../utils/manifest';
|
||||
import { wxt } from '../../wxt';
|
||||
@@ -37,7 +37,7 @@ export async function rebuild(
|
||||
const spinner = ora(`Preparing...`).start();
|
||||
|
||||
// Update types directory with new files and types
|
||||
await generateTypesDir(allEntrypoints).catch((err) => {
|
||||
await generateWxtDir(allEntrypoints).catch((err) => {
|
||||
wxt.logger.warn('Failed to update .wxt directory:', err);
|
||||
// Throw the error if doing a regular build, don't for dev mode.
|
||||
if (wxt.config.command === 'build') throw err;
|
||||
@@ -47,14 +47,16 @@ export async function rebuild(
|
||||
const newOutput = await buildEntrypoints(entrypointGroups, spinner);
|
||||
const mergedOutput: Omit<BuildOutput, 'manifest'> = {
|
||||
steps: [...existingOutput.steps, ...newOutput.steps],
|
||||
publicAssets: [...existingOutput.publicAssets, ...newOutput.publicAssets],
|
||||
// Do not merge existing because all publicAssets copied everytime
|
||||
publicAssets: newOutput.publicAssets,
|
||||
};
|
||||
|
||||
const { manifest: newManifest, warnings: manifestWarnings } =
|
||||
await generateManifest(allEntrypoints, mergedOutput);
|
||||
|
||||
const finalOutput: BuildOutput = {
|
||||
manifest: newManifest,
|
||||
...newOutput,
|
||||
...mergedOutput,
|
||||
};
|
||||
|
||||
// Write manifest
|
||||
@@ -64,14 +66,7 @@ export async function rebuild(
|
||||
spinner.clear().stop();
|
||||
|
||||
return {
|
||||
output: {
|
||||
manifest: newManifest,
|
||||
steps: [...existingOutput.steps, ...finalOutput.steps],
|
||||
publicAssets: [
|
||||
...existingOutput.publicAssets,
|
||||
...finalOutput.publicAssets,
|
||||
],
|
||||
},
|
||||
output: finalOutput,
|
||||
manifest: newManifest,
|
||||
warnings: manifestWarnings,
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ export function getEntrypointName(
|
||||
): string {
|
||||
const relativePath = path.relative(entrypointsDir, inputPath);
|
||||
// Grab the string up to the first . or / or \\
|
||||
const name = relativePath.split(/[\.\/\\]/, 2)[0];
|
||||
const name = relativePath.split(/[./\\]/, 2)[0];
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { config } from 'dotenv';
|
||||
|
||||
/**
|
||||
* Load environment files based on the current mode.
|
||||
*/
|
||||
export function loadEnv(mode: string) {
|
||||
return config({
|
||||
path: [`.env.${mode}.local`, `.env.${mode}`, `.env.local`, `.env`],
|
||||
});
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export function applyGlobals(globals: EnvGlobals): void {
|
||||
try {
|
||||
// @ts-expect-error: Untyped key on globalThis
|
||||
globalThis[key] = envValue;
|
||||
} catch (err) {
|
||||
} catch {
|
||||
// ignore any globals that can't be set
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,7 +5,8 @@ export async function getEslintVersion(): Promise<string[]> {
|
||||
);
|
||||
const { ESLint } = require('eslint');
|
||||
return ESLint.version?.split('.') ?? [];
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// Return an empty version when there's an error importing ESLint
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,14 +272,14 @@ function addEntrypoints(
|
||||
options.browser_style = popup.options.browserStyle;
|
||||
if (manifest.manifest_version === 3) {
|
||||
manifest.action = {
|
||||
...(manifest.action ?? {}),
|
||||
...manifest.action,
|
||||
...options,
|
||||
default_popup,
|
||||
};
|
||||
} else {
|
||||
const key = popup.options.mv2Key ?? 'browser_action';
|
||||
manifest[key] = {
|
||||
...(manifest[key] ?? {}),
|
||||
...manifest[key],
|
||||
...options,
|
||||
default_popup,
|
||||
};
|
||||
@@ -434,8 +434,8 @@ function discoverIcons(
|
||||
/^icon@([0-9]+)w\.png$/, // icon@16w.png
|
||||
/^icon@([0-9]+)h\.png$/, // icon@16h.png
|
||||
/^icon@([0-9]+)\.png$/, // icon@16.png
|
||||
/^icons?[\/\\]([0-9]+)\.png$/, // icon/16.png | icons/16.png
|
||||
/^icons?[\/\\]([0-9]+)x[0-9]+\.png$/, // icon/16x16.png | icons/16x16.png
|
||||
/^icons?[/\\]([0-9]+)\.png$/, // icon/16.png | icons/16.png
|
||||
/^icons?[/\\]([0-9]+)x[0-9]+\.png$/, // icon/16x16.png | icons/16x16.png
|
||||
];
|
||||
// #endregion snippet
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export function safeStringToNumber(str: string | undefined): number | null {
|
||||
const num = Number(str);
|
||||
return isNaN(num) ? null : num;
|
||||
}
|
||||
@@ -16,6 +16,7 @@ export async function getPackageJson(): Promise<
|
||||
} catch (err) {
|
||||
wxt.logger.debug(
|
||||
`Failed to read package.json at: ${file}. Returning undefined.`,
|
||||
err,
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { camelCase } from 'scule';
|
||||
|
||||
export function kebabCaseAlphanumeric(str: string): string {
|
||||
return str
|
||||
.toLowerCase()
|
||||
@@ -9,8 +11,17 @@ export function kebabCaseAlphanumeric(str: string): string {
|
||||
* Return a safe variable name for a given string.
|
||||
*/
|
||||
export function safeVarName(str: string): string {
|
||||
// _ prefix to ensure it doesn't start with a number
|
||||
return '_' + kebabCaseAlphanumeric(str.trim()).replace('-', '_');
|
||||
const name = camelCase(kebabCaseAlphanumeric(str));
|
||||
if (name.match(/^[a-z]/)) return name;
|
||||
// _ prefix to ensure it doesn't start with a number or other invalid symbol
|
||||
return '_' + name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string to a valid filename (NOT path), stripping out invalid characters.
|
||||
*/
|
||||
export function safeFilename(str: string): string {
|
||||
return kebabCaseAlphanumeric(str);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -293,6 +293,7 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
|
||||
downloadedPackagesDir: fakeDir(),
|
||||
downloadPackages: [],
|
||||
compressionLevel: 9,
|
||||
zipSources: false,
|
||||
},
|
||||
transformManifest: () => {},
|
||||
userConfigMetadata: {},
|
||||
@@ -311,6 +312,7 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
|
||||
|
||||
export const fakeWxt = fakeObjectCreator<Wxt>(() => ({
|
||||
config: fakeResolvedConfig(),
|
||||
hook: vi.fn(),
|
||||
hooks: mock(),
|
||||
logger: mock(),
|
||||
reloadConfig: vi.fn(),
|
||||
|
||||
@@ -162,9 +162,7 @@ function getSimpleAstJson(ast: any): any {
|
||||
} else if (typeof ast === 'object') {
|
||||
return Object.fromEntries(
|
||||
Object.entries(ast)
|
||||
.filter(
|
||||
([key, value]) => key !== 'loc' && key !== 'start' && key !== 'end',
|
||||
)
|
||||
.filter(([key]) => key !== 'loc' && key !== 'start' && key !== 'end')
|
||||
.map(([key, value]) => [key, getSimpleAstJson(value)]),
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
WxtHooks,
|
||||
WxtModule,
|
||||
} from '../types';
|
||||
import { resolveConfig } from './utils/building';
|
||||
import { resolveConfig } from './resolve-config';
|
||||
import { createHooks } from 'hookable';
|
||||
import { createWxtPackageManager } from './package-managers';
|
||||
import { createViteBuilder } from './builders/vite';
|
||||
@@ -40,6 +40,7 @@ export async function registerWxt(
|
||||
wxt = {
|
||||
config,
|
||||
hooks,
|
||||
hook: hooks.hook.bind(hooks),
|
||||
get logger() {
|
||||
return config.logger;
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { InlineConfig } from '../types';
|
||||
import path from 'node:path';
|
||||
import fs from 'fs-extra';
|
||||
import { kebabCaseAlphanumeric } from './utils/strings';
|
||||
import { safeFilename } from './utils/strings';
|
||||
import { getPackageJson } from './utils/package';
|
||||
import { minimatch } from 'minimatch';
|
||||
import { formatDuration } from './utils/time';
|
||||
@@ -20,6 +20,7 @@ import { normalizePath } from './utils/paths';
|
||||
export async function zip(config?: InlineConfig): Promise<string[]> {
|
||||
await registerWxt('build', config);
|
||||
const output = await internalBuild();
|
||||
await wxt.hooks.callHook('zip:start', wxt);
|
||||
|
||||
const start = Date.now();
|
||||
wxt.logger.info('Zipping extension...');
|
||||
@@ -27,8 +28,8 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
|
||||
|
||||
const projectName =
|
||||
wxt.config.zip.name ??
|
||||
kebabCaseAlphanumeric(
|
||||
(await getPackageJson())?.name || path.dirname(process.cwd()),
|
||||
safeFilename(
|
||||
(await getPackageJson())?.name || path.basename(process.cwd()),
|
||||
);
|
||||
const applyTemplate = (template: string): string =>
|
||||
template
|
||||
@@ -44,17 +45,17 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
|
||||
await fs.ensureDir(wxt.config.outBaseDir);
|
||||
|
||||
// ZIP output directory
|
||||
|
||||
await wxt.hooks.callHook('zip:extension:start', wxt);
|
||||
const outZipFilename = applyTemplate(wxt.config.zip.artifactTemplate);
|
||||
const outZipPath = path.resolve(wxt.config.outBaseDir, outZipFilename);
|
||||
await zipDir(wxt.config.outDir, outZipPath, {
|
||||
exclude: wxt.config.zip.exclude,
|
||||
});
|
||||
zipFiles.push(outZipPath);
|
||||
await wxt.hooks.callHook('zip:extension:done', wxt, outZipPath);
|
||||
|
||||
// ZIP sources for Firefox
|
||||
|
||||
if (wxt.config.browser === 'firefox') {
|
||||
if (wxt.config.zip.zipSources) {
|
||||
await wxt.hooks.callHook('zip:sources:start', wxt);
|
||||
const { overrides, files: downloadedPackages } =
|
||||
await downloadPrivatePackages();
|
||||
const sourcesZipFilename = applyTemplate(wxt.config.zip.sourcesTemplate);
|
||||
@@ -73,6 +74,7 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
|
||||
additionalFiles: downloadedPackages,
|
||||
});
|
||||
zipFiles.push(sourcesZipPath);
|
||||
await wxt.hooks.callHook('zip:sources:done', wxt, sourcesZipPath);
|
||||
}
|
||||
|
||||
await printFileList(
|
||||
@@ -82,8 +84,11 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
|
||||
zipFiles,
|
||||
);
|
||||
|
||||
await wxt.hooks.callHook('zip:done', wxt, zipFiles);
|
||||
|
||||
return zipFiles;
|
||||
}
|
||||
|
||||
async function zipDir(
|
||||
directory: string,
|
||||
outputPath: string,
|
||||
|
||||
@@ -34,17 +34,21 @@ export function defineWxtModule<TOptions extends WxtModuleOptions>(
|
||||
* the output directory. This will speed up project builds since it just has to
|
||||
* copy some files instead of bundling them.
|
||||
*
|
||||
* To extract entrypoint options from a JS/TS file, use
|
||||
* `wxt.builder.importEntrypoint` (see example).
|
||||
*
|
||||
* @param wxt The wxt instance provided by the module's setup function.
|
||||
* @param entrypoint The entrypoint to be bundled along with the extension.
|
||||
*
|
||||
* @example
|
||||
* export default defineWxtModule((wxt, options) => {
|
||||
* export default defineWxtModule(async (wxt, options) => {
|
||||
* const entrypointPath = "/path/to/my-entrypoint.ts";
|
||||
* addEntrypoint(wxt, {
|
||||
* type: "unlisted-page",
|
||||
* name: "changelog",
|
||||
* inputPath: "wxt-module-changelog/index.html"
|
||||
* outputDir: wxt.config.outputDir,
|
||||
* options: {},
|
||||
* type: "content-script",
|
||||
* name: "some-name",
|
||||
* inputPath: entrypointPath,
|
||||
* outputDir: wxt.config.outDir,
|
||||
* options: await wxt.builder.importEntrypoint(entrypointPath),
|
||||
* });
|
||||
* });
|
||||
*/
|
||||
|
||||
@@ -461,7 +461,16 @@ export interface WxtStorage {
|
||||
* @example
|
||||
* await storage.getItem<number>("local:installDate");
|
||||
*/
|
||||
getItem<T>(key: StorageItemKey, opts?: GetItemOptions<T>): Promise<T | null>;
|
||||
getItem<TValue>(
|
||||
key: StorageItemKey,
|
||||
opts: GetItemOptions<TValue> & { fallback: TValue },
|
||||
): Promise<TValue>;
|
||||
|
||||
getItem<TValue>(
|
||||
key: StorageItemKey,
|
||||
opts?: GetItemOptions<TValue>,
|
||||
): Promise<TValue | null>;
|
||||
|
||||
/**
|
||||
* Get multiple items from storage. The return order is guaranteed to be the same as the order
|
||||
* requested.
|
||||
|
||||
+122
-7
@@ -130,6 +130,16 @@ export interface InlineConfig {
|
||||
* @default "{{name}}-{{version}}-{{browser}}.zip"
|
||||
*/
|
||||
artifactTemplate?: string;
|
||||
/**
|
||||
* When zipping the extension, also zip sources.
|
||||
*
|
||||
* - `undefined`: zip sources if the target browser is "firefox" or "opera"
|
||||
* - `true`: always zip sources
|
||||
* - `false`: never zip sources
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
zipSources?: boolean;
|
||||
/**
|
||||
* Configure the filename output when zipping files.
|
||||
*
|
||||
@@ -830,18 +840,55 @@ export type ResolvedPerBrowserOptions<T, TOmitted extends keyof T = never> = {
|
||||
* Manifest customization available in the `wxt.config.ts` file. You cannot configure entrypoints
|
||||
* here, they are configured inline.
|
||||
*/
|
||||
export type UserManifest = Partial<
|
||||
Omit<
|
||||
Manifest.WebExtensionManifest,
|
||||
export type UserManifest = {
|
||||
[key in keyof chrome.runtime.ManifestV3 as key extends
|
||||
| 'action'
|
||||
| 'background'
|
||||
| 'chrome_url_overrides'
|
||||
| 'devtools_page'
|
||||
| 'manifest_version'
|
||||
| 'options_page'
|
||||
| 'options_ui'
|
||||
| 'permissions'
|
||||
| 'sandbox'
|
||||
>
|
||||
>;
|
||||
| 'web_accessible_resources'
|
||||
? never
|
||||
: key]?: chrome.runtime.ManifestV3[key];
|
||||
} & {
|
||||
// Add any Browser-specific or MV2 properties that WXT supports here
|
||||
action?: chrome.runtime.ManifestV3['action'] & {
|
||||
browser_style?: boolean;
|
||||
};
|
||||
browser_action?: chrome.runtime.ManifestV2['browser_action'] & {
|
||||
browser_style?: boolean;
|
||||
};
|
||||
page_action?: chrome.runtime.ManifestV2['page_action'] & {
|
||||
browser_style?: boolean;
|
||||
};
|
||||
browser_specific_settings?: {
|
||||
gecko?: {
|
||||
id?: string;
|
||||
strict_min_version?: string;
|
||||
strict_max_version?: string;
|
||||
update_url?: string;
|
||||
};
|
||||
gecko_android?: {
|
||||
strict_min_version?: string;
|
||||
strict_max_version?: string;
|
||||
};
|
||||
safari?: {
|
||||
strict_min_version?: string;
|
||||
strict_max_version?: string;
|
||||
};
|
||||
};
|
||||
permissions?: (
|
||||
| chrome.runtime.ManifestPermissions
|
||||
| (string & Record<never, never>)
|
||||
)[];
|
||||
web_accessible_resources?:
|
||||
| string[]
|
||||
| chrome.runtime.ManifestV3['web_accessible_resources'];
|
||||
};
|
||||
|
||||
export type UserManifestFn = (
|
||||
env: ConfigEnv,
|
||||
@@ -930,7 +977,7 @@ export interface ExtensionRunnerConfig {
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
chromiumPref?: string;
|
||||
chromiumPref?: Record<string, any>;
|
||||
/**
|
||||
* By default, chrome opens a random port for debugging. Set this value to use a specific port.
|
||||
*/
|
||||
@@ -1107,6 +1154,9 @@ export interface WxtHooks {
|
||||
) => HookResult;
|
||||
/**
|
||||
* Called once all entrypoints have been loaded from the `entrypointsDir`.
|
||||
* Use `wxt.builder.importEntrypoint` to load entrypoint options from the
|
||||
* file, or manually define them.
|
||||
*
|
||||
* @param wxt The configured WXT object
|
||||
* @param entrypoints The list of entrypoints to be built
|
||||
*/
|
||||
@@ -1124,11 +1174,53 @@ export interface WxtHooks {
|
||||
* @param entrypoints The list of files that will be copied into the output directory
|
||||
*/
|
||||
'build:publicAssets': (wxt: Wxt, files: ResolvedPublicFile[]) => HookResult;
|
||||
/**
|
||||
* Called before the zip process starts.
|
||||
* @param wxt The configured WXT object
|
||||
*/
|
||||
'zip:start': (wxt: Wxt) => HookResult;
|
||||
|
||||
/**
|
||||
* Called before zipping the extension files.
|
||||
* @param wxt The configured WXT object
|
||||
*/
|
||||
'zip:extension:start': (wxt: Wxt) => HookResult;
|
||||
|
||||
/**
|
||||
* Called after zipping the extension files.
|
||||
* @param wxt The configured WXT object
|
||||
* @param zipPath The path to the created extension zip file
|
||||
*/
|
||||
'zip:extension:done': (wxt: Wxt, zipPath: string) => HookResult;
|
||||
|
||||
/**
|
||||
* Called before zipping the source files (for Firefox).
|
||||
* @param wxt The configured WXT object
|
||||
*/
|
||||
'zip:sources:start': (wxt: Wxt) => HookResult;
|
||||
|
||||
/**
|
||||
* Called after zipping the source files (for Firefox).
|
||||
* @param wxt The configured WXT object
|
||||
* @param zipPath The path to the created sources zip file
|
||||
*/
|
||||
'zip:sources:done': (wxt: Wxt, zipPath: string) => HookResult;
|
||||
|
||||
/**
|
||||
* Called after the entire zip process is complete.
|
||||
* @param wxt The configured WXT object
|
||||
* @param zipFiles An array of paths to all created zip files
|
||||
*/
|
||||
'zip:done': (wxt: Wxt, zipFiles: string[]) => HookResult;
|
||||
}
|
||||
|
||||
export interface Wxt {
|
||||
config: ResolvedConfig;
|
||||
hooks: Hookable<WxtHooks>;
|
||||
/**
|
||||
* Alias for `wxt.hooks.hook(...)`.
|
||||
*/
|
||||
hook: Hookable<WxtHooks>['hook'];
|
||||
/**
|
||||
* Alias for config.logger
|
||||
*/
|
||||
@@ -1203,6 +1295,10 @@ export interface ResolvedConfig {
|
||||
downloadPackages: string[];
|
||||
compressionLevel: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
||||
exclude: string[];
|
||||
/**
|
||||
* If true, when zipping the extension, also zip the sources.
|
||||
*/
|
||||
zipSources: boolean;
|
||||
};
|
||||
/**
|
||||
* @deprecated Use `build:manifestGenerated` hook instead.
|
||||
@@ -1233,6 +1329,25 @@ export interface ResolvedConfig {
|
||||
server?: {
|
||||
port: number;
|
||||
hostname: string;
|
||||
/**
|
||||
* The milliseconds to debounce when a file is saved before reloading.
|
||||
* The only way to set this option is to set the `WXT_WATCH_DEBOUNCE`
|
||||
* environment variable, either globally (like in `.bashrc` file) or
|
||||
* per-project (in `.env` file).
|
||||
*
|
||||
* For example:
|
||||
* ```
|
||||
* # ~/.zshrc
|
||||
* export WXT_WATCH_DEBOUNCE=1000
|
||||
* ```
|
||||
* or
|
||||
* ```
|
||||
* # .env
|
||||
* WXT_WATCH_DEBOUNCE=1000
|
||||
* ```
|
||||
* @default 800
|
||||
*/
|
||||
watchDebounce: number;
|
||||
};
|
||||
reloadCommand: string | false;
|
||||
};
|
||||
@@ -1375,7 +1490,7 @@ export interface WxtModule<TOptions extends WxtModuleOptions> {
|
||||
* Alternative to adding hooks in setup function with `wxt.hooks`. Hooks are
|
||||
* added before the `setup` function is called.
|
||||
*/
|
||||
hooks?: Partial<WxtHooks>;
|
||||
hooks?: NestedHooks<WxtHooks>;
|
||||
/**
|
||||
* A custom function that can be used to setup hooks and call module-specific
|
||||
* APIs.
|
||||
|
||||
@@ -6,12 +6,12 @@ export default defineConfig({
|
||||
test: {
|
||||
mockReset: true,
|
||||
restoreMocks: true,
|
||||
setupFiles: ['vitest.setup.ts'],
|
||||
testTimeout: 120e3,
|
||||
coverage: {
|
||||
include: ['src/**'],
|
||||
exclude: ['**/dist', '**/__tests__', 'src/utils/testing'],
|
||||
},
|
||||
globalSetup: ['./vitest.globalSetup.ts'],
|
||||
},
|
||||
server: {
|
||||
watch: {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { exists, rm } from 'fs-extra';
|
||||
|
||||
let setupHappened = false;
|
||||
|
||||
export async function setup() {
|
||||
if (setupHappened) {
|
||||
throw new Error('setup called twice');
|
||||
}
|
||||
|
||||
setupHappened = true;
|
||||
|
||||
// @ts-expect-error
|
||||
globalThis.__ENTRYPOINT__ = 'test';
|
||||
|
||||
const e2eDistPath = './e2e/dist/';
|
||||
if (await exists(e2eDistPath)) {
|
||||
await rm(e2eDistPath, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
// @ts-expect-error
|
||||
globalThis.__ENTRYPOINT__ = 'test';
|
||||
Generated
+1566
-1274
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ import {
|
||||
generateMarkDown,
|
||||
parseChangelogMarkdown,
|
||||
} from 'changelogen';
|
||||
import { execa } from 'execa';
|
||||
import spawn from 'nano-spawn';
|
||||
import { getPkgTag, grabPackageDetails, listCommitsInDir } from './git';
|
||||
import { consola } from 'consola';
|
||||
import fs from 'fs-extra';
|
||||
@@ -36,7 +36,7 @@ if (currentVersion.startsWith('0.')) {
|
||||
bumpType = 'patch';
|
||||
}
|
||||
}
|
||||
await execa('pnpm', ['version', bumpType], {
|
||||
await spawn('pnpm', ['version', bumpType], {
|
||||
cwd: pkgDir,
|
||||
});
|
||||
const updatedPkgJson = await fs.readJson(pkgJsonPath);
|
||||
@@ -82,11 +82,11 @@ await fs.writeFile(changelogPath, newChangelog, 'utf8');
|
||||
consola.success('Updated changelog');
|
||||
|
||||
// Commit changes
|
||||
await execa('git', ['add', pkgJsonPath, changelogPath]);
|
||||
await execa('git', [
|
||||
await spawn('git', ['add', pkgJsonPath, changelogPath]);
|
||||
await spawn('git', [
|
||||
'commit',
|
||||
'-m',
|
||||
`chore(release): ${pkgName} v${newVersion}`,
|
||||
]);
|
||||
await execa('git', ['tag', newTag]);
|
||||
await spawn('git', ['tag', newTag]);
|
||||
consola.success('Committed version and changelog');
|
||||
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# Generate code in README.md and sync it with wxt/packages/README.md for NPM
|
||||
#
|
||||
|
||||
pnpm dlx automd
|
||||
echo "<!-- DO NOT EDIT, THIS FILE WAS GENERATED BY '../../scripts/generate-readmes.sh' -->" > packages/wxt/README.md
|
||||
cat README.md >> packages/wxt/README.md
|
||||
+8
-1
@@ -1,4 +1,11 @@
|
||||
// https://github.com/antfu-collective/taze?tab=readme-ov-file#config-file
|
||||
export default {
|
||||
exclude: ['typedoc', 'typedoc-plugin-markdown', 'typedoc-vitepress-theme'],
|
||||
exclude: [
|
||||
// Very touchy, don't change:
|
||||
'typedoc',
|
||||
'typedoc-plugin-markdown',
|
||||
'typedoc-vitepress-theme',
|
||||
// Manually manage version so a single version is used:
|
||||
'esbuild',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@wxt-dev/module-react": "^1.1.0",
|
||||
"typescript": "^5.5.4",
|
||||
"wxt": "^0.19.1"
|
||||
"@wxt-dev/module-react": "^1.1.1",
|
||||
"typescript": "^5.6.2",
|
||||
"wxt": "^0.19.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
"postinstall": "wxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"solid-js": "^1.8.19"
|
||||
"solid-js": "^1.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@wxt-dev/module-solid": "^1.1.1",
|
||||
"typescript": "^5.5.4",
|
||||
"wxt": "^0.19.1"
|
||||
"@wxt-dev/module-solid": "^1.1.2",
|
||||
"typescript": "^5.6.2",
|
||||
"wxt": "^0.19.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"description": "manifest.json description",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "wxt",
|
||||
"dev:firefox": "wxt -b firefox",
|
||||
@@ -15,11 +16,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/svelte": "^5.0.4",
|
||||
"@wxt-dev/module-svelte": "^1.0.0",
|
||||
"svelte": "^4.2.18",
|
||||
"svelte-check": "^3.8.4",
|
||||
"tslib": "^2.6.3",
|
||||
"typescript": "^5.5.4",
|
||||
"wxt": "^0.19.1"
|
||||
"@wxt-dev/module-svelte": "^1.0.1",
|
||||
"svelte": "^4.2.19",
|
||||
"svelte-check": "^3.8.6",
|
||||
"tslib": "^2.7.0",
|
||||
"typescript": "^5.6.2",
|
||||
"wxt": "^0.19.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"postinstall": "wxt prepare"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.4",
|
||||
"wxt": "^0.19.1"
|
||||
"typescript": "^5.6.2",
|
||||
"wxt": "^0.19.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
"postinstall": "wxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.4.34"
|
||||
"vue": "^3.5.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@wxt-dev/module-vue": "^1.0.0",
|
||||
"typescript": "^5.5.4",
|
||||
"vue-tsc": "^2.0.29",
|
||||
"wxt": "^0.19.1"
|
||||
"@wxt-dev/module-vue": "^1.0.1",
|
||||
"typescript": "^5.6.2",
|
||||
"vue-tsc": "^2.1.6",
|
||||
"wxt": "^0.19.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
"strict": true,
|
||||
"lib": ["DOM", "WebWorker", "ESNext"],
|
||||
|
||||
/* Linting */
|
||||
"noUnusedLocals": true,
|
||||
|
||||
/* Completeness */
|
||||
"skipLibCheck": true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user