Compare commits
134 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 58e11d3c06 | |||
| fddd7c14ee | |||
| 7d755f215f | |||
| 7e473375e5 | |||
| 08b9ea6e47 | |||
| 245aea4ce8 | |||
| e1e41c20ea | |||
| e16052cdc9 | |||
| de2b38217a | |||
| b2a099689f | |||
| 42c8a02ef9 | |||
| 32058027f3 | |||
| 99fc685e14 | |||
| 414117b9e6 | |||
| 799bd92b28 | |||
| d7cc8dc229 | |||
| f4fa36a0ab | |||
| 4d5eb28cb4 | |||
| a008426317 | |||
| 2a212790b6 | |||
| 09f27a902a | |||
| 6cbbca9e92 | |||
| f1fd8fc38b | |||
| a67e1b81e1 | |||
| 936c5f7fda | |||
| ab3151f924 | |||
| d4fb318381 | |||
| 31a7b5892e | |||
| c469f0e85a | |||
| 355d36a838 | |||
| 43e46477df | |||
| 34a235fc68 | |||
| ce9f9af980 | |||
| 5f799ca23e | |||
| 0159b4315e | |||
| 864d7a0bfd | |||
| fa698266b3 | |||
| ba96bbd8ff | |||
| 20d5e3310a | |||
| 70fdb04db5 | |||
| 65c3c7eba9 | |||
| 98e218042d | |||
| 8a56f73c7c | |||
| ffdd15e275 | |||
| 8f71e174cb | |||
| 6621aaf877 | |||
| c528361d07 | |||
| 5376cfd6a9 | |||
| 76615b9262 | |||
| ce551fbdb0 | |||
| 108e790cf8 | |||
| 0b5424cadb | |||
| 48571f5032 | |||
| c6c9f65040 | |||
| ace6c20164 | |||
| d64b64f39e | |||
| 41a687a63e | |||
| f4f6704510 | |||
| 65fc0fadb3 | |||
| 64b686713b | |||
| 9c6d3e37d4 | |||
| 37abe9db39 | |||
| 9ebef4a268 | |||
| e60f7c7e77 | |||
| 36aadd0604 | |||
| e4598a5e37 | |||
| db5c22809c | |||
| c61fa8f858 | |||
| 5fe4681620 | |||
| 80d384361b | |||
| a1e72c0749 | |||
| e3a80b8247 | |||
| c0013acadc | |||
| f472c53da7 | |||
| 032f7931e8 | |||
| 57a12c1711 | |||
| d13ca3983f | |||
| 9769ce4ac9 | |||
| 55dc263dc9 | |||
| e56ae23747 | |||
| bfd4af5d69 | |||
| 9673604878 | |||
| ae55aecb0d | |||
| a14d0b4f2f | |||
| 301e4a17da | |||
| 19cebbf673 | |||
| ef73c07dfe | |||
| ca5ad7e51b | |||
| 5dab944abc | |||
| adaae6e1a4 | |||
| 4524cbdf76 | |||
| 1171cf52f0 | |||
| c5978b1072 | |||
| e5ef9367ed | |||
| 0ca908a6cc | |||
| e20c012eb3 | |||
| e88df26d7c | |||
| 9491e6845e | |||
| e4433c4212 | |||
| c6fdc5a5d5 | |||
| 68dd3fc027 | |||
| cd7aec6752 | |||
| b80556498d | |||
| 38530cdb39 | |||
| 078fd5990e | |||
| b4be5feaa6 | |||
| d982f97bd8 | |||
| 653608c98c | |||
| d63be2d6f3 | |||
| 4500ca577f | |||
| cc935daec0 | |||
| 51ff46ec58 | |||
| 206b7731cf | |||
| d16f59d33d | |||
| a5ec046346 | |||
| 51dd803115 | |||
| 6e39424a30 | |||
| 6ca84782aa | |||
| ee1ba6369e | |||
| f35d70e6fe | |||
| 430a3acfeb | |||
| 395c5841f9 | |||
| 173161ca4d | |||
| a1a8e14368 | |||
| 2379806c61 | |||
| f604208a8b | |||
| cf31fbec5d | |||
| 279be8deb7 | |||
| 7366d8aab7 | |||
| a7ab409d48 | |||
| 9dea0cf3ae | |||
| 50d7c1ec80 | |||
| f32cdafdf6 | |||
| 2e2272d07a |
@@ -7,6 +7,7 @@
|
||||
pnpm-lock.yaml linguist-generated
|
||||
package-lock.json linguist-generated
|
||||
bun.lockb linguist-generated
|
||||
bun.lock linguist-generated
|
||||
yarn.lock linguist-generated
|
||||
|
||||
# Exclude templates from language statistics
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# Set default
|
||||
* @aklinker1 @Timeraa
|
||||
* @aklinker1
|
||||
|
||||
# Secure Directories
|
||||
/.github/ @aklinker1
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
name: Basic Setup
|
||||
description: Install PNPM, Node, and dependencies
|
||||
description: Install Bun and dependencies
|
||||
|
||||
inputs:
|
||||
install:
|
||||
default: 'true'
|
||||
description: Whether or not to run 'pnpm install'
|
||||
|
||||
installArgs:
|
||||
default: ''
|
||||
description: Additional args to append to "pnpm install"
|
||||
description: Whether or not to run 'bun install'
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: 🛠️ Setup PNPM
|
||||
uses: pnpm/action-setup@f2b2b233b538f500472c7274c7012f57857d8ce0 # v4.1.0
|
||||
|
||||
- name: 🛠️ Setup NodeJS
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
- name: 🛠️ Setup Bun
|
||||
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
|
||||
with:
|
||||
node-version: 20
|
||||
cache: pnpm
|
||||
bun-version-file: '.tool-versions'
|
||||
|
||||
- name: 📦 Install Dependencies
|
||||
if: ${{ inputs.install == 'true' }}
|
||||
shell: bash
|
||||
run: pnpm install ${{ inputs.installArgs }}
|
||||
run: bun ci ${{ inputs.installArgs }}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
name: ✨ Auto-label PR
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronized, reopened]
|
||||
|
||||
jobs:
|
||||
update-pr:
|
||||
name: Update PR
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Gather Info
|
||||
id: check
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
script: |
|
||||
const { data: pr } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.payload.pull_request.number
|
||||
});
|
||||
|
||||
// Check if PR has assignees
|
||||
const hasAssignees = pr.assignees && pr.assignees.length > 0;
|
||||
core.setOutput('has_assignees', hasAssignees);
|
||||
core.setOutput('author', pr.user.login);
|
||||
|
||||
// Get list of changed files
|
||||
const { data: files } = await github.rest.pulls.listFiles({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.payload.pull_request.number
|
||||
});
|
||||
|
||||
// Find all packages that were modified
|
||||
const packagesRegex = /^packages\/([^\/]+)\//;
|
||||
const affectedPackages = new Set();
|
||||
|
||||
for (const file of files) {
|
||||
const match = file.filename.match(packagesRegex);
|
||||
if (match) {
|
||||
affectedPackages.add(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
const labels = Array.from(affectedPackages).map(pkg => `pkg/${pkg}`);
|
||||
core.setOutput('labels', JSON.stringify(labels));
|
||||
console.log('Detected package labels:', labels);
|
||||
|
||||
// Get current labels on the PR that match pkg/* pattern
|
||||
const currentPkgLabels = pr.labels
|
||||
.map(label => label.name)
|
||||
.filter(name => name.startsWith('pkg/'));
|
||||
|
||||
core.setOutput('current_pkg_labels', JSON.stringify(currentPkgLabels));
|
||||
console.log('Current pkg labels:', currentPkgLabels);
|
||||
|
||||
- name: Sync Author
|
||||
if: steps.check.outputs.has_assignees == 'false'
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.addAssignees({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
assignees: ['${{ steps.check.outputs.author }}']
|
||||
});
|
||||
|
||||
console.log('Assigned PR author: ${{ steps.check.outputs.author }}');
|
||||
|
||||
- name: Sync Labels
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
script: |
|
||||
const newLabels = ${{ steps.check.outputs.labels }};
|
||||
const currentLabels = ${{ steps.check.outputs.current_pkg_labels }};
|
||||
|
||||
// Find labels to add (in newLabels but not in currentLabels)
|
||||
const labelsToAdd = newLabels.filter(label => !currentLabels.includes(label));
|
||||
|
||||
// Find labels to remove (in currentLabels but not in newLabels)
|
||||
const labelsToRemove = currentLabels.filter(label => !newLabels.includes(label));
|
||||
|
||||
// Add new labels
|
||||
if (labelsToAdd.length > 0) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
labels: labelsToAdd
|
||||
});
|
||||
console.log('Added labels:', labelsToAdd);
|
||||
}
|
||||
|
||||
// Remove obsolete labels
|
||||
for (const label of labelsToRemove) {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
name: label
|
||||
});
|
||||
console.log('Removed label:', label);
|
||||
}
|
||||
|
||||
if (labelsToAdd.length === 0 && labelsToRemove.length === 0) {
|
||||
console.log('No label changes needed');
|
||||
}
|
||||
@@ -23,7 +23,12 @@ jobs:
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Build All Packages
|
||||
run: pnpm buildc all
|
||||
run: bun run buildc all
|
||||
|
||||
- name: Publish
|
||||
run: pnpx pkg-pr-new publish --compact --pnpm './packages/*'
|
||||
run: bun pkg-pr-new publish --compact --bun './packages/*'
|
||||
|
||||
- name: DEBUG
|
||||
if: always()
|
||||
run: ls -lhA
|
||||
working-directory: packages/analytics
|
||||
|
||||
@@ -15,7 +15,20 @@ jobs:
|
||||
if: github.event.pull_request.merged == true
|
||||
|
||||
steps:
|
||||
- name: Log PR Author Info
|
||||
run: |
|
||||
echo "PR Author Login: ${{ github.event.pull_request.user.login }}"
|
||||
echo "PR Author ID: ${{ github.event.pull_request.user.id }}"
|
||||
echo "PR Author Type: ${{ github.event.pull_request.user.type }}"
|
||||
echo "PR Author Node ID: ${{ github.event.pull_request.user.node_id }}"
|
||||
|
||||
- name: Post Thank You Comment
|
||||
if: >-
|
||||
github.event.pull_request.user.login != 'aklinker1' &&
|
||||
github.event.pull_request.user.login != 'Timeraa' &&
|
||||
github.event.pull_request.user.login != 'PatrykKuniczak' &&
|
||||
github.event.pull_request.user.login != 'dependabot[bot]' &&
|
||||
github.event.pull_request.user.login != 'copilot-swe-agent[bot]'
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
comment: Thanks for helping make WXT better!
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Login to Docker Registry
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: https://${{ secrets.DOCKER_REGISTRY_HOSTNAME }}
|
||||
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
|
||||
- name: Build docs
|
||||
run: |
|
||||
pnpm docs:build
|
||||
bun run docs:build
|
||||
docker build docs/.vitepress -t ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
|
||||
|
||||
- name: Push Image
|
||||
|
||||
@@ -10,6 +10,7 @@ on:
|
||||
- analytics
|
||||
- auto-icons
|
||||
- i18n
|
||||
- is-background
|
||||
- module-react
|
||||
- module-solid
|
||||
- module-svelte
|
||||
@@ -55,18 +56,18 @@ jobs:
|
||||
|
||||
- name: Bump and Tag
|
||||
run: |
|
||||
pnpm tsx scripts/bump-package-version.ts ${{ inputs.package }}
|
||||
bun run scripts/bump-package-version.ts ${{ inputs.package }}
|
||||
git push
|
||||
git push --tags
|
||||
|
||||
- name: Publish to NPM
|
||||
working-directory: packages/${{ inputs.package }}
|
||||
run: |
|
||||
pnpm pack
|
||||
bun pm pack
|
||||
sudo npm i -g npm@latest
|
||||
/usr/local/bin/npm publish *.tgz
|
||||
npm publish *.tgz
|
||||
|
||||
- name: Create GitHub release
|
||||
run: pnpm tsx scripts/create-github-release.ts ${{ inputs.package }}
|
||||
run: bun run scripts/create-github-release.ts ${{ inputs.package }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -10,6 +10,7 @@ on:
|
||||
- analytics
|
||||
- auto-icons
|
||||
- i18n
|
||||
- is-background
|
||||
- module-react
|
||||
- module-solid
|
||||
- module-svelte
|
||||
@@ -38,6 +39,6 @@ jobs:
|
||||
installArgs: --ignore-scripts
|
||||
|
||||
- name: Sync Releases
|
||||
run: pnpm tsx scripts/sync-releases.ts ${{ inputs.package }}
|
||||
run: bun run scripts/sync-releases.ts ${{ inputs.package }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -28,11 +28,11 @@ jobs:
|
||||
|
||||
- name: Generate Latest Code
|
||||
working-directory: packages/browser
|
||||
run: pnpm gen
|
||||
run: bun run gen
|
||||
|
||||
- name: Run Checks
|
||||
working-directory: packages/browser
|
||||
run: pnpm check
|
||||
run: bun run check
|
||||
|
||||
- name: Commit Changes
|
||||
id: commit
|
||||
@@ -46,6 +46,6 @@ jobs:
|
||||
if: steps.commit.outputs.changes_detected == 'true'
|
||||
working-directory: packages/browser
|
||||
run: |
|
||||
pnpm pack
|
||||
bun pm pack
|
||||
sudo npm i -g npm@latest
|
||||
/usr/local/bin/npm publish *.tgz
|
||||
npm publish *.tgz
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Basic Checks
|
||||
run: pnpm check
|
||||
run: bun run check
|
||||
|
||||
builds:
|
||||
name: Builds
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Build All Packages
|
||||
run: pnpm buildc all
|
||||
run: bun run buildc all
|
||||
|
||||
build-demo:
|
||||
name: Build Demo
|
||||
@@ -47,11 +47,11 @@ jobs:
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Build
|
||||
run: pnpm build:all
|
||||
run: bun run build:all
|
||||
working-directory: packages/wxt-demo
|
||||
|
||||
- name: ZIP
|
||||
run: pnpm wxt zip
|
||||
run: bun run wxt zip
|
||||
working-directory: packages/wxt-demo
|
||||
|
||||
tests:
|
||||
@@ -73,20 +73,22 @@ jobs:
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Run Tests
|
||||
if: ${{ ! matrix.coverage }}
|
||||
run: pnpm test
|
||||
run: bun run test
|
||||
|
||||
- name: Run Tests (Coverage)
|
||||
if: matrix.coverage
|
||||
run: pnpm test:coverage --reporter=default --reporter=hanging-process
|
||||
run: bun run test:coverage --reporter=default --reporter=hanging-process
|
||||
|
||||
- name: Upload Coverage
|
||||
if: matrix.coverage
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -110,7 +112,7 @@ jobs:
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Pack WXT package
|
||||
run: pnpm pack
|
||||
run: bun pm pack
|
||||
working-directory: packages/wxt
|
||||
|
||||
- name: Install Dependencies
|
||||
@@ -122,15 +124,15 @@ jobs:
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
|
||||
- name: Type Check Template
|
||||
run: pnpm compile
|
||||
run: bun run compile
|
||||
if: matrix.template != 'svelte'
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
|
||||
- name: Type Check Template
|
||||
run: pnpm check
|
||||
run: bun run check
|
||||
if: matrix.template == 'svelte'
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
|
||||
- name: Build Template
|
||||
run: pnpm build
|
||||
run: bun run build
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
|
||||
@@ -11,18 +11,27 @@ permissions:
|
||||
jobs:
|
||||
vhs:
|
||||
name: Create VHS
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: macos-latest
|
||||
if: ${{ github.repository == 'wxt-dev/wxt' }}
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
install: false
|
||||
ref: ${{ github.ref_name }}
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
cache: pnpm
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c
|
||||
with:
|
||||
go-version: '1.25.1'
|
||||
|
||||
# This prevents pnpm dlx from downloading WXT in the video
|
||||
- name: Pre-install WXT
|
||||
@@ -31,9 +40,10 @@ jobs:
|
||||
pnpm dlx wxt@latest --version
|
||||
|
||||
- name: Record VHS
|
||||
uses: charmbracelet/vhs-action@59641cdc7fadf3978db65eb8c6937ea2752f4ec3 # v2.1.0
|
||||
with:
|
||||
path: 'docs/tapes/init-demo.tape'
|
||||
run: |
|
||||
brew install ttyd ffmpeg
|
||||
go install github.com/charmbracelet/vhs@517bcda0faf416728bcf6b7fe489eb0e2469d9b5 # v0.10.0
|
||||
vhs docs/tapes/init-demo.tape
|
||||
|
||||
- name: Save recorded GIF
|
||||
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||
@@ -41,5 +51,8 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
commit_message: 'docs: Update `wxt init` GIF'
|
||||
commit_user_name: github-actions[bot]
|
||||
commit_user_email: github-actions[bot]@users.noreply.github.com
|
||||
commit_author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
||||
# https://github.com/charmbracelet/vhs#output
|
||||
file_pattern: 'docs/assets/*.gif'
|
||||
|
||||
+1
-1
@@ -7,6 +7,7 @@
|
||||
.wxt
|
||||
.wxt-runner
|
||||
*.log
|
||||
*.tgz
|
||||
/docs/.vitepress/cache
|
||||
docs/.vitepress/.temp
|
||||
coverage
|
||||
@@ -20,6 +21,5 @@ templates/*/yarn.lock
|
||||
templates/*/package-lock.json
|
||||
docs/api/reference
|
||||
stats.html
|
||||
.tool-versions
|
||||
.cache
|
||||
*-stats.txt
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
singleQuote: true
|
||||
endOfLine: lf
|
||||
plugins:
|
||||
- prettier-plugin-jsdoc
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
bun 1.3.12
|
||||
+51
-35
@@ -15,7 +15,7 @@ WXT is two things:
|
||||
|
||||
The long term goal of WXT is provide an opinionated build tool that keeps WXT projects standard, while providing light-weight runtime utils that simplify a lot of the boilerplate/overhead when setting up a new extension.
|
||||
|
||||
I also want to provide a way for developers to use either one of those two things independently, and not require them to use both. This is why all of WXT's runtime utils are shipped as their own NPM packages, most of them not bundled inside the core `wxt` package. If you just want to use the packages, they're availalbe, and if you just want to use WXT's built tool, you don't have to import any of WXT's utilities, you can use your own.
|
||||
I also want to provide a way for developers to use either one of those two things independently, and not require them to use both. This is why all of WXT's runtime utils are shipped as their own NPM packages, most of them not bundled inside the core `wxt` package. If you just want to use the packages, they're available, and if you just want to use WXT's built tool, you don't have to import any of WXT's utilities, you can use your own.
|
||||
|
||||
> The few runtime utils shipped inside WXT are things that should be used by 90% of extensions. That said, they're also legacy utils left in from before I started creating separate NPM packages, and in the future, they may be removed from the core package.
|
||||
|
||||
@@ -48,16 +48,12 @@ To make a breaking change:
|
||||
|
||||
## Setup
|
||||
|
||||
WXT uses `pnpm`, so make sure you have it installed.
|
||||
WXT uses Bun for package management and development. Install it from: <https://bun.com/>
|
||||
|
||||
Then install dependencies:
|
||||
|
||||
```sh
|
||||
corepack enable
|
||||
```
|
||||
|
||||
Then, simply run the install command:
|
||||
|
||||
```sh
|
||||
pnpm i
|
||||
bun install
|
||||
```
|
||||
|
||||
## Development
|
||||
@@ -65,48 +61,68 @@ pnpm i
|
||||
Here are some helpful commands:
|
||||
|
||||
```sh
|
||||
# Build WXT package
|
||||
cd packages/wxt
|
||||
pnpm build
|
||||
# Build WXT package and workspace dependencies
|
||||
bun run --filter wxt build
|
||||
```
|
||||
|
||||
```sh
|
||||
# Build WXT package, then build demo extension
|
||||
cd packages/wxt-demo
|
||||
pnpm build
|
||||
```
|
||||
|
||||
```sh
|
||||
# Build WXT package, then start the demo extension in dev mode
|
||||
cd packages/wxt-demo
|
||||
pnpm dev
|
||||
# Build workspace dependencies, then start the demo extension in dev mode
|
||||
bun run --filter wxt-demo dev
|
||||
```
|
||||
|
||||
```sh
|
||||
# Run unit and E2E tests
|
||||
pnpm test
|
||||
bun run test
|
||||
```
|
||||
|
||||
```sh
|
||||
# Start the docs website locally
|
||||
pnpm docs:dev
|
||||
bun run docs:dev
|
||||
```
|
||||
|
||||
> Above, we used bun's `--filter` flag to choose which package to run a command in, but there are other ways:
|
||||
>
|
||||
> ```sh
|
||||
> bun run -F @wxt-dev/i18n build
|
||||
> # or
|
||||
> bun run --cwd packages/i18n build
|
||||
> # or
|
||||
> cd packages/i18n
|
||||
> bun run build
|
||||
> ```
|
||||
>
|
||||
> Pick your poison!
|
||||
|
||||
## Profiling
|
||||
|
||||
```sh
|
||||
# Build the latest version
|
||||
pnpm --filter wxt build
|
||||
bun run --filter wxt build
|
||||
|
||||
# CD to the demo directory
|
||||
cd packages/wxt-demo
|
||||
|
||||
# 1. Generate a flamechart with 0x
|
||||
pnpm dlx 0x node_modules/wxt/bin/wxt.mjs build
|
||||
# 2. Inspect the process with chrome @ chrome://inspect
|
||||
pnpm node --inspect node_modules/wxt/bin/wxt.mjs build
|
||||
```
|
||||
|
||||
Then there are a few different ways to profile WXT commands:
|
||||
|
||||
- Generate a flamechart with 0x:
|
||||
|
||||
```sh
|
||||
bunx 0x node_modules/wxt/bin/wxt.mjs build
|
||||
```
|
||||
|
||||
- Create a CPU profile:
|
||||
|
||||
```sh
|
||||
bun run --cpu-prof node_modules/wxt/bin/wxt.mjs build
|
||||
```
|
||||
|
||||
- Debug the process:
|
||||
|
||||
```sh
|
||||
bun run --inspect node_modules/wxt/bin/wxt.mjs build
|
||||
```
|
||||
|
||||
## Updating Docs
|
||||
|
||||
Documentation is written with VitePress, and is located in the `docs/` directory.
|
||||
@@ -122,7 +138,7 @@ WXT has unit and E2E tests. When making a change or adding a feature, make sure
|
||||
To run tests for a specific file, add the filename at the end of the test command:
|
||||
|
||||
```sh
|
||||
pnpm test manifest-contents
|
||||
bun run --filter wxt test manifest-contents
|
||||
```
|
||||
|
||||
All test (unit and E2E) for all packages are ran together via [Vitest workspaces](https://vitest.dev/guide/#workspaces-support).
|
||||
@@ -131,7 +147,7 @@ If you want to manually test a change, you can modify the demo project for your
|
||||
|
||||
## Templates
|
||||
|
||||
Each directory inside `templates/` is it's own standalone project. Simply `cd` into the directory you're updating, install dependencies with `npm` (NOT `pnpm`), and run the relevant commands
|
||||
Each directory inside `templates/` is it's own standalone project. Simply `cd` into the directory you're updating, install dependencies with `npm` (NOT `bun`), and run the relevant commands
|
||||
|
||||
```sh
|
||||
cd templates/vue
|
||||
@@ -147,7 +163,7 @@ Note that templates are hardcoded to a specific version of `wxt` from NPM, they
|
||||
"typescript": "^5.3.2",
|
||||
"vite-plugin-solid": "^2.7.0",
|
||||
- "wxt": "^0.8.0"
|
||||
+ "wxt": "../.."
|
||||
+ "wxt": "../../packages/wxt"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -175,13 +191,13 @@ Releases are done with GitHub actions:
|
||||
WXT has custom rules around what dependencies can be upgraded. Use the `scripts/upgrade-deps.ts` script to upgrade dependencies and follow these rules.
|
||||
|
||||
```sh
|
||||
pnpm tsx scripts/upgrade-deps.ts
|
||||
bun run scripts/upgrade-deps.ts
|
||||
```
|
||||
|
||||
To see all the options, run:
|
||||
|
||||
```sh
|
||||
pnpm tsx scripts/upgrade-deps.ts --help
|
||||
bun run scripts/upgrade-deps.ts --help
|
||||
```
|
||||
|
||||
## Install Unreleased Versions
|
||||
@@ -219,4 +235,4 @@ Anyone is welcome to submit a blog post on <https://wxt.dev/blog>!
|
||||
|
||||
If you're interested in becoming a maintainer, send an email to Aaron at <aaronklinker1@gmail.com> with your github username saying you're interested. The process is very informal, I will add you quickly if you've contributed code or answered questions and helped out the community!
|
||||
|
||||
Maintainers don't have to just write code - they can manage issues, answer questsions, review PRs, organize and prioritize work - there's lots of ways for you to help out.
|
||||
Maintainers don't have to just write code - they can manage issues, answer questions, review PRs, organize and prioritize work - there's lots of ways for you to help out.
|
||||
|
||||
@@ -47,3 +47,44 @@ Here's an example of how to ask for a reproduction: <https://github.com/wxt-dev/
|
||||
## Add yourself as a code owner
|
||||
|
||||
If you want to be responsible for a specific package or directory, add yourself to the [`.github/CODEOWNERS`](https://github.com/wxt-dev/wxt/blob/main/.github/CODEOWNERS) file to get added as a reviewer to PRs automatically. You can also add yourself to the default list to be added as a reviewer on all PRs.
|
||||
|
||||
## Releasing Package Updates
|
||||
|
||||
Releases are done with GitHub actions:
|
||||
|
||||
- Use the [Release workflow](https://github.com/wxt-dev/wxt/actions/workflows/release.yml) to release a single package in the monorepo. This automatically detects the version change with conventional commits, builds and uploads the package to NPM, and creates a GitHub release.
|
||||
- Use the [Sync Releases workflow](https://github.com/wxt-dev/wxt/actions/workflows/sync-releases.yml) to sync the GitHub releases with changes to the changelog. To change a release, update the `CHANGELOG.md` file and run the workflow. It will sync the releases of a single package in the monorepo.
|
||||
|
||||
## Creating New Packages
|
||||
|
||||
Example PR: <https://github.com/wxt-dev/wxt/pull/2152>
|
||||
|
||||
1. Create the package.
|
||||
|
||||
2. Update CI workflow inputs.
|
||||
|
||||
3. Add docs page and version for "Other Packages" dropdown.
|
||||
|
||||
4. Merge the PR.
|
||||
|
||||
5. Tag the commit (look at other tags for pattern):
|
||||
|
||||
```sh
|
||||
git tag <dir-name>-v<version>
|
||||
git push --tags
|
||||
```
|
||||
|
||||
6. Publish the package to NPM:
|
||||
|
||||
```sh
|
||||
cd packages/<dir-name>
|
||||
bun publish --access public
|
||||
```
|
||||
|
||||
7. Create a basic release on GitHub mentioning the new package is available.
|
||||
|
||||
A couple of things to note:
|
||||
|
||||
- pkg.pr.new will fail on the original PR. It's fine to ignore and merge your PR as long as it fails due to your new package not being published to NPM yet.
|
||||
- The regular release workflow DOES NOT WORK for new packages. You have to have at least one `<dir-name>-v<version>` tag created before you can run that workflow for your new package.
|
||||
- You don't need to create a CHANGELOG.md file for the package, it will be created automatically after future changes are released via the normal release workflow.
|
||||
|
||||
+2
-6
@@ -4,12 +4,8 @@ While WXT is in prerelease, only the latest version will receive security update
|
||||
|
||||
<img alt="npm version" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048">
|
||||
|
||||
<!--
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Use this section to tell people how to report a vulnerability.
|
||||
If you discover a security vulnerability, please email me at <aaronklinker1@gmail.com>. I will respond within a few days to acknowledge receipt of your report.
|
||||
|
||||
Tell them where to go, how often they can expect to get an update on a
|
||||
reported vulnerability, what to expect if the vulnerability is accepted or
|
||||
declined, etc.
|
||||
-->
|
||||
If the vulnerability is accepted, I will open a public issue to track the fix. If the vulnerability is not accepted, no further action will be taken.
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
version: '0.2'
|
||||
ignorePaths:
|
||||
- pnpm-lock.yaml
|
||||
- '**/node_modules/**'
|
||||
- '**/dist/**'
|
||||
- '**/coverage/**'
|
||||
- docs/api/**
|
||||
- '**/CHANGELOG.md'
|
||||
- docs/public/_redirects
|
||||
- packages/browser/src/gen/**
|
||||
- patches/**
|
||||
- '**/*.snap'
|
||||
- docs/assets/extension-showcase.yml
|
||||
words:
|
||||
- Aabid
|
||||
- aabidk
|
||||
- aklinker
|
||||
- automount
|
||||
- avenir
|
||||
- bidi
|
||||
- buildc
|
||||
- bunx
|
||||
- cachable
|
||||
- charmbracelet
|
||||
- chromedriver
|
||||
- Cira
|
||||
- comctx
|
||||
- confbox
|
||||
- configfile
|
||||
- consolas
|
||||
- Cpath
|
||||
- crbug
|
||||
- crunchyroll
|
||||
- cssinjs
|
||||
- CSUI
|
||||
- Csvg
|
||||
- defu
|
||||
- deinit
|
||||
- dequal
|
||||
- dtrace
|
||||
- duckduckgo
|
||||
- esbuild
|
||||
- esque
|
||||
- extglob
|
||||
- favicons
|
||||
- fieldtrials
|
||||
- firefoxdeveloperedition
|
||||
- flamechart
|
||||
- Florian
|
||||
- focusring
|
||||
- frontmatter
|
||||
- giget
|
||||
- grayscale
|
||||
- greyscale
|
||||
- gtag
|
||||
- hckhakegfgenefhikdcfkaaonnclljmf
|
||||
- hookable
|
||||
- iconify
|
||||
- jsons
|
||||
- Kenobi
|
||||
- keystyle
|
||||
- klinker
|
||||
- linkedom
|
||||
- llms
|
||||
- locationchange
|
||||
- magicast
|
||||
- Menlo
|
||||
- menupanel
|
||||
- metas
|
||||
- mktemp
|
||||
- mycompany
|
||||
- nacl
|
||||
- nanospinner
|
||||
- natsu
|
||||
- newtab
|
||||
- njsproj
|
||||
- nodebuffer
|
||||
- Noto
|
||||
- ntvs
|
||||
- nypm
|
||||
- ohash
|
||||
- oklab
|
||||
- oxlint
|
||||
- pbbf
|
||||
- personaltoolbar
|
||||
- portaling
|
||||
- prebundled
|
||||
- prefs
|
||||
- proxified
|
||||
- pWXLO
|
||||
- repost
|
||||
- rxliuli
|
||||
- sandboxed
|
||||
- scule
|
||||
- Segoe
|
||||
- sidepanel
|
||||
- sidepanels
|
||||
- solidjs
|
||||
- styl
|
||||
- subdependency
|
||||
- tabstrip
|
||||
- teleporting
|
||||
- treemap
|
||||
- triaging
|
||||
- umami
|
||||
- ungh
|
||||
- unimport
|
||||
- unocss
|
||||
- unrun
|
||||
- unstub
|
||||
- unwatch
|
||||
- USSS
|
||||
- vitepress
|
||||
- webext
|
||||
- webextension
|
||||
- webextrc
|
||||
- xcrun
|
||||
- xlink
|
||||
@@ -1,168 +1,120 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import useListExtensionDetails, {
|
||||
ChromeExtension,
|
||||
} from '../composables/useListExtensionDetails';
|
||||
import useListExtensionDetails from '../composables/useListExtensionDetails';
|
||||
import _extensionEntries from '../../assets/extension-showcase.yml';
|
||||
|
||||
// Add extension IDs to end of the list. On the website, extensions will be sorted by a combination of weekly active users and rating.
|
||||
// Change the commit message or PR title to: "docs: Added "[extension name]" to the homepage"
|
||||
const chromeExtensionIds = [
|
||||
'ocfdgncpifmegplaglcnglhioflaimkd', // GitHub: Better Line Counts
|
||||
'mgmdkjcljneegjfajchedjpdhbadklcf', // Anime Skip Player
|
||||
'bfbnagnphiehemkdgmmficmjfddgfhpl', // UltraWideo
|
||||
'elfaihghhjjoknimpccccmkioofjjfkf', // StayFree - Website Blocker & Web Analytics
|
||||
'okifoaikfmpfcamplcfjkpdnhfodpkil', // Doozy: Ai Made Easy
|
||||
'lknmjhcajhfbbglglccadlfdjbaiifig', // tl;dv - Record, Transcribe & ChatGPT for Google Meet
|
||||
'oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
|
||||
'agjnjboanicjcpenljmaaigopkgdnihi', // PreMiD
|
||||
'aiakblgmlabokilgljkglggnpflljdgp', // Markdown Sticky Notes
|
||||
'nomnkbngkijpffepcgbbofhcnafpkiep', // DocVersionRedirector
|
||||
'ceicccfeikoipigeghddpocceifjelph', // Plex Skipper
|
||||
'aelkipgppclpfimeamgmlonimflbhlgf', // GitHub Custom Notifier
|
||||
'djnlaiohfaaifbibleebjggkghlmcpcj', // Fluent Read
|
||||
'nhclljcpfmmaiojbhhnkpjcfmacfcian', // Facebook Video Controls
|
||||
'mblkhbaakhbhiimkbcnmeciblfhmafna', // ElemSnap - Quick capture of webpage elements and conversion to images,
|
||||
'oajalfneblkfiejoadecnmodfpnaeblh', // MS Edge TTS (Text to Speech)
|
||||
'nedcanggplmbbgmlpcjiafgjcpdimpea', // YTBlock - Block any content from YouTube™
|
||||
'oadbjpccljkplmhnjekgjamejnbadlne', // demo.fun - Interactive product demos that convert
|
||||
'iopdafdcollfgaoffingmahpffckmjni', // SmartEReply: Elevate Your LinkedIn™ Engagement with AI 🚀📈
|
||||
'khjdmjcmpolknpccmaaipmidphjokhdf', // WorkFlowy MultiFlow
|
||||
'fencadnndhdeggodopebjgdfdlhcimfk', // 香草布丁🌿🍮- https://github.com/Xdy1579883916/vanilla-pudding
|
||||
'bnacincmbaknlbegecpioobkfgejlojp', // MaxFocus: Link Preview
|
||||
'bcpgdpedphodjcjlminjbdeejccjbimp', // 汇率转换-中文版本
|
||||
'loeilaonggnalkaiiaepbegccilkmjjp', // Currency Converter Plus
|
||||
'npcnninnjghigjfiecefheeibomjpkak', // Respond Easy
|
||||
'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
|
||||
'ncokhechhpjgjonhjnlaneglmdkfkcbj', // Habit Tracker app widget for daily habit tracking
|
||||
'lnjaiaapbakfhlbjenjkhffcdpoompki', // Catppuccin for GitHub File Explorer Icons
|
||||
'cpaedhbidlpnbdfegakhiamfpndhjpgf', // WebChat: Chat with anyone on any website
|
||||
'fcphghnknhkimeagdglkljinmpbagone', // YouTube Auto HD + FPS
|
||||
'lpomjgbicdemjkgmbnkjncgdebogkhlb', // MultiViewer Companion
|
||||
'ggiafipgeeaaahnjamgpjcgkdpanhddg', // Sync Watch - Watch videos together on any site
|
||||
'nmldnjcblcihmegipecakhmnieiofmgl', // Keyword Rank Checker
|
||||
'gppllamhaciichleihemgilcpledblpn', // YouTube Simple View - Hide distractions & more
|
||||
'pccbghdfdnnkkbcdcibchpbffdgednkf', // Propbar - Property Data Enhancer
|
||||
'lfknakglefggmdkjdfhhofkjnnolffkh', // Text Search Pro - Search by case and whole-word match!
|
||||
'mbenhbocjckkbaojacmaepiameldglij', // Invoice Generator
|
||||
'phlfhkmdofajnbhgmbmjkbkdgppgoppb', // Monthly Bill Tracker
|
||||
'macmkmchfoclhpbncclinhjflmdkaoom', // Wandpen - Instantly improve your writing with AI
|
||||
'lhmgechokhmdekdpgkkemoeecelcaonm', // YouTube Hider - Remove Comments By Keywords, Usernames & Tools
|
||||
'imgheieooppmahcgniieddodaliodeeg', // QA Compass - Record standardized bug reports easily
|
||||
'npgghjedpchajflknnbngajkjkdhncdo', // aesthetic Notion, styled
|
||||
'hmdcmlfkchdmnmnmheododdhjedfccka', // Eye Dropper
|
||||
'eihpmapodnppeemkhkbhikmggfojdkjd', // Cursorful - Screen Recorder with Auto Zoom
|
||||
'hjjkgbibknbahijglkffklflidncplkn', // Show IP – Live View of Website IPs for Developers
|
||||
'ilbikcehnpkmldojkcmlldkoelofnbde', // Strong Password Generator
|
||||
'ocllfkhcdopiafndigclebelbecaiocp', // ZenGram: Mindful Instagram, Your Way
|
||||
'odffpjnpocjfcaclnenaaaddghkgijdb', // Blync: Preview Links, Selection Search, AI Assistant
|
||||
'kofbbilhmnkcmibjbioafflgmpkbnmme', // HTML to Markdown - Convert webpages to markdown
|
||||
'boecmgggeigllcdocgioijmleimjbfkg', // Walmart WFS Profit Calculator
|
||||
'dlnjcbkmomenmieechnmgglgcljhoepd', // Youtube Live Chat Fullscreen
|
||||
'keiealdacakpnbbljlmhfgcebmaadieg', // Python Code Runner
|
||||
'hafcajcllbjnoolpfngclfmmgpikdhlm', // Monochromate
|
||||
'bmoggiinmnodjphdjnmpcnlleamkfedj', // AliasVault - Open-Source Password & (Email) Alias Manager
|
||||
'hlnhhamckimoaiekbglafiebkfimhapb', // SnapThePrice: AI-Powered Real-time Lowest Price Finder
|
||||
'gdjampjdgjmbifnhldgcnccdjkcoicmg', // radiofrance - news & broadcasts (French), music (international)
|
||||
'jlnhphlghikichhgbnkepenehbmloenb', // Blens - Time Tracker and AI Insight
|
||||
'njnammmpdodmfkodnfpammnpdcbhnlcm', // Always Light Mode - Setting website always in light mode
|
||||
'lblmfclcfniabobmamfkdogcgdagbhhb', // DesignPicker - Color Picker & Font Detector
|
||||
'pamnlaoeobcmhkliljfaofekeddpmfoh', // Web to PDF
|
||||
'jmbcbeepjfenihlocplnbmbhimcoooka', // Online CSV Viewer
|
||||
'nkjcoophmpcmmgadnljnlpbpfdfacgbo', // YouTube Video Transcript
|
||||
'lcaieahkjgeggeiihblhcjbbjlppgieh', // NetSuite Record Scripts
|
||||
'gmocfknjllodfiomnljmaehcplnekhlo', // VueTracker
|
||||
'ggcfemmoabhhelfkhknhbnkmeahloiod', // CanCopy - A web extension that allow you to copy any content from website
|
||||
'modkelfkcfjpgbfmnbnllalkiogfofhb', // Language Learning with AI
|
||||
'npfopljnjbamegincfjelhjhnonnjloo', // Bilibili Feed History Helper
|
||||
'edkhpdceeinkcacjdgebjehipmnbomce', // NZBDonkey - The ultimate NZB file download tool
|
||||
'cckggnbnimdbbpmdinkkgbbncopbloob', // WeChat Markdown Editor(微信 Markdown 编辑器)
|
||||
'jcblcjolcojmfopefcighfmkkefbaofg', // Tab Grab
|
||||
'eehmoikadcijkapfjocnhjclpbaindlb', // BrowserLens - https://browserlens.com/
|
||||
'hfhellofkjebbchcdffmicekjdomkcmc', // Epic Games Library Extension
|
||||
'gknigcbhlammoakmmdddkblknanpjiac', // Zen Analytics Pixel Tracker - zapt.web.app
|
||||
'cnklededohhcbmjjdlbjdkkihkgoggol', // Crypto Pulse - Compose your newtab with nature images, widgets & realtime Crypto Price & Bitcoin RSS.
|
||||
'miponnamafdenpgjemkknimgjfibicdc', // Youtube Video Scheduler
|
||||
'nhmbcmalgpkjbomhlhgdicanmkkaajmg', // Chatslator: Livestream Chat Translator
|
||||
'mbamjfdjbcdgpopfnkkmlohadbbnplhm', // 公众号阅读增强器 - https://wxreader.honwhy.wang
|
||||
'hannhecbnjnnbbafffmogdlnajpcomek', // 토탐정
|
||||
'ehboaofjncodknjkngdggmpdinhdoijp', // 2FAS Pass - https://2fas.com/
|
||||
'hnjamiaoicaepbkhdoknhhcedjdocpkd', // Quick Prompt - https://github.com/wenyuanw/quick-prompt
|
||||
'kacblhilkacgfnkjfodalohcnllcgmjd', // Add QR Code Generator Icon Back To Address Bar
|
||||
'fkbdlogfdjmpfepbbbjcgcfbgbcfcnne', // Piwik PRO Tracking Helper
|
||||
'nkbikckldmljjiiajklecmgmajgapbfl', // PIPX - Take Control of Picture-in-Picture, Automatically
|
||||
'hgppdobcpkfkmiegekaglonjajeojmdd', // Browsely - AI-powered browser extension
|
||||
'ehmoihnjgkdimihkhokkmfjdgomohjgm', // Filmbudd Pro - Simple, private – and synced ratings and watch notes across all your devices
|
||||
'alglchohmdikgdjhafiicilegegieafa', // MultiField CopyCat - Copy, Paste & Autofill Web Forms Instantly
|
||||
'aamihahiiogceidpbnfgehacgiecephe', // ChatSight - Add Table of Contents to ChatGPT
|
||||
'cndibmoanboadcifjkjbdpjgfedanolh', // BetterCampus (prev. BetterCanvas)
|
||||
'hinfimgacobnellbncbcpdlpaapcofaa', // Leetcode Fonts - Change fonts in leetcode effortlessly
|
||||
'kbkbfefhhabpkibojinapkkgciiacggg', // TranslateManga - Manga Translator & Manga Tracker
|
||||
'emeakbgdecgmdjgegnejpppcnkcnoaen', // SiteData - Free Website Traffic Checker & Reverse AdSense Tool
|
||||
'gpnckbhgpnjciklpoehkmligeaebigaa', // Livestream Chat Reader - Text-to-Speech for YouTube/Twitch chat
|
||||
'fjlalaedpfcojcfpkgkglbjjbbkofgnl', // ChatGPT Token Counter - Count tokens in real time on chatgpt conversation
|
||||
'fbgblmjbeebanackldpbmpacppflgmlj', // LinuxDo Scripts - 为 linux.do 用户提供了一些增强功能
|
||||
'dfacnjidgbagicaekenjgclfnhdnjjdi', // Zen Virtual Piano - https://zen-piano.web.app/
|
||||
'naeibcegmgpofimedkmfgjgphfhfhlab', // Crypto Pulse price tracker - https://get-crypto-pulse.web.app/
|
||||
'ffglckbhfbfmdkefdmjbhpnffkcmlhdh', // Redirect Web - Automatically redirect pages or open them in another app
|
||||
'eglpfhbhmelampoihamjomgkeobgdofl', // Capture It - Capture & Edit Screenshots
|
||||
'jmghclbfbbapimhbgnpffbimphlpolnm', // Teams Chat Exporter
|
||||
'jdcppdokgfbnhiacbeplahgnciahnhck', // Lofi BGM Player - Free lofi focus music for work & study
|
||||
'cgpmbiiagnehkikhcbnhiagfomajncpa', // Margin - Annotate and highlight any webpage, with your notes saved to the decentralized AT Protocol.
|
||||
'mfjdonmgmgcijagclnkfhmjiblbfjaid', // KeyFloat - Floating multilingual keyboard with native key mappings, drag, dark mode, sounds, and dynamic layouts for macOS & Windows
|
||||
interface StoreLink {
|
||||
label: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface ListedExtension {
|
||||
id: string;
|
||||
name: string;
|
||||
iconUrl: string;
|
||||
shortDescription: string;
|
||||
users: number;
|
||||
rating: number | undefined;
|
||||
stores: StoreLink[];
|
||||
}
|
||||
|
||||
const extensionEntries = _extensionEntries as Array<{
|
||||
chromeId?: string;
|
||||
firefoxSlug?: string;
|
||||
}>;
|
||||
|
||||
const chromeIds = extensionEntries.flatMap((e) =>
|
||||
e.chromeId ? [e.chromeId] : [],
|
||||
);
|
||||
const firefoxSlugs = [
|
||||
...new Set(
|
||||
extensionEntries.flatMap((e) => (e.firefoxSlug ? [e.firefoxSlug] : [])),
|
||||
),
|
||||
];
|
||||
|
||||
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
||||
const sortedExtensions = computed(() => {
|
||||
if (!data.value?.length) return [];
|
||||
const { data, isLoading } = useListExtensionDetails(chromeIds, firefoxSlugs);
|
||||
|
||||
return [...data.value]
|
||||
.filter((item) => item != null)
|
||||
.map((item) => ({
|
||||
...item,
|
||||
// Sort based on the user count weighted by the rating
|
||||
sortKey: ((item.rating ?? 5) / 5) * item.weeklyActiveUsers,
|
||||
}))
|
||||
.filter((item) => !!item)
|
||||
.sort((l, r) => r.sortKey - l.sortKey);
|
||||
});
|
||||
|
||||
function getStoreUrl(extension: ChromeExtension) {
|
||||
const url = new URL(extension.storeUrl);
|
||||
function addUtmSource(storeUrl: string): string {
|
||||
const url = new URL(storeUrl);
|
||||
url.searchParams.set('utm_source', 'wxt.dev');
|
||||
return url.href;
|
||||
}
|
||||
|
||||
const sortedExtensions = computed((): ListedExtension[] => {
|
||||
if (!data.value) return [];
|
||||
|
||||
const chromeById = new Map(
|
||||
(data.value.chrome ?? []).filter(Boolean).map((e) => [e.id, e]),
|
||||
);
|
||||
const firefoxBySlug = new Map(
|
||||
(data.value.firefox ?? []).filter(Boolean).map((e) => [e.id, e]),
|
||||
);
|
||||
|
||||
const results: ListedExtension[] = [];
|
||||
|
||||
for (const entry of extensionEntries) {
|
||||
const chrome = entry.chromeId ? chromeById.get(entry.chromeId) : undefined;
|
||||
const firefox = entry.firefoxSlug
|
||||
? firefoxBySlug.get(entry.firefoxSlug)
|
||||
: undefined;
|
||||
|
||||
if (!chrome && !firefox) continue;
|
||||
|
||||
const primary = chrome ?? firefox!;
|
||||
const users = (chrome?.users ?? 0) + (firefox?.users ?? 0);
|
||||
const rating = chrome?.rating ?? firefox?.rating;
|
||||
|
||||
const stores: StoreLink[] = [];
|
||||
if (chrome) {
|
||||
stores.push({ label: 'Chrome', url: addUtmSource(chrome.storeUrl) });
|
||||
}
|
||||
if (firefox) {
|
||||
stores.push({ label: 'Firefox', url: addUtmSource(firefox.storeUrl) });
|
||||
}
|
||||
|
||||
results.push({
|
||||
id: entry.chromeId ?? `firefox:${entry.firefoxSlug}`,
|
||||
name: primary.name,
|
||||
iconUrl: primary.iconUrl,
|
||||
shortDescription: primary.shortDescription,
|
||||
users,
|
||||
rating,
|
||||
stores,
|
||||
});
|
||||
}
|
||||
|
||||
const score = (e: ListedExtension) => ((e.rating ?? 5) / 5) * e.users;
|
||||
return results.sort((a, b) => score(b) - score(a));
|
||||
});
|
||||
|
||||
function formatUsers(n: number): string {
|
||||
return `${n.toLocaleString()} users`;
|
||||
}
|
||||
|
||||
function formatStars(r: number): string {
|
||||
const rounded = Math.round(r * 10) / 10;
|
||||
return `${rounded % 1 === 0 ? rounded.toFixed(0) : rounded.toFixed(1)} stars`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<p v-if="isLoading" style="text-align: center; opacity: 50%">Loading...</p>
|
||||
<p
|
||||
v-else-if="err || sortedExtensions.length === 0"
|
||||
v-else-if="sortedExtensions.length === 0"
|
||||
style="text-align: center; opacity: 50%"
|
||||
>
|
||||
Failed to load extension details.
|
||||
</p>
|
||||
<ul v-else>
|
||||
<li
|
||||
v-for="extension of sortedExtensions"
|
||||
:key="extension.id"
|
||||
class="relative"
|
||||
>
|
||||
<ul v-else class="extension-grid">
|
||||
<li v-for="extension of sortedExtensions" :key="extension.id">
|
||||
<img
|
||||
:src="extension.iconUrl"
|
||||
:alt="`${extension.name} icon`"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<div class="relative">
|
||||
<div class="card-content">
|
||||
<a
|
||||
:href="getStoreUrl(extension)"
|
||||
:href="extension.stores[0]?.url ?? '#'"
|
||||
target="_blank"
|
||||
:title="extension.name"
|
||||
class="extension-name"
|
||||
@@ -171,19 +123,40 @@ function getStoreUrl(extension: ChromeExtension) {
|
||||
<p class="description" :title="extension.shortDescription">
|
||||
{{ extension.shortDescription }}
|
||||
</p>
|
||||
<div v-if="extension.stores.length > 0" class="store-stats">
|
||||
<span class="store-stats-info">
|
||||
<span>{{ formatUsers(extension.users) }}</span>
|
||||
<template v-if="extension.rating">
|
||||
<span class="store-stats-sep" aria-hidden="true">,</span>
|
||||
<span>{{ formatStars(extension.rating) }}</span>
|
||||
</template>
|
||||
</span>
|
||||
<span class="store-stats-sep" aria-hidden="true">·</span>
|
||||
<span class="store-links">
|
||||
<a
|
||||
v-for="(store, i) of extension.stores"
|
||||
:key="store.label"
|
||||
:href="store.url"
|
||||
target="_blank"
|
||||
class="store-link"
|
||||
:title="store.label"
|
||||
>
|
||||
{{ store.label
|
||||
}}<span
|
||||
v-if="i < extension.stores.length - 1"
|
||||
class="store-stats-sep"
|
||||
aria-hidden="true"
|
||||
>, </span
|
||||
>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="user-count">
|
||||
<span>{{ extension.weeklyActiveUsers.toLocaleString() }} users</span
|
||||
><template v-if="extension.rating != null"
|
||||
>,
|
||||
<span>{{ extension.rating }} stars</span>
|
||||
</template>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="centered pr">
|
||||
<a
|
||||
href="https://github.com/wxt-dev/wxt/edit/main/docs/.vitepress/components/UsingWxtSection.vue"
|
||||
href="https://github.com/wxt-dev/wxt/edit/main/docs/assets/extension-showcase.yml"
|
||||
target="_blank"
|
||||
>Open a PR</a
|
||||
>
|
||||
@@ -192,7 +165,7 @@ function getStoreUrl(extension: ChromeExtension) {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
li img {
|
||||
.extension-grid > li > img {
|
||||
width: 116px;
|
||||
height: 116px;
|
||||
padding: 16px;
|
||||
@@ -200,7 +173,7 @@ li img {
|
||||
background-color: var(--vp-c-default-soft);
|
||||
}
|
||||
|
||||
ul {
|
||||
.extension-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
align-items: stretch;
|
||||
@@ -210,12 +183,12 @@ ul {
|
||||
padding: 0;
|
||||
}
|
||||
@media (min-width: 960px) {
|
||||
ul {
|
||||
.extension-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
.extension-grid > li {
|
||||
margin: 0 !important;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
@@ -223,28 +196,68 @@ li {
|
||||
border-radius: 12px;
|
||||
flex: 1;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
li a,
|
||||
li .user-count,
|
||||
li .description {
|
||||
.extension-grid > li .description {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
li .user-count {
|
||||
opacity: 70%;
|
||||
font-size: small;
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
right: 16px;
|
||||
|
||||
.extension-grid > li .card-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
li a {
|
||||
.store-stats {
|
||||
margin-top: auto;
|
||||
padding-top: 6px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
align-items: baseline;
|
||||
column-gap: 6px;
|
||||
row-gap: 4px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
opacity: 0.72;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.store-stats-info {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.store-stats-sep {
|
||||
opacity: 0.45;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.store-links {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.store-link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.store-link:hover {
|
||||
color: var(--vp-c-text-1);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.extension-grid > li .extension-name {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
@@ -253,33 +266,23 @@ li a {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
font-size: large;
|
||||
}
|
||||
li a:hover {
|
||||
|
||||
.extension-grid > li .extension-name:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
li div {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
li .description {
|
||||
.extension-grid > li .description {
|
||||
opacity: 90%;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
li .extension-name {
|
||||
font-size: large;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.pr {
|
||||
opacity: 70%;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,39 +1,58 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
export interface ChromeExtension {
|
||||
export interface Extension {
|
||||
id: string;
|
||||
name: string;
|
||||
iconUrl: string;
|
||||
weeklyActiveUsers: number;
|
||||
shortDescription: string;
|
||||
storeUrl: string;
|
||||
rating: number | undefined;
|
||||
users: number;
|
||||
}
|
||||
|
||||
export interface ExtensionResults {
|
||||
chrome: Extension[];
|
||||
firefox: Extension[];
|
||||
}
|
||||
|
||||
const operationName = 'WxtDocsUsedBy';
|
||||
const query = `query ${operationName}($ids:[String!]!) {
|
||||
chromeExtensions(ids: $ids) {
|
||||
const query = `query ${operationName}($chromeIds: [String!]!, $firefoxIds: [String!]!) {
|
||||
chromeExtensions(ids: $chromeIds) {
|
||||
id
|
||||
name
|
||||
iconUrl
|
||||
weeklyActiveUsers
|
||||
shortDescription
|
||||
storeUrl
|
||||
rating
|
||||
...ExtensionData
|
||||
}
|
||||
firefoxAddons(ids: $firefoxIds) {
|
||||
id: slug
|
||||
...ExtensionData
|
||||
}
|
||||
}
|
||||
|
||||
fragment ExtensionData on Extension {
|
||||
name
|
||||
iconUrl
|
||||
shortDescription
|
||||
storeUrl
|
||||
rating
|
||||
users
|
||||
}`;
|
||||
|
||||
export default function (ids: string[]) {
|
||||
const data = ref<ChromeExtension[]>();
|
||||
export default function (chromeIds: string[], firefoxSlugs: string[]) {
|
||||
const data = ref<ExtensionResults>();
|
||||
const err = ref<unknown>();
|
||||
const isLoading = ref(true);
|
||||
|
||||
if (chromeIds.length === 0 && firefoxSlugs.length === 0) {
|
||||
data.value = { chrome: [], firefox: [] };
|
||||
isLoading.value = false;
|
||||
return { data, err, isLoading };
|
||||
}
|
||||
|
||||
fetch('https://queue.wxt.dev/api', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
operationName,
|
||||
query,
|
||||
variables: { ids },
|
||||
variables: { chromeIds, firefoxIds: firefoxSlugs },
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -41,10 +60,11 @@ export default function (ids: string[]) {
|
||||
})
|
||||
.then(async (res) => {
|
||||
isLoading.value = false;
|
||||
const {
|
||||
data: { chromeExtensions },
|
||||
} = await res.json();
|
||||
data.value = chromeExtensions;
|
||||
const { data: responseData } = await res.json();
|
||||
data.value = {
|
||||
chrome: responseData.chromeExtensions ?? [],
|
||||
firefox: responseData.firefoxAddons ?? [],
|
||||
};
|
||||
err.value = undefined;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -54,9 +74,5 @@ export default function (ids: string[]) {
|
||||
err.value = error;
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
err,
|
||||
isLoading,
|
||||
};
|
||||
return { data, err, isLoading };
|
||||
}
|
||||
|
||||
+34
-20
@@ -1,5 +1,25 @@
|
||||
import { Feed } from 'feed';
|
||||
import footnote from 'markdown-it-footnote';
|
||||
import fs, { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { DefaultTheme, defineConfig } from 'vitepress';
|
||||
import addKnowledge from 'vitepress-knowledge';
|
||||
import {
|
||||
groupIconMdPlugin,
|
||||
groupIconVitePlugin,
|
||||
localIconLoader,
|
||||
} from 'vitepress-plugin-group-icons';
|
||||
import llmstxt from 'vitepress-plugin-llms';
|
||||
import { version as analyticsVersion } from '../../packages/analytics/package.json';
|
||||
import { version as autoIconsVersion } from '../../packages/auto-icons/package.json';
|
||||
import { version as i18nVersion } from '../../packages/i18n/package.json';
|
||||
import { version as runnerVersion } from '../../packages/runner/package.json';
|
||||
import { version as storageVersion } from '../../packages/storage/package.json';
|
||||
import { version as unocssVersion } from '../../packages/unocss/package.json';
|
||||
import { version as wxtVersion } from '../../packages/wxt/package.json';
|
||||
import { version as isBackgroundVersion } from '../../packages/is-background/package.json';
|
||||
import typedocSidebar from '../api/reference/typedoc-sidebar.json';
|
||||
import { meta, script } from './utils/head';
|
||||
import {
|
||||
menuGroup,
|
||||
menuItem,
|
||||
@@ -7,25 +27,6 @@ import {
|
||||
navItem,
|
||||
prepareTypedocSidebar,
|
||||
} from './utils/menus';
|
||||
import { meta, script } from './utils/head';
|
||||
import footnote from 'markdown-it-footnote';
|
||||
import { version as wxtVersion } from '../../packages/wxt/package.json';
|
||||
import { version as i18nVersion } from '../../packages/i18n/package.json';
|
||||
import { version as autoIconsVersion } from '../../packages/auto-icons/package.json';
|
||||
import { version as unocssVersion } from '../../packages/unocss/package.json';
|
||||
import { version as storageVersion } from '../../packages/storage/package.json';
|
||||
import { version as analyticsVersion } from '../../packages/analytics/package.json';
|
||||
import { version as runnerVersion } from '../../packages/runner/package.json';
|
||||
import addKnowledge from 'vitepress-knowledge';
|
||||
import {
|
||||
groupIconMdPlugin,
|
||||
groupIconVitePlugin,
|
||||
localIconLoader,
|
||||
} from 'vitepress-plugin-group-icons';
|
||||
import { Feed } from 'feed';
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import llmstxt from 'vitepress-plugin-llms';
|
||||
|
||||
const origin = 'https://wxt.dev';
|
||||
|
||||
@@ -44,6 +45,7 @@ const otherPackages = {
|
||||
storage: storageVersion,
|
||||
unocss: unocssVersion,
|
||||
runner: runnerVersion,
|
||||
'is-background': isBackgroundVersion,
|
||||
};
|
||||
|
||||
const knowledge = addKnowledge<DefaultTheme.Config>({
|
||||
@@ -81,6 +83,17 @@ export default defineConfig({
|
||||
),
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'yaml-loader',
|
||||
load: {
|
||||
handler: async (id) => {
|
||||
if (id.endsWith('.yml') || id.endsWith('.yaml')) {
|
||||
const obj = Bun.YAML.parse(await readFile(id, 'utf8'));
|
||||
return `export default ${JSON.stringify(obj)}`;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
lastUpdated: true,
|
||||
@@ -111,7 +124,7 @@ export default defineConfig({
|
||||
});
|
||||
// console.log('rss.xml:');
|
||||
// console.log(feed.rss2());
|
||||
await writeFile(join(site.outDir, 'rss.xml'), feed.rss2(), 'utf8');
|
||||
await fs.writeFile(join(site.outDir, 'rss.xml'), feed.rss2());
|
||||
},
|
||||
|
||||
head: [
|
||||
@@ -222,6 +235,7 @@ export default defineConfig({
|
||||
menuItem('Storage', 'storage.md'),
|
||||
menuItem('Messaging', 'messaging.md'),
|
||||
menuItem('I18n', 'i18n.md'),
|
||||
menuItem('Favicons', 'favicons.md'),
|
||||
menuItem('Scripting', 'scripting.md'),
|
||||
menuItem('WXT Modules', 'wxt-modules.md'),
|
||||
menuItem('Frontend Frameworks', 'frontend-frameworks.md'),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { resolve } from 'node:path';
|
||||
import consola from 'consola';
|
||||
import spawn from 'nano-spawn';
|
||||
import { resolve } from 'node:path';
|
||||
import { version } from '../../../packages/wxt/package.json';
|
||||
|
||||
const cliDir = resolve('packages/wxt/src/cli/commands');
|
||||
const cliDirGlob = resolve(cliDir, '**');
|
||||
@@ -38,19 +39,20 @@ export default {
|
||||
|
||||
async function getHelp(command: string): Promise<string> {
|
||||
const args = command.split(' ');
|
||||
const res = await spawn(args[0], [...args.slice(1), '--help'], {
|
||||
const result = await spawn(args[0], [...args.slice(1), '--help'], {
|
||||
cwd: 'packages/wxt',
|
||||
});
|
||||
return res.stdout;
|
||||
return result.stdout;
|
||||
}
|
||||
|
||||
function getWxtHelp(command: string): Promise<string> {
|
||||
return getHelp(`pnpm -s wxt ${command}`.trim());
|
||||
async function getWxtHelp(command: string): Promise<string> {
|
||||
const res = await getHelp(`bun run --silent wxt ${command}`.trim());
|
||||
return res.replaceAll('{{version}}', version);
|
||||
}
|
||||
|
||||
async function getPublishExtensionHelp(command: string): Promise<string> {
|
||||
const res = await getHelp(
|
||||
`./node_modules/.bin/publish-extension ${command}`.trim(),
|
||||
`bun run --silent publish-extension ${command}`.trim(),
|
||||
);
|
||||
return res.replace(/\$ publish-extension/g, '$ wxt submit');
|
||||
}
|
||||
|
||||
@@ -66,3 +66,29 @@ body {
|
||||
.dark .dark-netlify {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Add Aaron's favorite pic from Artemis II to the hero section because it's cool. */
|
||||
.dark .VPHome::before {
|
||||
content: '';
|
||||
background-image: url('/assets/art002e009298.jpg');
|
||||
opacity: 50%;
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
height: 110vh;
|
||||
background-size: cover;
|
||||
background-position: 90%;
|
||||
pointer-events: none;
|
||||
}
|
||||
.dark .VPHome::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
height: 10vh;
|
||||
top: 100vh;
|
||||
background: linear-gradient(to bottom, transparent, var(--vp-c-bg));
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
.item {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@@ -77,9 +77,7 @@ export function menuItem(
|
||||
return { text, link };
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up and add badges to typedoc leaf sections
|
||||
*/
|
||||
/** Clean up and add badges to typedoc leaf sections */
|
||||
export function prepareTypedocSidebar(items: SidebarItem[]) {
|
||||
// skip contents file
|
||||
const filtered = items.slice(1);
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 237 KiB |
@@ -0,0 +1,435 @@
|
||||
# Add extension entries to end of the list. On the website, extensions will be sorted by a combination of users and rating.
|
||||
#
|
||||
# Change the commit message or PR title to:
|
||||
#
|
||||
# > "docs: Added "[extension name]" to showcase"
|
||||
#
|
||||
# Use:
|
||||
#
|
||||
# - `chromeId` for the Chrome Web Store listing,
|
||||
# - `firefoxSlug` for Firefox Add-ons (slug from https://addons.mozilla.org/firefox/addon/{slug}/)
|
||||
#
|
||||
# You may include one or more of these fields for your extension.
|
||||
|
||||
- # GitHub: Better Line Counts
|
||||
chromeId: ocfdgncpifmegplaglcnglhioflaimkd
|
||||
firefoxSlug: github-better-line-counts
|
||||
|
||||
- # Anime Skip Player
|
||||
chromeId: mgmdkjcljneegjfajchedjpdhbadklcf
|
||||
firefoxSlug: anime-skip
|
||||
|
||||
- # UltraWideo
|
||||
chromeId: bfbnagnphiehemkdgmmficmjfddgfhpl
|
||||
firefoxSlug: ultrawideo
|
||||
|
||||
- # StayFree - Website Blocker & Web Analytics
|
||||
chromeId: elfaihghhjjoknimpccccmkioofjjfkf
|
||||
firefoxSlug: stayfree
|
||||
|
||||
- # Doozy: Ai Made Easy
|
||||
chromeId: okifoaikfmpfcamplcfjkpdnhfodpkil
|
||||
|
||||
- # tl;dv - Record, Transcribe & ChatGPT for Google Meet
|
||||
chromeId: lknmjhcajhfbbglglccadlfdjbaiifig
|
||||
|
||||
- # Youtube中文配音
|
||||
chromeId: oglffgiaiekgeicdgkdlnlkhliajdlja
|
||||
|
||||
- # PreMiD
|
||||
chromeId: agjnjboanicjcpenljmaaigopkgdnihi
|
||||
|
||||
- # Markdown Sticky Notes
|
||||
chromeId: aiakblgmlabokilgljkglggnpflljdgp
|
||||
firefoxSlug: markdown-sticky-notes
|
||||
|
||||
- # DocVersionRedirector
|
||||
chromeId: nomnkbngkijpffepcgbbofhcnafpkiep
|
||||
|
||||
- # Plex Skipper
|
||||
chromeId: ceicccfeikoipigeghddpocceifjelph
|
||||
firefoxSlug: plex-skipper
|
||||
|
||||
- # GitHub Custom Notifier
|
||||
chromeId: aelkipgppclpfimeamgmlonimflbhlgf
|
||||
firefoxSlug: github-custom-notifier
|
||||
|
||||
- # Fluent Read
|
||||
chromeId: djnlaiohfaaifbibleebjggkghlmcpcj
|
||||
|
||||
- # Facebook Video Controls
|
||||
chromeId: nhclljcpfmmaiojbhhnkpjcfmacfcian
|
||||
firefoxSlug: facebook-video-controls
|
||||
|
||||
- # ElemSnap - Quick capture of webpage elements and conversion to images,
|
||||
chromeId: mblkhbaakhbhiimkbcnmeciblfhmafna
|
||||
|
||||
- # MS Edge TTS (Text to Speech)
|
||||
chromeId: oajalfneblkfiejoadecnmodfpnaeblh
|
||||
firefoxSlug: ms-edge-tts-text-to-speech
|
||||
|
||||
- # YTBlock - Block any content from YouTube™
|
||||
chromeId: nedcanggplmbbgmlpcjiafgjcpdimpea
|
||||
|
||||
- # demo.fun - Interactive product demos that convert
|
||||
chromeId: oadbjpccljkplmhnjekgjamejnbadlne
|
||||
|
||||
- # SmartEReply: Elevate Your LinkedIn™ Engagement with AI 🚀📈
|
||||
chromeId: iopdafdcollfgaoffingmahpffckmjni
|
||||
|
||||
- # WorkFlowy MultiFlow
|
||||
chromeId: khjdmjcmpolknpccmaaipmidphjokhdf
|
||||
|
||||
- # 香草布丁🌿🍮- https://github.com/Xdy1579883916/vanilla-pudding
|
||||
chromeId: fencadnndhdeggodopebjgdfdlhcimfk
|
||||
|
||||
- # MaxFocus: Link Preview
|
||||
chromeId: bnacincmbaknlbegecpioobkfgejlojp
|
||||
firefoxSlug: maxfocus-link-preview
|
||||
|
||||
- # 汇率转换-中文版本
|
||||
chromeId: bcpgdpedphodjcjlminjbdeejccjbimp
|
||||
|
||||
- # Currency Converter Plus
|
||||
chromeId: loeilaonggnalkaiiaepbegccilkmjjp
|
||||
firefoxSlug: currency-converter-plus
|
||||
|
||||
- # Respond Easy
|
||||
chromeId: npcnninnjghigjfiecefheeibomjpkak
|
||||
|
||||
- # mindful - stay focused on your goals
|
||||
chromeId: cfkdcideecefncbglkhneoflfnmhoicc
|
||||
|
||||
- # 1Proompt
|
||||
chromeId: lnhejcpclabmbgpiiomjbhalblnnbffg
|
||||
|
||||
- # NiceTab - https://github.com/web-dahuyou/NiceTab
|
||||
chromeId: fonflmjnjbkigocpoommgmhljdpljain
|
||||
|
||||
- # Draftly for LinkedIn
|
||||
chromeId: fcffekbnfcfdemeekijbbmgmkognnmkd
|
||||
|
||||
- # YouTube Summarized - Summarize any YouTube video
|
||||
chromeId: nkndldfehcidpejfkokbeghpnlbppdmo
|
||||
|
||||
- # 社媒助手 - https://github.com/iszhouhua/social-media-copilot
|
||||
chromeId: dbichmdlbjdeplpkhcejgkakobjbjalc
|
||||
|
||||
- # Dofollow Links for SEO
|
||||
chromeId: opepfpjeogkbgeigkbepobceinnfmjdd
|
||||
|
||||
- # ChatGPT Writer: Use AI on Any Site (GPT-4o, Claude, Gemini, and More)
|
||||
chromeId: pdnenlnelpdomajfejgapbdpmjkfpjkp
|
||||
|
||||
- # discord message translator
|
||||
chromeId: jobnhifpphkgoelnhnopgkdhbdkiadmj
|
||||
|
||||
- # Habit Tracker app widget for daily habit tracking
|
||||
chromeId: ncokhechhpjgjonhjnlaneglmdkfkcbj
|
||||
|
||||
- # Catppuccin for GitHub File Explorer Icons
|
||||
chromeId: lnjaiaapbakfhlbjenjkhffcdpoompki
|
||||
firefoxSlug: catppuccin-web-file-icons
|
||||
|
||||
- # WebChat: Chat with anyone on any website
|
||||
chromeId: cpaedhbidlpnbdfegakhiamfpndhjpgf
|
||||
|
||||
- # YouTube Auto HD + FPS
|
||||
chromeId: fcphghnknhkimeagdglkljinmpbagone
|
||||
firefoxSlug: youtube-auto-hd-fps
|
||||
|
||||
- # MultiViewer Companion
|
||||
chromeId: lpomjgbicdemjkgmbnkjncgdebogkhlb
|
||||
firefoxSlug: multiviewer-companion
|
||||
|
||||
- # Sync Watch - Watch videos together on any site
|
||||
chromeId: ggiafipgeeaaahnjamgpjcgkdpanhddg
|
||||
firefoxSlug: syncwatch
|
||||
|
||||
- # Keyword Rank Checker
|
||||
chromeId: nmldnjcblcihmegipecakhmnieiofmgl
|
||||
firefoxSlug: keyword-rank-checker-sa
|
||||
|
||||
- # YouTube Simple View - Hide distractions & more
|
||||
chromeId: gppllamhaciichleihemgilcpledblpn
|
||||
firefoxSlug: youtube-simple-view
|
||||
|
||||
- # Propbar - Property Data Enhancer
|
||||
chromeId: pccbghdfdnnkkbcdcibchpbffdgednkf
|
||||
|
||||
- # Text Search Pro - Search by case and whole-word match!
|
||||
chromeId: lfknakglefggmdkjdfhhofkjnnolffkh
|
||||
firefoxSlug: text-search-pro-ext
|
||||
|
||||
- # Invoice Generator
|
||||
chromeId: mbenhbocjckkbaojacmaepiameldglij
|
||||
firefoxSlug: quick-invoice-generator
|
||||
|
||||
- # Monthly Bill Tracker
|
||||
chromeId: phlfhkmdofajnbhgmbmjkbkdgppgoppb
|
||||
|
||||
- # Wandpen - Instantly improve your writing with AI
|
||||
chromeId: macmkmchfoclhpbncclinhjflmdkaoom
|
||||
|
||||
- # YouTube Hider - Remove Comments By Keywords, Usernames & Tools
|
||||
chromeId: lhmgechokhmdekdpgkkemoeecelcaonm
|
||||
|
||||
- # QA Compass - Record standardized bug reports easily
|
||||
chromeId: imgheieooppmahcgniieddodaliodeeg
|
||||
|
||||
- # aesthetic Notion, styled
|
||||
chromeId: npgghjedpchajflknnbngajkjkdhncdo
|
||||
|
||||
- # Eye Dropper
|
||||
chromeId: hmdcmlfkchdmnmnmheododdhjedfccka
|
||||
firefoxSlug: eye_dropper
|
||||
|
||||
- # Cursorful - Screen Recorder with Auto Zoom
|
||||
chromeId: eihpmapodnppeemkhkbhikmggfojdkjd
|
||||
|
||||
- # Show IP – Live View of Website IPs for Developers
|
||||
chromeId: hjjkgbibknbahijglkffklflidncplkn
|
||||
|
||||
- # Strong Password Generator
|
||||
chromeId: ilbikcehnpkmldojkcmlldkoelofnbde
|
||||
firefoxSlug: strongpasswordgenerator
|
||||
|
||||
- # ZenGram: Mindful Instagram, Your Way
|
||||
chromeId: ocllfkhcdopiafndigclebelbecaiocp
|
||||
|
||||
- # Blync: Preview Links, Selection Search, AI Assistant
|
||||
chromeId: odffpjnpocjfcaclnenaaaddghkgijdb
|
||||
|
||||
- # HTML to Markdown - Convert webpages to markdown
|
||||
chromeId: kofbbilhmnkcmibjbioafflgmpkbnmme
|
||||
|
||||
- # Walmart WFS Profit Calculator
|
||||
chromeId: boecmgggeigllcdocgioijmleimjbfkg
|
||||
firefoxSlug: walmart-wfs-profit-calculator
|
||||
|
||||
- # Youtube Live Chat Fullscreen
|
||||
chromeId: dlnjcbkmomenmieechnmgglgcljhoepd
|
||||
firefoxSlug: youtube-live-chat-fullscreen
|
||||
|
||||
- # Python Code Runner
|
||||
chromeId: keiealdacakpnbbljlmhfgcebmaadieg
|
||||
firefoxSlug: code-runner-manager
|
||||
|
||||
- # Monochromate
|
||||
chromeId: hafcajcllbjnoolpfngclfmmgpikdhlm
|
||||
|
||||
- # AliasVault - Open-Source Password & (Email) Alias Manager
|
||||
chromeId: bmoggiinmnodjphdjnmpcnlleamkfedj
|
||||
|
||||
- # SnapThePrice: AI-Powered Real-time Lowest Price Finder
|
||||
chromeId: hlnhhamckimoaiekbglafiebkfimhapb
|
||||
|
||||
- # radiofrance - news & broadcasts (French), music (international)
|
||||
chromeId: gdjampjdgjmbifnhldgcnccdjkcoicmg
|
||||
|
||||
- # Blens - Time Tracker and AI Insight
|
||||
chromeId: jlnhphlghikichhgbnkepenehbmloenb
|
||||
firefoxSlug: blens-timetracker-ai-insight
|
||||
|
||||
- # Always Light Mode - Setting website always in light mode
|
||||
chromeId: njnammmpdodmfkodnfpammnpdcbhnlcm
|
||||
|
||||
- # DesignPicker - Color Picker & Font Detector
|
||||
chromeId: lblmfclcfniabobmamfkdogcgdagbhhb
|
||||
firefoxSlug: design-colorpicker-fontdetect
|
||||
|
||||
- # Web to PDF
|
||||
chromeId: pamnlaoeobcmhkliljfaofekeddpmfoh
|
||||
firefoxSlug: export-web-to-pdf
|
||||
|
||||
- # Online CSV Viewer
|
||||
chromeId: jmbcbeepjfenihlocplnbmbhimcoooka
|
||||
firefoxSlug: csv-viewer
|
||||
|
||||
- # YouTube Video Transcript
|
||||
chromeId: nkjcoophmpcmmgadnljnlpbpfdfacgbo
|
||||
firefoxSlug: youtube-transcript-copy
|
||||
|
||||
- # NetSuite Record Scripts
|
||||
chromeId: lcaieahkjgeggeiihblhcjbbjlppgieh
|
||||
firefoxSlug: netsuite-scripts-manager
|
||||
|
||||
- # VueTracker
|
||||
chromeId: gmocfknjllodfiomnljmaehcplnekhlo
|
||||
|
||||
- # CanCopy - A web extension that allow you to copy any content from website
|
||||
chromeId: ggcfemmoabhhelfkhknhbnkmeahloiod
|
||||
|
||||
- # Language Learning with AI
|
||||
chromeId: modkelfkcfjpgbfmnbnllalkiogfofhb
|
||||
firefoxSlug: intersub
|
||||
|
||||
- # Bilibili Feed History Helper
|
||||
chromeId: npfopljnjbamegincfjelhjhnonnjloo
|
||||
|
||||
- # NZBDonkey - The ultimate NZB file download tool
|
||||
chromeId: edkhpdceeinkcacjdgebjehipmnbomce
|
||||
|
||||
- # WeChat Markdown Editor(微信 Markdown 编辑器)
|
||||
chromeId: cckggnbnimdbbpmdinkkgbbncopbloob
|
||||
|
||||
- # Tab Grab
|
||||
chromeId: jcblcjolcojmfopefcighfmkkefbaofg
|
||||
firefoxSlug: tab-grab
|
||||
|
||||
- # BrowserLens - https://browserlens.com/
|
||||
chromeId: eehmoikadcijkapfjocnhjclpbaindlb
|
||||
firefoxSlug: browserlens
|
||||
|
||||
- # Epic Games Library Extension
|
||||
chromeId: hfhellofkjebbchcdffmicekjdomkcmc
|
||||
firefoxSlug: epic-games-library-extension
|
||||
|
||||
- # Zen Analytics Pixel Tracker - zapt.web.app
|
||||
chromeId: gknigcbhlammoakmmdddkblknanpjiac
|
||||
|
||||
- # Crypto Pulse - Compose your newtab with nature images, widgets & realtime Crypto Price & Bitcoin RSS.
|
||||
chromeId: cnklededohhcbmjjdlbjdkkihkgoggol
|
||||
|
||||
- # Youtube Video Scheduler
|
||||
chromeId: miponnamafdenpgjemkknimgjfibicdc
|
||||
firefoxSlug: youtube-video-scheduler
|
||||
|
||||
- # Chatslator: Livestream Chat Translator
|
||||
chromeId: nhmbcmalgpkjbomhlhgdicanmkkaajmg
|
||||
firefoxSlug: chatslator
|
||||
|
||||
- # 公众号阅读增强器 - https://wxreader.honwhy.wang
|
||||
chromeId: mbamjfdjbcdgpopfnkkmlohadbbnplhm
|
||||
|
||||
- # 토탐정
|
||||
chromeId: hannhecbnjnnbbafffmogdlnajpcomek
|
||||
firefoxSlug: 토탐정
|
||||
|
||||
- # 2FAS Pass - https://2fas.com/
|
||||
chromeId: ehboaofjncodknjkngdggmpdinhdoijp
|
||||
firefoxSlug: 2fas-pass-browser-extension
|
||||
|
||||
- # Quick Prompt - https://github.com/wenyuanw/quick-prompt
|
||||
chromeId: hnjamiaoicaepbkhdoknhhcedjdocpkd
|
||||
firefoxSlug: quick-prompt
|
||||
|
||||
- # Add QR Code Generator Icon Back To Address Bar
|
||||
chromeId: kacblhilkacgfnkjfodalohcnllcgmjd
|
||||
|
||||
- # Piwik PRO Tracking Helper
|
||||
chromeId: fkbdlogfdjmpfepbbbjcgcfbgbcfcnne
|
||||
firefoxSlug: piwik-pro-tracking-helper
|
||||
|
||||
- # PIPX - Take Control of Picture-in-Picture, Automatically
|
||||
chromeId: nkbikckldmljjiiajklecmgmajgapbfl
|
||||
|
||||
- # Browsely - AI-powered browser extension
|
||||
chromeId: hgppdobcpkfkmiegekaglonjajeojmdd
|
||||
|
||||
- # Filmbudd Pro - Simple, private – and synced ratings and watch notes across all your devices
|
||||
chromeId: ehmoihnjgkdimihkhokkmfjdgomohjgm
|
||||
|
||||
- # MultiField CopyCat - Copy, Paste & Autofill Web Forms Instantly
|
||||
chromeId: alglchohmdikgdjhafiicilegegieafa
|
||||
|
||||
- # ChatSight - Add Table of Contents to ChatGPT
|
||||
chromeId: aamihahiiogceidpbnfgehacgiecephe
|
||||
|
||||
- # BetterCampus (prev. BetterCanvas)
|
||||
chromeId: cndibmoanboadcifjkjbdpjgfedanolh
|
||||
firefoxSlug: better-canvas
|
||||
|
||||
- # Leetcode Fonts - Change fonts in leetcode effortlessly
|
||||
chromeId: hinfimgacobnellbncbcpdlpaapcofaa
|
||||
|
||||
- # TranslateManga - Manga Translator & Manga Tracker
|
||||
chromeId: kbkbfefhhabpkibojinapkkgciiacggg
|
||||
|
||||
- # SiteData - Free Website Traffic Checker & Reverse AdSense Tool
|
||||
chromeId: emeakbgdecgmdjgegnejpppcnkcnoaen
|
||||
firefoxSlug: sitedata
|
||||
|
||||
- # Livestream Chat Reader - Text-to-Speech for YouTube/Twitch chat
|
||||
chromeId: gpnckbhgpnjciklpoehkmligeaebigaa
|
||||
|
||||
- # ChatGPT Token Counter - Count tokens in real time on chatgpt conversation
|
||||
chromeId: fjlalaedpfcojcfpkgkglbjjbbkofgnl
|
||||
firefoxSlug: chatgpt-token-counter
|
||||
|
||||
- # LinuxDo Scripts - 为 linux.do 用户提供了一些增强功能
|
||||
chromeId: fbgblmjbeebanackldpbmpacppflgmlj
|
||||
firefoxSlug: linux_do-scripts
|
||||
|
||||
- # Zen Virtual Piano - https://zen-piano.web.app/
|
||||
chromeId: dfacnjidgbagicaekenjgclfnhdnjjdi
|
||||
|
||||
- # Crypto Pulse price tracker - https://get-crypto-pulse.web.app/
|
||||
chromeId: naeibcegmgpofimedkmfgjgphfhfhlab
|
||||
|
||||
- # Redirect Web - Automatically redirect pages or open them in another app
|
||||
chromeId: ffglckbhfbfmdkefdmjbhpnffkcmlhdh
|
||||
|
||||
- # Capture It - Capture & Edit Screenshots
|
||||
chromeId: eglpfhbhmelampoihamjomgkeobgdofl
|
||||
|
||||
- # Teams Chat Exporter
|
||||
chromeId: jmghclbfbbapimhbgnpffbimphlpolnm
|
||||
firefoxSlug: teams-chat-exporter
|
||||
|
||||
- # Lofi BGM Player - Free lofi focus music for work & study
|
||||
chromeId: jdcppdokgfbnhiacbeplahgnciahnhck
|
||||
firefoxSlug: lofi-bgm-player
|
||||
|
||||
- # Margin - Annotate and highlight any webpage, with your notes saved to the decentralized AT Protocol.
|
||||
chromeId: cgpmbiiagnehkikhcbnhiagfomajncpa
|
||||
|
||||
- # KeyFloat - Floating multilingual keyboard with native key mappings, drag, dark mode, sounds, and dynamic layouts for macOS & Windows
|
||||
chromeId: mfjdonmgmgcijagclnkfhmjiblbfjaid
|
||||
|
||||
- # Glossy New Tab - Say Goodbye to Boring Tabs with live wallpapers
|
||||
chromeId: dhiekgdaipindoapjmcnpompdknjeijf
|
||||
firefoxSlug: glossy-new-tab
|
||||
|
||||
- # All API Hub – AI Relay & New API Manager - https://github.com/qixing-jk/all-api-hub
|
||||
chromeId: lapnciffpekdengooeolaienkeoilfeo
|
||||
firefoxSlug: all-api-hub
|
||||
|
||||
- # Scrape Similar - Extract data from websites into spreadsheets - https://github.com/zizzfizzix/scrape-similar
|
||||
chromeId: bhgobenflkkhfcgkikejaaejenoddcmo
|
||||
|
||||
- # isTrust - https://github.com/Internet-Society-Belgium/isTrust/
|
||||
chromeId: kinlknncggaihnhdcalijdmpbhbflalm
|
||||
|
||||
- # Dymo
|
||||
chromeId: ojpakgiekphppgkcdihbjpafobhnhlkp
|
||||
|
||||
- # Extension Rank Checker - Extension Ranker
|
||||
chromeId: pmgehhllikbjmadpenhabejhpemplhmd
|
||||
firefoxSlug: rank-checker
|
||||
|
||||
- # PlayFaster - Enhanced playback speed control for online videos and audio
|
||||
chromeId: fppcbkhpahkbgijkdcpjgjmhpfbmfiih
|
||||
firefoxSlug: playfaster
|
||||
|
||||
- # LatTab - Learn Latin With Every New Tab
|
||||
chromeId: eiocjaocpmackhbaffoejkcmnfbdpgpj
|
||||
firefoxSlug: lattab-learn-latin-new-tabs
|
||||
|
||||
- # Vim What? - Interactive Vim command reference
|
||||
chromeId: ngbehgnlcdjkbnihgpkgdangbhemidge
|
||||
firefoxSlug: vim-what
|
||||
|
||||
- # FRED - Fraud Recognition Easy Detection
|
||||
chromeId: bjdbcabacnlmbpcmiapcdfancfgcakfn
|
||||
firefoxSlug: fred
|
||||
|
||||
- # Clear Wisdom - Gems of wisdom from James Clear's 3-2-1 newsletter
|
||||
chromeId: jijfmgoijddfmlcdghopbkdpelbpmjdm
|
||||
firefoxSlug: clear-wisdom
|
||||
|
||||
- # Soundcloud Enhanced Pro
|
||||
chromeId: ggplcohodggmdfpopelnpplhgfjclomi
|
||||
|
||||
- # AlarmBot: ULTIMATE Web Monitoring & Smart Price Alerts
|
||||
chromeId: mpckalcodookackleecihhnngdibelif
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 136 KiB |
@@ -159,6 +159,30 @@ export default defineConfig({
|
||||
|
||||
Alternatively, you can use [`@wxt-dev/auto-icons`](https://www.npmjs.com/package/@wxt-dev/auto-icons) to let WXT generate your icon at the required sizes.
|
||||
|
||||
### Firefox `theme_icons`
|
||||
|
||||
Firefox supports a [`theme_icons`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/action#theme_icons) field on the toolbar action that swaps between a light and dark variant based on the current browser theme.
|
||||
|
||||
When [targeting Firefox](/guide/essentials/target-different-browsers.md), WXT auto-discovers paired light/dark icons in the `public/` directory and attaches them to `action` (MV3) or `browser_action` (MV2). You only need to drop both variants next to your regular icons:
|
||||
|
||||
```plaintext
|
||||
public/
|
||||
├─ icon-16.png # regular (default_icon)
|
||||
├─ icon-light-16.png # Firefox light theme
|
||||
├─ icon-dark-16.png # Firefox dark theme
|
||||
├─ icon-32.png
|
||||
├─ icon-light-32.png
|
||||
└─ icon-dark-32.png
|
||||
```
|
||||
|
||||
A size is only included in `theme_icons` if **both** a light and a dark file are present. The following filename patterns are discovered:
|
||||
|
||||
<<< @/../packages/wxt/src/core/utils/theme-icons.ts#snippet
|
||||
|
||||
Only `.png` files are discovered today, even though Firefox supports `.svg` - follow [#1120](https://github.com/wxt-dev/wxt/issues/1120) for updates.
|
||||
|
||||
If you set `manifest.action.theme_icons` (or `manifest.browser_action.theme_icons`) explicitly in `wxt.config.ts`, WXT will not overwrite it.
|
||||
|
||||
## Permissions
|
||||
|
||||
> [Chrome docs](https://developer.chrome.com/docs/extensions/reference/permissions/)
|
||||
|
||||
@@ -23,12 +23,12 @@ export default defineAppConfig({
|
||||
This file is committed to the repo, so don't put any secrets here. Instead, use [Environment Variables](/guide/essentials/config/environment-variables)
|
||||
:::
|
||||
|
||||
To access runtime config, WXT provides the `useAppConfig` function:
|
||||
To access runtime config, WXT provides the `getAppConfig` function:
|
||||
|
||||
```ts
|
||||
import { useAppConfig } from '#imports';
|
||||
import { getAppConfig } from '#imports';
|
||||
|
||||
console.log(useAppConfig()); // { theme: "dark" }
|
||||
console.log(getAppConfig()); // { theme: "dark" }
|
||||
```
|
||||
|
||||
## Environment Variables in App Config
|
||||
|
||||
@@ -25,7 +25,7 @@ export default defineBackground({
|
||||
});
|
||||
```
|
||||
|
||||
This will change the output format to ESM, enable code-spliting between your background script and HTML pages, and set `"type": "module"` in your manifest.
|
||||
This will change the output format to ESM, enable code-splitting between your background script and HTML pages, and set `"type": "module"` in your manifest.
|
||||
|
||||
:::warning
|
||||
Only MV3 supports ESM background scripts/service workers. When targeting MV2, the `type` option is ignored and the background is always bundled into a single file as IIFE.
|
||||
|
||||
@@ -76,6 +76,28 @@ Alternatively, if you're trying to use similar APIs under different names (to su
|
||||
});
|
||||
```
|
||||
|
||||
### Augmenting the Browser Type
|
||||
|
||||
WXT's `browser` types are based on the `@types/chrome` package. That means some Firefox-specific APIs may not be typed, like `browser.sidebarAction`. If you want to add types for these APIs, you can augment the browser type to add them yourself:
|
||||
|
||||
```ts
|
||||
// <srcDir>/browser-types.d.ts
|
||||
import '@wxt-dev/browser';
|
||||
import type { SidebarAction } from 'webextension-polyfill';
|
||||
|
||||
declare module '@wxt-dev/browser' {
|
||||
namespace Browser {
|
||||
export const sidebarAction: SidebarAction.Static;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> For this to work, you may need to install `@wxt-dev/browser` as a direct dependency.
|
||||
>
|
||||
> ```sh
|
||||
> pnpm add @wxt-dev/browser
|
||||
> ```
|
||||
|
||||
## Entrypoint Limitations
|
||||
|
||||
Because WXT imports your entrypoint files into a NodeJS, non-extension environment, the `chrome`/`browser` variables provided to extensions by the browser **will not be available**.
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Favicons
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/how-to/ui/favicons)
|
||||
|
||||
Chromium-based browsers expose cached favicons through the `_favicon/` URL served from your extension. To use it, declare the `favicon` permission in your manifest and call `browser.runtime.getURL` with a `_favicon/` path:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
permissions: ['favicon'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
// Any entrypoint
|
||||
function getFaviconUrl(pageUrl: string, size = 16) {
|
||||
const url = new URL(browser.runtime.getURL('/_favicon/'));
|
||||
url.searchParams.set('pageUrl', pageUrl);
|
||||
url.searchParams.set('size', String(size));
|
||||
return url.toString();
|
||||
}
|
||||
```
|
||||
|
||||
::: warning Chromium only
|
||||
The favicon API is only available on Chromium-based browsers. Firefox has no equivalent, so `/_favicon/` URLs will not resolve at runtime there. If your extension supports both browsers, gate favicon usage behind `import.meta.env.CHROME` (or similar) and declare the permission per-browser.
|
||||
:::
|
||||
|
||||
## Usage from a content script
|
||||
|
||||
WXT does **not** add a `web_accessible_resources` entry for `_favicon/*` — not every extension needs one, and adding it unconditionally would expose internals to sites that don't need them. If you want to load a favicon inside a content script (for example, as an `<img src>`), add your own entry in `wxt.config.ts`:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
permissions: ['favicon'],
|
||||
web_accessible_resources: [
|
||||
{
|
||||
resources: ['_favicon/*'],
|
||||
matches: ['<all_urls>'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -163,8 +163,8 @@ yarn zip:firefox
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun i
|
||||
bun zip:firefox
|
||||
bun install
|
||||
bun run zip:firefox
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# WXT Modules
|
||||
|
||||
WXT provides a "module system" that let's you run code at different steps in the build process to modify it.
|
||||
@@ -131,12 +135,64 @@ declare module 'wxt/utils/define-app-config' {
|
||||
Runtime options are returned when calling
|
||||
|
||||
```ts
|
||||
const config = useAppConfig();
|
||||
const config = getAppConfig();
|
||||
console.log(config.myModule);
|
||||
```
|
||||
|
||||
This is very useful when [generating runtime code](#generate-runtime-module).
|
||||
|
||||
#### Add custom entrypoint options
|
||||
|
||||
Modules can add custom options to entrypoints by augmenting the entrypoint options types. This allows you to add custom configuration that can be accessed during the build process.
|
||||
|
||||
```ts
|
||||
import { defineWxtModule } from 'wxt/modules';
|
||||
import 'wxt';
|
||||
|
||||
declare module 'wxt' {
|
||||
export interface BackgroundEntrypointOptions {
|
||||
// Add custom options to the background entrypoint
|
||||
myCustomOption?: string;
|
||||
}
|
||||
}
|
||||
|
||||
export default defineWxtModule({
|
||||
setup(wxt) {
|
||||
wxt.hook('entrypoints:resolved', (_, entrypoints) => {
|
||||
const background = entrypoints.find((e) => e.type === 'background');
|
||||
if (background) {
|
||||
console.log('Custom option:', background.options.myCustomOption);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Now users can set the custom option in their entrypoint:
|
||||
|
||||
```ts [entrypoints/background.ts]
|
||||
export default defineBackground({
|
||||
myCustomOption: 'custom value',
|
||||
main() {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This works for all other JS and HTML entrypoints, here's an example of how to pass a custom option from an HTML file.
|
||||
|
||||
```html [entrypoints/popup.html]
|
||||
<html>
|
||||
<head>
|
||||
<meta name="wxt.myHtmlOption" content="custom value" />
|
||||
<title>Popup</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- ... -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
#### Generate output file
|
||||
|
||||
```ts
|
||||
@@ -220,7 +276,7 @@ export default defineWxtModule({
|
||||
const analyticsModuleCode = `
|
||||
import { createAnalytics } from 'some-module';
|
||||
|
||||
export const analytics = createAnalytics(useAppConfig().analytics);
|
||||
export const analytics = createAnalytics(getAppConfig().analytics);
|
||||
export const { reportEvent, reportPageView } = analytics;
|
||||
`;
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ Once you've run the `dev` command, continue to [Next Steps](#next-steps)!
|
||||
```
|
||||
|
||||
```sh [Bun]
|
||||
bun i -D wxt
|
||||
bun add -D wxt
|
||||
```
|
||||
|
||||
```sh [NPM]
|
||||
|
||||
@@ -13,3 +13,4 @@ This page is dedicated to all the awesome people how have made something for WXT
|
||||
|
||||
- [`@webext-core/*`](https://webext-core.aklinker1.io/): Easy-to-use utilities for writing and testing web extensions that work on all browsers.
|
||||
- [`Comctx`](https://github.com/molvqingtai/comctx): Cross-context RPC solution with type safety and flexible adapters.
|
||||
- [`wxt-local-analytics`](https://github.com/HaNdTriX/wxt-local-analytics): Local analytics provider for [`@wxt-dev/analytics`](https://wxt.dev/analytics)
|
||||
|
||||
@@ -177,7 +177,7 @@ import { defineContentScript } from 'wxt/sandbox'; // [!code --]
|
||||
import { ContentScriptContext, useAppConfig } from 'wxt/client'; // [!code --]
|
||||
import { storage } from '#imports'; // [!code ++]
|
||||
import { defineContentScript } from '#imports'; // [!code ++]
|
||||
import { ContentScriptContext, useAppConfig } from '#imports'; // [!code ++]
|
||||
import { ContentScriptContext, getAppConfig } from '#imports'; // [!code ++]
|
||||
```
|
||||
|
||||
You can combine the imports into a single import statement, but it's easier to just find/replace each statement.
|
||||
@@ -191,7 +191,7 @@ import {
|
||||
storage, // [!code ++]
|
||||
defineContentScript, // [!code ++]
|
||||
ContentScriptContext, // [!code ++]
|
||||
useAppConfig, // [!code ++]
|
||||
getAppConfig, // [!code ++]
|
||||
} from '#imports'; // [!code ++]
|
||||
```
|
||||
|
||||
@@ -476,7 +476,7 @@ export default defineConfig({
|
||||
|
||||
### Renamed Undocumented Constants
|
||||
|
||||
Renamed undocumented constants for detecting the build config at runtime in [#380](https://github.com/wxt-dev/wxt/pull/380). Now documented here: <https://wxt.dev/guide/multiple-browsers.html#runtime>
|
||||
Renamed undocumented constants for detecting the build config at runtime in [#380](https://github.com/wxt-dev/wxt/pull/380). Now documented here: <https://wxt.dev/guide/essentials/config/environment-variables.html#built-in-environment-variables>
|
||||
|
||||
- `__BROWSER__` → `import.meta.env.BROWSER`
|
||||
- `__COMMAND__` → `import.meta.env.COMMAND`
|
||||
@@ -557,6 +557,8 @@ Unlisted scripts must now `export default defineUnlistedScript(...)`.
|
||||
|
||||
### `BackgroundDefinition` Type
|
||||
|
||||
<!-- cspell:disable -->
|
||||
|
||||
Rename `BackgroundScriptDefintition` to `BackgroundDefinition`.
|
||||
|
||||
## v0.6.0 → v0.7.0
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<!--@include: ../packages/is-background/README.md-->
|
||||
+3
-5
@@ -4,7 +4,7 @@ outline: deep
|
||||
|
||||
# WXT Storage
|
||||
|
||||
[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md)
|
||||
[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md) • [API Reference](/api/reference/wxt/utils/storage/interfaces/WxtStorage)
|
||||
|
||||
A simplified wrapper around the extension storage APIs.
|
||||
|
||||
@@ -74,7 +74,7 @@ await storage.watch<number>(
|
||||
await storage.getMeta<{ v: number }>('local:installDate');
|
||||
```
|
||||
|
||||
For a full list of methods available, see the [API reference](/api/reference/wxt/utils/storage/interfaces/WxtStorage).
|
||||
> This approach is fine for one-off storage fields or generic helpers, but [defining storage items](#defining-storage-items) is the recommended way to add type-safety.
|
||||
|
||||
## Watchers
|
||||
|
||||
@@ -146,7 +146,7 @@ const showChangelogOnUpdate = storage.defineItem<boolean>(
|
||||
);
|
||||
```
|
||||
|
||||
Now, instead of using the `storage` variable, you can use the helper functions on the storage item you created:
|
||||
Now, instead of using the `storage` variable, you can use the storage item instead:
|
||||
|
||||
```ts
|
||||
await showChangelogOnUpdate.getValue();
|
||||
@@ -157,8 +157,6 @@ const unwatch = showChangelogOnUpdate.watch((newValue) => {
|
||||
});
|
||||
```
|
||||
|
||||
For a full list of properties and methods available, see the [API reference](/api/reference/wxt/utils/storage/interfaces/WxtStorageItem).
|
||||
|
||||
### Versioning
|
||||
|
||||
You can add versioning to storage items if you expect them to grow or change over time. When defining the first version of an item, start with version 1.
|
||||
|
||||
@@ -26,7 +26,7 @@ Set FontSize 32
|
||||
# Terminal settings
|
||||
Set Shell "bash"
|
||||
|
||||
# Terminal prompt. It looks like "● mycommand ..."
|
||||
# Terminal prompt. It looks like "● command ..."
|
||||
Env PS1 "\e[0;32m●\e[0m "
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
[build]
|
||||
command = "bun run docs:build"
|
||||
publish = "docs/.vitepress/dist"
|
||||
|
||||
[build.environment]
|
||||
BUN_VERSION = "1.3.5"
|
||||
+33
-24
@@ -2,41 +2,45 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18.20.3"
|
||||
"bun": ">=1.3.5"
|
||||
},
|
||||
"packageManager": "pnpm@10.29.2+sha512.bef43fa759d91fd2da4b319a5a0d13ef7a45bb985a3d7342058470f9d2051a3ba8674e629672654686ef9443ad13a82da2beb9eeb3e0221c87b8154fff9d74b8",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"check": "check && pnpm -r --sequential run check",
|
||||
"test": "pnpm -r --sequential run test run",
|
||||
"test:coverage": "pnpm -r --sequential run test:coverage",
|
||||
"check": "check && bun run --workspaces check",
|
||||
"test": "bun run --workspaces test run",
|
||||
"test:coverage": "bun run --workspaces test:coverage",
|
||||
"prepare": "simple-git-hooks",
|
||||
"docs:gen": "typedoc --options docs/typedoc.json",
|
||||
"docs:dev": "pnpm -s docs:gen && vitepress dev docs",
|
||||
"docs:build": "pnpm -s docs:gen && vitepress build docs",
|
||||
"docs:preview": "pnpm -s docs:gen && vitepress preview docs"
|
||||
"docs:dev": "bun run --silent docs:gen && bun run --bun vitepress dev docs",
|
||||
"docs:build": "bun run --silent docs:gen && bun run --bun vitepress build docs",
|
||||
"docs:preview": "bun run --silent docs:gen && bun run --bun vitepress preview docs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.6",
|
||||
"@aklinker1/check": "^2.2.0",
|
||||
"@commitlint/config-conventional": "^20.4.1",
|
||||
"@commitlint/types": "^20.4.0",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@aklinker1/check": "^2.4.0",
|
||||
"@commitlint/config-conventional": "^20.4.3",
|
||||
"@commitlint/types": "^20.4.3",
|
||||
"@types/bun": "^1.3.5",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"changelogen": "^0.6.2",
|
||||
"consola": "^3.4.2",
|
||||
"fast-glob": "^3.3.3",
|
||||
"cspell": "^9.7.0",
|
||||
"feed": "^5.2.0",
|
||||
"fs-extra": "^11.3.3",
|
||||
"lint-staged": "^16.2.7",
|
||||
"markdown-it-footnote": "^4.0.0",
|
||||
"markdownlint-cli": "^0.47.0",
|
||||
"markdownlint-cli": "^0.48.0",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"nano-staged": "^0.8.0",
|
||||
"p-map": "^7.0.4",
|
||||
"pkg-pr-new": "^0.0.66",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-plugin-jsdoc": "^1.8.0",
|
||||
"semver": "^7.7.4",
|
||||
"simple-git-hooks": "^2.13.1",
|
||||
"tsdown": "^0.20.3",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"tsdown": "^0.21.0",
|
||||
"tsx": "4.21.0",
|
||||
"typedoc": "^0.25.4",
|
||||
"typedoc-plugin-frontmatter": "^1.3.1",
|
||||
@@ -45,16 +49,21 @@
|
||||
"typescript": "^5.9.3",
|
||||
"vitepress": "^1.6.4",
|
||||
"vitepress-knowledge": "^0.4.1",
|
||||
"vitepress-plugin-group-icons": "^1.7.1",
|
||||
"vitepress-plugin-llms": "^1.11.0",
|
||||
"vitepress-plugin-group-icons": "^1.7.3",
|
||||
"vitepress-plugin-llms": "^1.12.0",
|
||||
"vitest-mock-extended": "^3.1.0",
|
||||
"vue": "^3.5.27",
|
||||
"vue": "^3.5.29",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "pnpm lint-staged"
|
||||
"patchedDependencies": {
|
||||
"markdown-it-footnote@4.0.0": "patches/markdown-it-footnote.patch",
|
||||
"simple-git-hooks@2.13.1": "patches/simple-git-hooks@2.13.1.patch",
|
||||
"pkg-pr-new@0.0.66": "patches/pkg-pr-new@0.0.66.patch"
|
||||
},
|
||||
"lint-staged": {
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "./node_modules/.bin/nano-staged"
|
||||
},
|
||||
"nano-staged": {
|
||||
"*": "prettier --ignore-unknown --write"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,65 @@
|
||||
# Changelog
|
||||
|
||||
## v0.5.4
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.3...analytics-v0.5.4)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Continue using `useAppConfig` to support older versions of WXT ([bfd94556](https://github.com/wxt-dev/wxt/commit/bfd94556))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v0.5.3
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.2...analytics-v0.5.3)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Add `getAppConfig` as an alias to `useAppConfig` ([#2144](https://github.com/wxt-dev/wxt/pull/2144))
|
||||
- Allow `userId` option to return `undefined` ([636cf1f8](https://github.com/wxt-dev/wxt/commit/636cf1f8))
|
||||
- Improve background script detection logic for analytics package ([#1808](https://github.com/wxt-dev/wxt/pull/1808))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Created new types, instead of `any` for `analytics` ([#2119](https://github.com/wxt-dev/wxt/pull/2119))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Smit ([@sm17p](https://github.com/sm17p))
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
|
||||
- Sheng Zhang ([@Arktomson](https://github.com/Arktomson))
|
||||
|
||||
## v0.5.2
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.1...analytics-v0.5.2)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Normalize path for createAnalytics of analytics/index.ts ([#2013](https://github.com/wxt-dev/wxt/pull/2013))
|
||||
- Allow custom API URL in Google Analytics 4 provider options ([#1653](https://github.com/wxt-dev/wxt/pull/1653))
|
||||
|
||||
### 💅 Refactors
|
||||
|
||||
- Code cleanup in analytics package ([#2084](https://github.com/wxt-dev/wxt/pull/2084))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Fix other type error after `chrome` types update ([31ebf966](https://github.com/wxt-dev/wxt/commit/31ebf966))
|
||||
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
|
||||
- Use `tsdown` to build packages ([#2006](https://github.com/wxt-dev/wxt/pull/2006))
|
||||
- Move script-only dev dependencies to top-level `package.json` ([#2007](https://github.com/wxt-dev/wxt/pull/2007))
|
||||
- Update dependencies ([#2069](https://github.com/wxt-dev/wxt/pull/2069))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Honwhy Wang <honwhy.wang@gmail.com>
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
|
||||
|
||||
## v0.5.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.0...analytics-v0.5.1)
|
||||
|
||||
@@ -2,14 +2,30 @@ import { UAParser } from 'ua-parser-js';
|
||||
import type {
|
||||
Analytics,
|
||||
AnalyticsConfig,
|
||||
AnalyticsEventMetadata,
|
||||
AnalyticsPageViewEvent,
|
||||
AnalyticsProvider,
|
||||
AnalyticsStorageItem,
|
||||
AnalyticsTrackEvent,
|
||||
BaseAnalyticsEvent,
|
||||
AnalyticsEventMetadata,
|
||||
AnalyticsProvider,
|
||||
} from './types';
|
||||
import { browser } from '@wxt-dev/browser';
|
||||
import { isBackground } from '@wxt-dev/is-background';
|
||||
|
||||
type AnalyticsMessage = {
|
||||
[K in keyof Analytics]: {
|
||||
fn: K;
|
||||
args: Parameters<Analytics[K]>;
|
||||
};
|
||||
}[keyof Analytics];
|
||||
|
||||
type AnalyticsMethod =
|
||||
| ((...args: Parameters<Analytics[keyof Analytics]>) => void)
|
||||
| undefined;
|
||||
|
||||
type MethodForwarder = <K extends keyof Analytics>(
|
||||
fn: K,
|
||||
) => (...args: Parameters<Analytics[K]>) => void;
|
||||
|
||||
const ANALYTICS_PORT = '@wxt-dev/analytics';
|
||||
|
||||
@@ -40,16 +56,14 @@ export function createAnalytics(config?: AnalyticsConfig): Analytics {
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: This only works for standard WXT extensions, add a more generic
|
||||
// background script detector that works with non-WXT projects.
|
||||
if (location.pathname === '/background.js')
|
||||
return createBackgroundAnalytics(config);
|
||||
if (isBackground()) return createBackgroundAnalytics(config);
|
||||
|
||||
return createFrontendAnalytics();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an analytics client in the background responsible for uploading events to the server to avoid CORS errors.
|
||||
* Creates an analytics client in the background responsible for uploading
|
||||
* events to the server to avoid CORS errors.
|
||||
*/
|
||||
function createBackgroundAnalytics(
|
||||
config: AnalyticsConfig | undefined,
|
||||
@@ -163,7 +177,7 @@ function createBackgroundAnalytics(
|
||||
},
|
||||
track: async (
|
||||
eventName: string,
|
||||
eventProperties?: Record<string, string>,
|
||||
eventProperties?: Record<string, string | undefined>,
|
||||
meta: AnalyticsEventMetadata = getBackgroundMeta(),
|
||||
) => {
|
||||
const baseEvent = await getBaseEvent(meta);
|
||||
@@ -197,9 +211,8 @@ function createBackgroundAnalytics(
|
||||
// Listen for messages from the rest of the extension
|
||||
browser.runtime.onConnect.addListener((port) => {
|
||||
if (port.name === ANALYTICS_PORT) {
|
||||
port.onMessage.addListener(({ fn, args }) => {
|
||||
// @ts-expect-error: Untyped fn key
|
||||
void analytics[fn]?.(...args);
|
||||
port.onMessage.addListener(({ fn, args }: AnalyticsMessage) => {
|
||||
void (analytics[fn] as AnalyticsMethod)?.(...args);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -207,9 +220,7 @@ function createBackgroundAnalytics(
|
||||
return analytics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an analytics client for non-background contexts.
|
||||
*/
|
||||
/** Creates an analytics client for non-background contexts. */
|
||||
function createFrontendAnalytics(): Analytics {
|
||||
const port = browser.runtime.connect({ name: ANALYTICS_PORT });
|
||||
const sessionId = Date.now();
|
||||
@@ -217,17 +228,15 @@ function createFrontendAnalytics(): Analytics {
|
||||
sessionId,
|
||||
timestamp: Date.now(),
|
||||
language: navigator.language,
|
||||
referrer: globalThis.document?.referrer || undefined,
|
||||
screen: globalThis.window
|
||||
? `${globalThis.window.screen.width}x${globalThis.window.screen.height}`
|
||||
: undefined,
|
||||
referrer: document.referrer || undefined,
|
||||
screen: `${window.screen.width}x${window.screen.height}`,
|
||||
url: location.href,
|
||||
title: document.title || undefined,
|
||||
});
|
||||
|
||||
const methodForwarder =
|
||||
(fn: string) =>
|
||||
(...args: any[]) => {
|
||||
const methodForwarder: MethodForwarder =
|
||||
(fn) =>
|
||||
(...args) => {
|
||||
port.postMessage({ fn, args: [...args, getFrontendMetadata()] });
|
||||
};
|
||||
|
||||
@@ -238,11 +247,11 @@ function createFrontendAnalytics(): Analytics {
|
||||
setEnabled: methodForwarder('setEnabled'),
|
||||
autoTrack: (root) => {
|
||||
const onClick = (event: Event) => {
|
||||
const element = event.target as any;
|
||||
const element = event.target as HTMLElement | null;
|
||||
if (
|
||||
!element ||
|
||||
(!INTERACTIVE_TAGS.has(element.tagName) &&
|
||||
!INTERACTIVE_ROLES.has(element.getAttribute('role')))
|
||||
!INTERACTIVE_ROLES.has(element.getAttribute('role') ?? ''))
|
||||
)
|
||||
return;
|
||||
|
||||
@@ -251,7 +260,7 @@ function createFrontendAnalytics(): Analytics {
|
||||
id: element.id || undefined,
|
||||
className: element.className || undefined,
|
||||
textContent: element.textContent?.substring(0, 50) || undefined, // Limit text content length
|
||||
href: element.href,
|
||||
href: (element as HTMLAnchorElement).href,
|
||||
});
|
||||
};
|
||||
root.addEventListener('click', onClick, { capture: true, passive: true });
|
||||
@@ -263,13 +272,18 @@ function createFrontendAnalytics(): Analytics {
|
||||
return analytics;
|
||||
}
|
||||
|
||||
function defineStorageItem<T>(key: string): AnalyticsStorageItem<T | undefined>;
|
||||
function defineStorageItem<T>(
|
||||
key: string,
|
||||
defaultValue?: NonNullable<T>,
|
||||
): AnalyticsStorageItem<T> {
|
||||
defaultValue: T,
|
||||
): AnalyticsStorageItem<T>;
|
||||
function defineStorageItem(
|
||||
key: string,
|
||||
defaultValue?: unknown,
|
||||
): AnalyticsStorageItem<unknown> {
|
||||
return {
|
||||
getValue: async () =>
|
||||
(await browser.storage.local.get<Record<string, any>>(key))[key] ??
|
||||
(await browser.storage.local.get<Record<string, unknown>>(key))[key] ??
|
||||
defaultValue,
|
||||
setValue: (newValue) => browser.storage.local.set({ [key]: newValue }),
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { BaseAnalyticsEvent } from '../types';
|
||||
const DEFAULT_ENGAGEMENT_TIME_IN_MSEC = 100;
|
||||
|
||||
export interface GoogleAnalytics4ProviderOptions {
|
||||
apiUrl?: string;
|
||||
apiSecret: string;
|
||||
measurementId: string;
|
||||
}
|
||||
@@ -18,7 +19,7 @@ export const googleAnalytics4 =
|
||||
): Promise<void> => {
|
||||
const url = new URL(
|
||||
config?.debug ? '/debug/mp/collect' : '/mp/collect',
|
||||
'https://www.google-analytics.com',
|
||||
options.apiUrl ?? 'https://www.google-analytics.com',
|
||||
);
|
||||
if (options.apiSecret)
|
||||
url.searchParams.set('api_secret', options.apiSecret);
|
||||
|
||||
@@ -2,39 +2,45 @@ export interface Analytics {
|
||||
/** Report a page change. */
|
||||
page: (url: string) => void;
|
||||
/** Report a custom event. */
|
||||
track: (eventName: string, eventProperties?: Record<string, string>) => void;
|
||||
track: (
|
||||
eventName: string,
|
||||
eventProperties?: Record<string, string | undefined>,
|
||||
) => void;
|
||||
/** Save information about the user. */
|
||||
identify: (userId: string, userProperties?: Record<string, string>) => void;
|
||||
/** Automatically setup and track user interactions, returning a function to remove any listeners that were setup. */
|
||||
/**
|
||||
* Automatically setup and track user interactions, returning a function to
|
||||
* remove any listeners that were setup.
|
||||
*/
|
||||
autoTrack: (root: Document | ShadowRoot | Element) => () => void;
|
||||
/** Calls `config.enabled.setValue`. */
|
||||
setEnabled: (enabled: boolean) => void;
|
||||
}
|
||||
|
||||
export interface AnalyticsConfig {
|
||||
/**
|
||||
* Array of providers to send analytics to.
|
||||
*/
|
||||
/** Array of providers to send analytics to. */
|
||||
providers: AnalyticsProvider[];
|
||||
/**
|
||||
* Enable debug logs and other provider-specific debugging features.
|
||||
*/
|
||||
/** Enable debug logs and other provider-specific debugging features. */
|
||||
debug?: boolean;
|
||||
/**
|
||||
* Your extension's version, reported alongside events.
|
||||
*
|
||||
* @default browser.runtime.getManifest().version`.
|
||||
*/
|
||||
version?: string;
|
||||
/**
|
||||
* Configure how the enabled flag is persisted. Defaults to using `browser.storage.local`.
|
||||
* Configure how the enabled flag is persisted. Defaults to using
|
||||
* `browser.storage.local`.
|
||||
*/
|
||||
enabled?: AnalyticsStorageItem<boolean>;
|
||||
/**
|
||||
* Configure how the user Id is persisted. Defaults to using `browser.storage.local`.
|
||||
* Configure how the user Id is persisted. Defaults to using
|
||||
* `browser.storage.local`.
|
||||
*/
|
||||
userId?: AnalyticsStorageItem<string>;
|
||||
userId?: AnalyticsStorageItem<string | undefined>;
|
||||
/**
|
||||
* Configure how user properties are persisted. Defaults to using `browser.storage.local`.
|
||||
* Configure how user properties are persisted. Defaults to using
|
||||
* `browser.storage.local`.
|
||||
*/
|
||||
userProperties?: AnalyticsStorageItem<Record<string, string>>;
|
||||
}
|
||||
@@ -94,6 +100,6 @@ export interface AnalyticsPageViewEvent extends BaseAnalyticsEvent {
|
||||
export interface AnalyticsTrackEvent extends BaseAnalyticsEvent {
|
||||
event: {
|
||||
name: string;
|
||||
properties?: Record<string, string>;
|
||||
properties?: Record<string, string | undefined>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,40 @@
|
||||
{
|
||||
"name": "@wxt-dev/analytics",
|
||||
"version": "0.5.1",
|
||||
"version": "0.5.4",
|
||||
"description": "Add analytics to your web extension",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "buildc --deps-only -- wxt",
|
||||
"dev:build": "buildc --deps-only -- wxt build",
|
||||
"check": "bun run build && check",
|
||||
"build": "buildc -- tsdown",
|
||||
"prepack": "bun run build",
|
||||
"postinstall": "buildc --deps-only -- wxt prepare",
|
||||
"test": "echo 'noop'",
|
||||
"test:coverage": "echo 'noop'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wxt-dev/browser": "workspace:^",
|
||||
"@wxt-dev/is-background": "workspace:^",
|
||||
"ua-parser-js": "^1.0.40"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wxt-dev/wxt.git",
|
||||
"directory": "packages/analytics"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.mts",
|
||||
@@ -38,26 +64,5 @@
|
||||
"types": "./dist/index.d.mts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "buildc --deps-only -- wxt",
|
||||
"dev:build": "buildc --deps-only -- wxt build",
|
||||
"check": "pnpm build && check",
|
||||
"build": "buildc -- tsdown",
|
||||
"prepack": "pnpm -s build",
|
||||
"prepare": "buildc --deps-only -- wxt prepare"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"publint": "^0.3.17",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wxt-dev/browser": "workspace:^",
|
||||
"ua-parser-js": "^1.0.40"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ export default defineConfig({
|
||||
'./modules/analytics/providers/google-analytics-4.ts',
|
||||
'providers/umami': './modules/analytics/providers/umami.ts',
|
||||
},
|
||||
external: ['#analytics'],
|
||||
deps: {
|
||||
neverBundle: ['#analytics'],
|
||||
},
|
||||
define: {
|
||||
'process.env.NPM': 'true',
|
||||
},
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# Changelog
|
||||
|
||||
## v1.1.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.1.0...auto-icons-v1.1.1)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Auto icons override default icons ([#1616](https://github.com/wxt-dev/wxt/pull/1616))
|
||||
|
||||
### 💅 Refactors
|
||||
|
||||
- Standardize file existence checks to `pathExists` ([#2083](https://github.com/wxt-dev/wxt/pull/2083))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps:** Upgrade oxlint from 0.16.8 to 1.14.0 ([a01928e0](https://github.com/wxt-dev/wxt/commit/a01928e0))
|
||||
- **deps:** Upgrade typescript from 5.8.3 to 5.9.2 ([a6eef643](https://github.com/wxt-dev/wxt/commit/a6eef643))
|
||||
- Create script for managing dependency upgrades ([#1875](https://github.com/wxt-dev/wxt/pull/1875))
|
||||
- **deps:** Upgrade all dev dependencies ([#1876](https://github.com/wxt-dev/wxt/pull/1876))
|
||||
- **deps:** Upgrade non-breaking production dependencies ([#1877](https://github.com/wxt-dev/wxt/pull/1877))
|
||||
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
|
||||
- Use `tsdown` to build packages ([#2006](https://github.com/wxt-dev/wxt/pull/2006))
|
||||
- Move script-only dev dependencies to top-level `package.json` ([#2007](https://github.com/wxt-dev/wxt/pull/2007))
|
||||
- Update dependencies ([#2069](https://github.com/wxt-dev/wxt/pull/2069))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Omerfardemir <od080624@gmail.com>
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v1.1.0
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.0.2...auto-icons-v1.1.0)
|
||||
|
||||
@@ -16,7 +16,7 @@ Install the package:
|
||||
npm i --save-dev @wxt-dev/auto-icons
|
||||
pnpm i -D @wxt-dev/auto-icons
|
||||
yarn add --dev @wxt-dev/auto-icons
|
||||
bun i -D @wxt-dev/auto-icons
|
||||
bun add -D @wxt-dev/auto-icons
|
||||
```
|
||||
|
||||
Add the module to `wxt.config.ts`:
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"type": "module",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.mts",
|
||||
@@ -39,20 +39,26 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "pnpm build && check"
|
||||
"check": "bun run build && check",
|
||||
"test": "buildc --deps-only -- vitest",
|
||||
"test:coverage": "bun run test run --coverage",
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.19.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"defu": "^6.1.4",
|
||||
"fs-extra": "^11.3.3",
|
||||
"sharp": "^0.34.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"oxlint": "^1.43.0",
|
||||
"publint": "^0.3.17",
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,630 @@
|
||||
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
|
||||
import { resolve } from 'node:path';
|
||||
import * as fsPromises from 'node:fs/promises';
|
||||
import sharp from 'sharp';
|
||||
import type { Wxt, UserManifest } from 'wxt';
|
||||
|
||||
// Import the actual module
|
||||
import autoIconsModule from '../index';
|
||||
import type { AutoIconsOptions } from '../index';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('node:fs/promises', () => ({
|
||||
mkdir: vi.fn(),
|
||||
access: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('sharp', () => ({
|
||||
default: vi.fn(),
|
||||
}));
|
||||
|
||||
// Type definitions for better type safety
|
||||
interface MockWxt {
|
||||
config: {
|
||||
srcDir: string;
|
||||
outDir: string;
|
||||
mode: 'development' | 'production';
|
||||
};
|
||||
logger: {
|
||||
warn: Mock;
|
||||
};
|
||||
hooks: {
|
||||
hook: Mock;
|
||||
};
|
||||
}
|
||||
|
||||
interface PublicAsset {
|
||||
type: string;
|
||||
fileName: string;
|
||||
}
|
||||
|
||||
interface BuildOutput {
|
||||
publicAssets: PublicAsset[];
|
||||
}
|
||||
|
||||
describe('auto-icons module', () => {
|
||||
const mockWxt: MockWxt = {
|
||||
config: {
|
||||
srcDir: '/mock/src',
|
||||
outDir: '/mock/dist',
|
||||
mode: 'development',
|
||||
},
|
||||
logger: {
|
||||
warn: vi.fn(),
|
||||
},
|
||||
hooks: {
|
||||
hook: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
const createMockSharpInstance = () => {
|
||||
const instance = {
|
||||
png: vi.fn(),
|
||||
grayscale: vi.fn(),
|
||||
resize: vi.fn(),
|
||||
toFile: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
// Make methods chainable
|
||||
instance.png.mockReturnValue(instance);
|
||||
instance.grayscale.mockReturnValue(instance);
|
||||
instance.resize.mockImplementation(() => {
|
||||
// Create a new instance for each resize to simulate real sharp behavior
|
||||
const resizedInstance = { ...instance };
|
||||
resizedInstance.toFile = vi.fn().mockResolvedValue(undefined);
|
||||
return resizedInstance;
|
||||
});
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
let mockSharpInstance: ReturnType<typeof createMockSharpInstance>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockSharpInstance = createMockSharpInstance();
|
||||
vi.mocked(sharp).mockReturnValue(
|
||||
mockSharpInstance as unknown as sharp.Sharp,
|
||||
);
|
||||
vi.mocked(fsPromises.access).mockResolvedValue(undefined);
|
||||
vi.mocked(fsPromises.mkdir).mockResolvedValue(undefined as any);
|
||||
});
|
||||
|
||||
describe('module setup', () => {
|
||||
it('should have correct module metadata', () => {
|
||||
expect(autoIconsModule.name).toBe('@wxt-dev/auto-icons');
|
||||
expect(autoIconsModule.configKey).toBe('autoIcons');
|
||||
expect(typeof autoIconsModule.setup).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('options handling', () => {
|
||||
it('should use default options when not provided', async () => {
|
||||
const options: AutoIconsOptions = {};
|
||||
|
||||
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
|
||||
|
||||
// Verify that the module was set up (hooks were registered)
|
||||
expect(mockWxt.hooks.hook).toHaveBeenCalledWith(
|
||||
'build:manifestGenerated',
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(mockWxt.hooks.hook).toHaveBeenCalledWith(
|
||||
'build:done',
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(mockWxt.hooks.hook).toHaveBeenCalledWith(
|
||||
'prepare:publicPaths',
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('should merge custom options with defaults', async () => {
|
||||
const options: AutoIconsOptions = {
|
||||
sizes: [64, 32],
|
||||
grayscaleOnDevelopment: false,
|
||||
};
|
||||
|
||||
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
|
||||
|
||||
// Verify that the module was set up with custom options
|
||||
expect(mockWxt.hooks.hook).toHaveBeenCalledWith(
|
||||
'build:manifestGenerated',
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(mockWxt.hooks.hook).toHaveBeenCalledWith(
|
||||
'build:done',
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(mockWxt.hooks.hook).toHaveBeenCalledWith(
|
||||
'prepare:publicPaths',
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('should warn when disabled', async () => {
|
||||
const options: AutoIconsOptions = {
|
||||
enabled: false,
|
||||
};
|
||||
|
||||
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
|
||||
|
||||
expect(mockWxt.logger.warn).toHaveBeenCalledWith(
|
||||
'`[auto-icons]` @wxt-dev/auto-icons disabled',
|
||||
);
|
||||
expect(mockWxt.hooks.hook).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should warn when base icon not found', async () => {
|
||||
vi.mocked(fsPromises.access).mockRejectedValue(new Error('ENOENT'));
|
||||
|
||||
const options: AutoIconsOptions = {
|
||||
enabled: true,
|
||||
baseIconPath: 'assets/missing-icon.png',
|
||||
};
|
||||
|
||||
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
|
||||
|
||||
expect(mockWxt.logger.warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
'Skipping icon generation, no base icon found at',
|
||||
),
|
||||
);
|
||||
expect(mockWxt.hooks.hook).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('manifest generation hook', () => {
|
||||
it('should update manifest with default icons when no custom sizes provided', async () => {
|
||||
const options: AutoIconsOptions = {
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
|
||||
|
||||
const manifestHook = vi
|
||||
.mocked(mockWxt.hooks.hook)
|
||||
.mock.calls.find((call) => call[0] === 'build:manifestGenerated')?.[1];
|
||||
|
||||
expect(manifestHook).toBeDefined();
|
||||
|
||||
const manifest: UserManifest = {};
|
||||
if (manifestHook) {
|
||||
await manifestHook(mockWxt as unknown as Wxt, manifest);
|
||||
}
|
||||
|
||||
// Should use default sizes: [128, 48, 32, 16]
|
||||
expect(manifest.icons).toEqual({
|
||||
128: 'icons/128.png',
|
||||
48: 'icons/48.png',
|
||||
32: 'icons/32.png',
|
||||
16: 'icons/16.png',
|
||||
});
|
||||
});
|
||||
|
||||
it('should merge custom sizes with defaults', async () => {
|
||||
const options: AutoIconsOptions = {
|
||||
enabled: true,
|
||||
sizes: [96, 64], // These will be merged with defaults
|
||||
};
|
||||
|
||||
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
|
||||
|
||||
const manifestHook = vi
|
||||
.mocked(mockWxt.hooks.hook)
|
||||
.mock.calls.find((call) => call[0] === 'build:manifestGenerated')?.[1];
|
||||
|
||||
expect(manifestHook).toBeDefined();
|
||||
|
||||
const manifest: UserManifest = {};
|
||||
if (manifestHook) {
|
||||
await manifestHook(mockWxt as unknown as Wxt, manifest);
|
||||
}
|
||||
|
||||
// defu merges arrays, so we get both custom and default sizes
|
||||
expect(manifest.icons).toEqual({
|
||||
96: 'icons/96.png',
|
||||
64: 'icons/64.png',
|
||||
128: 'icons/128.png',
|
||||
48: 'icons/48.png',
|
||||
32: 'icons/32.png',
|
||||
16: 'icons/16.png',
|
||||
});
|
||||
});
|
||||
|
||||
it('should warn when overwriting existing icons in manifest', async () => {
|
||||
const options: AutoIconsOptions = {
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
|
||||
|
||||
const manifestHook = vi
|
||||
.mocked(mockWxt.hooks.hook)
|
||||
.mock.calls.find((call) => call[0] === 'build:manifestGenerated')?.[1];
|
||||
|
||||
const manifest: UserManifest = {
|
||||
icons: {
|
||||
128: 'existing-icon.png',
|
||||
},
|
||||
};
|
||||
|
||||
if (manifestHook) {
|
||||
await manifestHook(mockWxt as unknown as Wxt, manifest);
|
||||
}
|
||||
|
||||
expect(mockWxt.logger.warn).toHaveBeenCalledWith(
|
||||
'`[auto-icons]` icons property found in manifest, overwriting with auto-generated icons',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('icon generation hook', () => {
|
||||
it('should generate icons with default sizes', async () => {
|
||||
const options: AutoIconsOptions = {
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
const output: BuildOutput = {
|
||||
publicAssets: [],
|
||||
};
|
||||
|
||||
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
|
||||
|
||||
const buildHook = vi
|
||||
.mocked(mockWxt.hooks.hook)
|
||||
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
|
||||
|
||||
expect(buildHook).toBeDefined();
|
||||
if (buildHook) {
|
||||
await buildHook(mockWxt as unknown as Wxt, output);
|
||||
}
|
||||
|
||||
expect(sharp).toHaveBeenCalledWith(
|
||||
resolve('/mock/src', 'assets/icon.png'),
|
||||
);
|
||||
expect(mockSharpInstance.png).toHaveBeenCalled();
|
||||
|
||||
// Should resize to default sizes
|
||||
expect(mockSharpInstance.resize).toHaveBeenCalledWith(128);
|
||||
expect(mockSharpInstance.resize).toHaveBeenCalledWith(48);
|
||||
expect(mockSharpInstance.resize).toHaveBeenCalledWith(32);
|
||||
expect(mockSharpInstance.resize).toHaveBeenCalledWith(16);
|
||||
|
||||
expect(fsPromises.mkdir).toHaveBeenCalledWith(
|
||||
resolve('/mock/dist', 'icons'),
|
||||
{ recursive: true },
|
||||
);
|
||||
|
||||
expect(output.publicAssets).toEqual([
|
||||
{ type: 'asset', fileName: 'icons/128.png' },
|
||||
{ type: 'asset', fileName: 'icons/48.png' },
|
||||
{ type: 'asset', fileName: 'icons/32.png' },
|
||||
{ type: 'asset', fileName: 'icons/16.png' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should generate icons with custom sizes merged with defaults', async () => {
|
||||
const options: AutoIconsOptions = {
|
||||
enabled: true,
|
||||
sizes: [96, 64],
|
||||
};
|
||||
|
||||
const output: BuildOutput = {
|
||||
publicAssets: [],
|
||||
};
|
||||
|
||||
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
|
||||
|
||||
const buildHook = vi
|
||||
.mocked(mockWxt.hooks.hook)
|
||||
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
|
||||
|
||||
expect(buildHook).toBeDefined();
|
||||
if (buildHook) {
|
||||
await buildHook(mockWxt as unknown as Wxt, output);
|
||||
}
|
||||
|
||||
// Should include both custom and default sizes
|
||||
expect(mockSharpInstance.resize).toHaveBeenCalledWith(96);
|
||||
expect(mockSharpInstance.resize).toHaveBeenCalledWith(64);
|
||||
expect(mockSharpInstance.resize).toHaveBeenCalledWith(128);
|
||||
expect(mockSharpInstance.resize).toHaveBeenCalledWith(48);
|
||||
expect(mockSharpInstance.resize).toHaveBeenCalledWith(32);
|
||||
expect(mockSharpInstance.resize).toHaveBeenCalledWith(16);
|
||||
|
||||
expect(output.publicAssets).toEqual([
|
||||
{ type: 'asset', fileName: 'icons/96.png' },
|
||||
{ type: 'asset', fileName: 'icons/64.png' },
|
||||
{ type: 'asset', fileName: 'icons/128.png' },
|
||||
{ type: 'asset', fileName: 'icons/48.png' },
|
||||
{ type: 'asset', fileName: 'icons/32.png' },
|
||||
{ type: 'asset', fileName: 'icons/16.png' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should apply grayscale in development mode', async () => {
|
||||
const options: AutoIconsOptions = {
|
||||
enabled: true,
|
||||
grayscaleOnDevelopment: true,
|
||||
sizes: [128],
|
||||
};
|
||||
|
||||
const output: BuildOutput = { publicAssets: [] };
|
||||
|
||||
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
|
||||
|
||||
const buildHook = vi
|
||||
.mocked(mockWxt.hooks.hook)
|
||||
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
|
||||
|
||||
if (buildHook) {
|
||||
await buildHook(mockWxt as unknown as Wxt, output);
|
||||
}
|
||||
|
||||
expect(mockSharpInstance.grayscale).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not apply grayscale in production mode', async () => {
|
||||
const prodMockWxt = {
|
||||
...mockWxt,
|
||||
config: {
|
||||
...mockWxt.config,
|
||||
mode: 'production' as const,
|
||||
},
|
||||
};
|
||||
|
||||
const options: AutoIconsOptions = {
|
||||
enabled: true,
|
||||
grayscaleOnDevelopment: true,
|
||||
sizes: [128],
|
||||
};
|
||||
|
||||
const output: BuildOutput = { publicAssets: [] };
|
||||
|
||||
await autoIconsModule.setup!(prodMockWxt as unknown as Wxt, options);
|
||||
|
||||
const buildHook = vi
|
||||
.mocked(prodMockWxt.hooks.hook)
|
||||
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
|
||||
|
||||
if (buildHook) {
|
||||
await buildHook(prodMockWxt as unknown as Wxt, output);
|
||||
}
|
||||
|
||||
expect(mockSharpInstance.grayscale).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not apply grayscale when disabled', async () => {
|
||||
const options: AutoIconsOptions = {
|
||||
enabled: true,
|
||||
grayscaleOnDevelopment: false,
|
||||
sizes: [128],
|
||||
};
|
||||
|
||||
const output: BuildOutput = { publicAssets: [] };
|
||||
|
||||
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
|
||||
|
||||
const buildHook = vi
|
||||
.mocked(mockWxt.hooks.hook)
|
||||
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
|
||||
|
||||
if (buildHook) {
|
||||
await buildHook(mockWxt as unknown as Wxt, output);
|
||||
}
|
||||
|
||||
expect(mockSharpInstance.grayscale).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('public paths hook', () => {
|
||||
it('should add default icon paths to public paths', async () => {
|
||||
const options: AutoIconsOptions = {
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
|
||||
|
||||
const pathsHook = vi
|
||||
.mocked(mockWxt.hooks.hook)
|
||||
.mock.calls.find((call) => call[0] === 'prepare:publicPaths')?.[1];
|
||||
|
||||
expect(pathsHook).toBeDefined();
|
||||
|
||||
const paths: string[] = [];
|
||||
if (pathsHook) {
|
||||
pathsHook(mockWxt as unknown as Wxt, paths);
|
||||
}
|
||||
|
||||
expect(paths).toEqual([
|
||||
'icons/128.png',
|
||||
'icons/48.png',
|
||||
'icons/32.png',
|
||||
'icons/16.png',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases and error handling', () => {
|
||||
it('should handle empty sizes array', async () => {
|
||||
const options: AutoIconsOptions = {
|
||||
enabled: true,
|
||||
sizes: [], // Empty array should still merge with defaults
|
||||
};
|
||||
|
||||
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
|
||||
|
||||
const manifestHook = vi
|
||||
.mocked(mockWxt.hooks.hook)
|
||||
.mock.calls.find((call) => call[0] === 'build:manifestGenerated')?.[1];
|
||||
|
||||
const manifest: UserManifest = {};
|
||||
if (manifestHook) {
|
||||
await manifestHook(mockWxt as unknown as Wxt, manifest);
|
||||
}
|
||||
|
||||
// Should still have default sizes due to defu merge
|
||||
expect(manifest.icons).toEqual({
|
||||
128: 'icons/128.png',
|
||||
48: 'icons/48.png',
|
||||
32: 'icons/32.png',
|
||||
16: 'icons/16.png',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle sharp processing errors gracefully', async () => {
|
||||
const options: AutoIconsOptions = {
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
// Make toFile throw an error - need to properly chain resize -> png -> toFile
|
||||
const errorInstance = {
|
||||
toFile: vi.fn().mockRejectedValue(new Error('Sharp processing failed')),
|
||||
grayscale: vi.fn(),
|
||||
composite: vi.fn(),
|
||||
};
|
||||
errorInstance.grayscale.mockReturnValue(errorInstance);
|
||||
errorInstance.composite.mockReturnValue(errorInstance);
|
||||
|
||||
mockSharpInstance.resize = vi.fn().mockImplementation(() => ({
|
||||
png: vi.fn().mockReturnValue(errorInstance),
|
||||
}));
|
||||
|
||||
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
|
||||
|
||||
const buildHook = vi
|
||||
.mocked(mockWxt.hooks.hook)
|
||||
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
|
||||
|
||||
const output: BuildOutput = { publicAssets: [] };
|
||||
|
||||
// Should throw the sharp error
|
||||
if (buildHook) {
|
||||
await expect(
|
||||
buildHook(mockWxt as unknown as Wxt, output),
|
||||
).rejects.toThrow('Sharp processing failed');
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle file system errors during directory creation', async () => {
|
||||
const options: AutoIconsOptions = {
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
// Make ensureDir throw an error
|
||||
vi.mocked(fsPromises.mkdir).mockRejectedValue(
|
||||
new Error('Directory creation failed'),
|
||||
);
|
||||
|
||||
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
|
||||
|
||||
const buildHook = vi
|
||||
.mocked(mockWxt.hooks.hook)
|
||||
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
|
||||
|
||||
const output: BuildOutput = { publicAssets: [] };
|
||||
|
||||
// The module doesn't await ensureDir, so it won't throw
|
||||
if (buildHook) {
|
||||
await buildHook(mockWxt as unknown as Wxt, output);
|
||||
// But ensureDir should have been called
|
||||
expect(fsPromises.mkdir).toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle custom base icon path correctly', async () => {
|
||||
const customPath = 'custom/icon.png';
|
||||
const options: AutoIconsOptions = {
|
||||
enabled: true,
|
||||
baseIconPath: customPath,
|
||||
};
|
||||
|
||||
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
|
||||
|
||||
const buildHook = vi
|
||||
.mocked(mockWxt.hooks.hook)
|
||||
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
|
||||
|
||||
const output: BuildOutput = { publicAssets: [] };
|
||||
|
||||
if (buildHook) {
|
||||
await buildHook(mockWxt as unknown as Wxt, output);
|
||||
}
|
||||
|
||||
// Should resolve the path relative to srcDir
|
||||
expect(sharp).toHaveBeenCalledWith(resolve('/mock/src', customPath));
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration test', () => {
|
||||
it('should handle full workflow correctly', async () => {
|
||||
const options: AutoIconsOptions = {
|
||||
enabled: true,
|
||||
baseIconPath: 'assets/custom-icon.png',
|
||||
sizes: [96], // Will be merged with defaults
|
||||
grayscaleOnDevelopment: false,
|
||||
};
|
||||
|
||||
const manifest: UserManifest = {};
|
||||
const output: BuildOutput = { publicAssets: [] };
|
||||
const paths: string[] = [];
|
||||
|
||||
// Setup the module
|
||||
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
|
||||
|
||||
// Execute all hooks
|
||||
const manifestHook = vi
|
||||
.mocked(mockWxt.hooks.hook)
|
||||
.mock.calls.find((call) => call[0] === 'build:manifestGenerated')?.[1];
|
||||
const buildHook = vi
|
||||
.mocked(mockWxt.hooks.hook)
|
||||
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
|
||||
const pathsHook = vi
|
||||
.mocked(mockWxt.hooks.hook)
|
||||
.mock.calls.find((call) => call[0] === 'prepare:publicPaths')?.[1];
|
||||
|
||||
if (manifestHook) {
|
||||
await manifestHook(mockWxt as unknown as Wxt, manifest);
|
||||
}
|
||||
if (buildHook) {
|
||||
await buildHook(mockWxt as unknown as Wxt, output);
|
||||
}
|
||||
if (pathsHook) {
|
||||
pathsHook(mockWxt as unknown as Wxt, paths);
|
||||
}
|
||||
|
||||
// Verify results - defu merges arrays
|
||||
expect(manifest.icons).toEqual({
|
||||
96: 'icons/96.png',
|
||||
128: 'icons/128.png',
|
||||
48: 'icons/48.png',
|
||||
32: 'icons/32.png',
|
||||
16: 'icons/16.png',
|
||||
});
|
||||
|
||||
expect(output.publicAssets).toEqual([
|
||||
{ type: 'asset', fileName: 'icons/96.png' },
|
||||
{ type: 'asset', fileName: 'icons/128.png' },
|
||||
{ type: 'asset', fileName: 'icons/48.png' },
|
||||
{ type: 'asset', fileName: 'icons/32.png' },
|
||||
{ type: 'asset', fileName: 'icons/16.png' },
|
||||
]);
|
||||
|
||||
expect(paths).toEqual([
|
||||
'icons/96.png',
|
||||
'icons/128.png',
|
||||
'icons/48.png',
|
||||
'icons/32.png',
|
||||
'icons/16.png',
|
||||
]);
|
||||
|
||||
expect(sharp).toHaveBeenCalledWith(
|
||||
resolve('/mock/src', 'assets/custom-icon.png'),
|
||||
);
|
||||
expect(mockSharpInstance.grayscale).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,7 @@ import { defineWxtModule } from 'wxt/modules';
|
||||
import { resolve, relative } from 'node:path';
|
||||
import defu from 'defu';
|
||||
import sharp from 'sharp';
|
||||
import { ensureDir, pathExists } from 'fs-extra';
|
||||
import { access, mkdir } from 'node:fs/promises';
|
||||
|
||||
export default defineWxtModule<AutoIconsOptions>({
|
||||
name: '@wxt-dev/auto-icons',
|
||||
@@ -37,7 +37,11 @@ export default defineWxtModule<AutoIconsOptions>({
|
||||
if (!parsedOptions.enabled)
|
||||
return wxt.logger.warn(`\`[auto-icons]\` ${this.name} disabled`);
|
||||
|
||||
if (!(await pathExists(resolvedPath))) {
|
||||
const iconExists = await access(resolvedPath).then(
|
||||
() => true,
|
||||
() => false,
|
||||
);
|
||||
if (!iconExists) {
|
||||
return wxt.logger.warn(
|
||||
`\`[auto-icons]\` Skipping icon generation, no base icon found at ${relative(process.cwd(), resolvedPath)}`,
|
||||
);
|
||||
@@ -45,7 +49,7 @@ export default defineWxtModule<AutoIconsOptions>({
|
||||
|
||||
wxt.hooks.hook('build:manifestGenerated', async (wxt, manifest) => {
|
||||
if (manifest.icons)
|
||||
return wxt.logger.warn(
|
||||
wxt.logger.warn(
|
||||
'`[auto-icons]` icons property found in manifest, overwriting with auto-generated icons',
|
||||
);
|
||||
|
||||
@@ -91,7 +95,7 @@ export default defineWxtModule<AutoIconsOptions>({
|
||||
}
|
||||
}
|
||||
|
||||
ensureDir(resolve(outputFolder, 'icons'));
|
||||
mkdir(resolve(outputFolder, 'icons'), { recursive: true });
|
||||
await resizedImage.toFile(resolve(outputFolder, `icons/${size}.png`));
|
||||
|
||||
output.publicAssets.push({
|
||||
@@ -109,12 +113,11 @@ export default defineWxtModule<AutoIconsOptions>({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Options for the auto-icons module
|
||||
*/
|
||||
/** Options for the auto-icons module */
|
||||
export interface AutoIconsOptions {
|
||||
/**
|
||||
* Enable auto-icons generation
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
enabled?: boolean;
|
||||
@@ -122,27 +125,30 @@ export interface AutoIconsOptions {
|
||||
* Path to the image to use.
|
||||
*
|
||||
* Path is relative to the project's src directory.
|
||||
* @default "<srcDir>/assets/icon.png"
|
||||
*
|
||||
* @default '<srcDir>/assets/icon.png'
|
||||
*/
|
||||
baseIconPath?: string;
|
||||
/**
|
||||
* Apply a visual indicator to the icon when running in development mode.
|
||||
*
|
||||
* "grayscale" converts the icon to grayscale.
|
||||
* "overlay" covers the bottom half with a yellow rectangle and writes "DEV" in black text.
|
||||
* Set to `false` to disable any indicator.
|
||||
* "grayscale" converts the icon to grayscale. "overlay" covers the bottom
|
||||
* half with a yellow rectangle and writes "DEV" in black text. Set to `false`
|
||||
* to disable any indicator.
|
||||
*
|
||||
* @default "grayscale"
|
||||
* @default 'grayscale'
|
||||
*/
|
||||
developmentIndicator?: 'grayscale' | 'overlay' | false;
|
||||
/**
|
||||
* Grayscale the image when in development mode to indicate development
|
||||
* @default true
|
||||
*
|
||||
* @deprecated Use `developmentIndicator` instead
|
||||
* @default true
|
||||
*/
|
||||
grayscaleOnDevelopment?: boolean;
|
||||
/**
|
||||
* Sizes to generate icons for
|
||||
*
|
||||
* @default [128, 48, 32, 16]
|
||||
*/
|
||||
sizes?: number[];
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
mockReset: true,
|
||||
restoreMocks: true,
|
||||
},
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@wxt-dev/browser",
|
||||
"description": "Provides a cross-browser API for using extension APIs and types based on @types/chrome",
|
||||
"version": "0.1.36",
|
||||
"version": "0.1.40",
|
||||
"type": "module",
|
||||
"main": "src/index.mjs",
|
||||
"types": "src/index.d.ts",
|
||||
@@ -12,7 +12,9 @@
|
||||
},
|
||||
"scripts": {
|
||||
"check": "check",
|
||||
"gen": "tsx scripts/generate.ts"
|
||||
"gen": "bun run scripts/generate.ts",
|
||||
"test": "echo 'noop'",
|
||||
"test:coverage": "echo 'noop'"
|
||||
},
|
||||
"author": {
|
||||
"name": "Aaron Klinker",
|
||||
@@ -23,8 +25,8 @@
|
||||
"src"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/chrome": "0.1.36",
|
||||
"fs-extra": "^11.3.3",
|
||||
"@types/chrome": "0.1.40",
|
||||
"@types/node": "^20.0.0",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import spawn from 'nano-spawn';
|
||||
import fs from 'fs-extra';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join, resolve, sep } from 'node:path';
|
||||
import { sep as posixSep } from 'node:path/posix';
|
||||
import { readdir, mkdir } from 'node:fs/promises';
|
||||
import { styleText } from 'node:util';
|
||||
|
||||
// Fetch latest version
|
||||
|
||||
console.log('Getting latest version of \x1b[36m@types/chrome\x1b[0m');
|
||||
await spawn('pnpm', ['i', '--ignore-scripts', '-D', '@types/chrome@latest']);
|
||||
console.log(`Getting latest version of ${styleText('cyan', '@types/chrome')}`);
|
||||
await Bun.$`bun install --ignore-scripts -D @types/chrome@latest`;
|
||||
|
||||
// Generate new package.json
|
||||
|
||||
console.log('Generating new \x1b[36mpackage.json\x1b[0m');
|
||||
console.log(`Generating new ${styleText('cyan', 'package.json')}`);
|
||||
|
||||
const pkgJsonPath = fileURLToPath(
|
||||
import.meta.resolve('@types/chrome/package.json'),
|
||||
);
|
||||
const pkgDir = dirname(pkgJsonPath);
|
||||
const pkgJson = await fs.readJson(pkgJsonPath);
|
||||
const pkgJsonTemplate = await fs.readFile('templates/package.json', 'utf8');
|
||||
const pkgJson = await Bun.file(pkgJsonPath).json();
|
||||
const pkgJsonTemplate = await Bun.file('templates/package.json').text();
|
||||
const newPkgJson = JSON.parse(
|
||||
pkgJsonTemplate.replaceAll('{{chromeTypesVersion}}', pkgJson.version),
|
||||
);
|
||||
@@ -27,15 +27,15 @@ newPkgJson.peerDependencies = pkgJson.peerDependencies;
|
||||
newPkgJson.peerDependenciesMeta = pkgJson.peerDependenciesMeta;
|
||||
|
||||
const outPkgJsonPath = resolve('package.json');
|
||||
await fs.writeJson(outPkgJsonPath, newPkgJson);
|
||||
await spawn('pnpm', ['-w', 'prettier', '--write', outPkgJsonPath]);
|
||||
await Bun.write(outPkgJsonPath, JSON.stringify(newPkgJson));
|
||||
await Bun.$`bun run --cwd ../.. prettier --write "${outPkgJsonPath}"`;
|
||||
|
||||
// Generate declaration files
|
||||
|
||||
console.log('Generating declaration files');
|
||||
const outDir = resolve('src/gen');
|
||||
const declarationFileMapping = (
|
||||
await fs.readdir(pkgDir, {
|
||||
await readdir(pkgDir, {
|
||||
recursive: true,
|
||||
encoding: 'utf8',
|
||||
})
|
||||
@@ -50,17 +50,19 @@ const declarationFileMapping = (
|
||||
}));
|
||||
|
||||
for (const { file, srcPath, destPath } of declarationFileMapping) {
|
||||
const content = await fs.readFile(srcPath, 'utf8');
|
||||
const content = await Bun.file(srcPath).text();
|
||||
const transformedContent = transformFile(file, content);
|
||||
const destDir = dirname(destPath);
|
||||
await fs.mkdir(destDir, { recursive: true });
|
||||
await fs.writeFile(destPath, transformedContent);
|
||||
console.log(` \x1b[2m-\x1b[0m \x1b[36m${file}\x1b[0m`);
|
||||
await mkdir(destDir, { recursive: true });
|
||||
await Bun.write(destPath, transformedContent);
|
||||
console.log(` ${styleText('dim', '-')} ${styleText('cyan', file)}`);
|
||||
}
|
||||
|
||||
// Done!
|
||||
|
||||
console.log('\x1b[32m✔\x1b[0m Done in ' + performance.now().toFixed(0) + ' ms');
|
||||
console.log(
|
||||
`${styleText('green', '✔')} Done in ${performance.now().toFixed(0)} ms`,
|
||||
);
|
||||
|
||||
// Transformations
|
||||
|
||||
|
||||
@@ -5,19 +5,19 @@ import { browser, type Browser } from '../index';
|
||||
describe('browser', () => {
|
||||
describe('types', () => {
|
||||
it('should provide types via the Browser import', () => {
|
||||
expectTypeOf<Browser.runtime.MessageSender>().toMatchTypeOf<chrome.runtime.MessageSender>();
|
||||
expectTypeOf<Browser.storage.AreaName>().toMatchTypeOf<chrome.storage.AreaName>();
|
||||
expectTypeOf<Browser.i18n.LanguageDetectionResult>().toMatchTypeOf<chrome.i18n.LanguageDetectionResult>();
|
||||
expectTypeOf<Browser.runtime.MessageSender>().toEqualTypeOf<chrome.runtime.MessageSender>();
|
||||
expectTypeOf<Browser.storage.AreaName>().toEqualTypeOf<chrome.storage.AreaName>();
|
||||
expectTypeOf<Browser.i18n.LanguageDetectionResult>().toEqualTypeOf<chrome.i18n.LanguageDetectionResult>();
|
||||
});
|
||||
|
||||
it('should provide values via the browser import', () => {
|
||||
expectTypeOf(browser.runtime.id).toMatchTypeOf<string>();
|
||||
expectTypeOf(browser.runtime.id).toEqualTypeOf<string>();
|
||||
expectTypeOf(
|
||||
browser.storage.local,
|
||||
).toMatchTypeOf<Browser.storage.StorageArea>();
|
||||
).toEqualTypeOf<Browser.storage.LocalStorageArea>();
|
||||
expectTypeOf(
|
||||
browser.i18n.detectLanguage('Hello, world!'),
|
||||
).resolves.toMatchTypeOf<chrome.i18n.LanguageDetectionResult>();
|
||||
browser.i18n.detectLanguage,
|
||||
).returns.resolves.toEqualTypeOf<chrome.i18n.LanguageDetectionResult>();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+199
-96
@@ -348,9 +348,10 @@ export namespace Browser {
|
||||
export function setIcon(details: TabIconDetails, callback: () => void): void;
|
||||
|
||||
/**
|
||||
* Sets the html document to be opened as a popup when the user clicks on the action's icon.
|
||||
* Sets the HTML document to be opened as a popup when the user clicks on the action's icon.
|
||||
*
|
||||
* Can return its result via Promise.
|
||||
* @since Chrome 96
|
||||
*/
|
||||
export function setPopup(details: PopupDetails): Promise<void>;
|
||||
export function setPopup(details: PopupDetails, callback: () => void): void;
|
||||
@@ -694,9 +695,16 @@ export namespace Browser {
|
||||
|
||||
/** @deprecated Bookmark write operations are no longer limited by Chrome. */
|
||||
export const MAX_WRITE_OPERATIONS_PER_HOUR: 1000000;
|
||||
|
||||
/** @deprecated Bookmark write operations are no longer limited by Chrome. */
|
||||
export const MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE: 1000000;
|
||||
|
||||
/**
|
||||
* The `id` associated with the root level node.
|
||||
* @since Chrome 145
|
||||
*/
|
||||
export const ROOT_NODE_ID = "0";
|
||||
|
||||
/**
|
||||
* Creates a bookmark or folder under the specified parentId. If url is NULL or missing, it will be a folder.
|
||||
*
|
||||
@@ -1160,6 +1168,7 @@ export namespace Browser {
|
||||
* Clears websites' cache storage data.
|
||||
*
|
||||
* Can return its result via Promise in Manifest V3 or later since Chrome 96.
|
||||
* @since Chrome 72
|
||||
*/
|
||||
export function removeCacheStorage(options: RemovalOptions): Promise<void>;
|
||||
export function removeCacheStorage(options: RemovalOptions, callback: () => void): void;
|
||||
@@ -2428,7 +2437,17 @@ export namespace Browser {
|
||||
* Exactly one of `imageData` or `path` must be specified. Both are dictionaries mapping a number of pixels to an image representation. The image representation in `imageData` is an `ImageData` object; for example, from a `canvas` element, while the image representation in `path` is the path to an image file relative to the extension's manifest. If `scale` screen pixels fit into a device-independent pixel, the `scale * n` icon is used. If that scale is missing, another image is resized to the required size.
|
||||
*/
|
||||
export class SetIcon {
|
||||
constructor(options?: { imageData?: ImageData | { [size: string]: ImageData } | undefined });
|
||||
constructor(
|
||||
options:
|
||||
| {
|
||||
imageData: ImageData | { [index: number]: ImageData };
|
||||
path?: string | { [index: number]: string } | undefined;
|
||||
}
|
||||
| {
|
||||
imageData?: ImageData | { [index: number]: ImageData } | undefined;
|
||||
path: string | { [index: number]: string };
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** Provides the Declarative Event API consisting of {@link events.Event.addRules addRules}, {@link events.Event.removeRules removeRules}, and {@link events.Event.getRules getRules}. */
|
||||
@@ -3325,7 +3344,7 @@ export namespace Browser {
|
||||
/** Indicates the data type of the option. The requested data type must match the real data type of the underlying option. */
|
||||
type: `${OptionType}`;
|
||||
/** Indicates the value to set. Leave unset to request automatic setting for options that have `autoSettable` enabled. The data type supplied for `value` must match `type`. */
|
||||
value?: string | number | boolean | number;
|
||||
value?: string | number | boolean | number[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3555,6 +3574,8 @@ export namespace Browser {
|
||||
/**
|
||||
* Performs a document scan and returns a Promise that resolves with a {@link ScanResults} object. If a callback is passed to this function, the returned data is passed to it instead.
|
||||
* @param options An object containing scan parameters.
|
||||
*
|
||||
* Can return its result via Promise in Manifest V3 or later since Chrome 96.
|
||||
*/
|
||||
export function scan(options: ScanOptions): Promise<ScanResults>;
|
||||
export function scan(options: ScanOptions, callback: (result: ScanResults) => void): void;
|
||||
@@ -4069,14 +4090,17 @@ export namespace Browser {
|
||||
id: string;
|
||||
/**
|
||||
* Implements the WebCrypto's SubtleCrypto interface. The cryptographic operations, including key generation, are hardware-backed.
|
||||
* Only non-extractable keys can be generated. The supported key types are RSASSA-PKCS1-V1_5 and RSA-OAEP (on Chrome versions 134+) with `modulusLength` up to 2048 and ECDSA with `namedCurve` P-256. Each RSASSA-PKCS1-V1_5 and ECDSA key can be used for signing data at most once, unless the extension is allowlisted through the KeyPermissions policy, in which case the key can be used indefinitely. RSA-OAEP keys are supported since Chrome version 134 and can be used by extensions allowlisted through that same policy to unwrap other keys.
|
||||
*
|
||||
* Only non-extractable keys can be generated. The supported key types are RSASSA-PKCS1-V1_5 with `modulusLength` up to 2048 and ECDSA with `namedCurve` P-256. Each key can be used for signing data at most once, unless the extension is allowlisted by the KeyPermissions policy, in which case the key can be used indefinitely.
|
||||
*
|
||||
* Keys generated on a specific `Token` cannot be used with any other Tokens, nor can they be used with `window.crypto.subtle`. Equally, `Key` objects created with `window.crypto.subtle` cannot be used with this interface.
|
||||
*/
|
||||
subtleCrypto: SubtleCrypto;
|
||||
/**
|
||||
* Implements the WebCrypto's SubtleCrypto interface. The cryptographic operations, including key generation, are software-backed.
|
||||
* Protection of the keys, and thus implementation of the non-extractable property, is done in software, so the keys are less protected than hardware-backed keys.
|
||||
* Only non-extractable keys can be generated. The supported key types are RSASSA-PKCS1-V1_5 and RSA-OAEP (on Chrome versions 134+) with `modulusLength` up to 2048. Each RSASSA-PKCS1-V1_5 key can be used for signing data at most once, unless the extension is allowlisted through the KeyPermissions policy, in which case the key can be used indefinitely. RSA-OAEP keys are supported since Chrome version 134 and can be used by extensions allowlisted through that same policy to unwrap other keys.
|
||||
* Implements the WebCrypto's SubtleCrypto interface. The cryptographic operations, including key generation, are software-backed. Protection of the keys, and thus implementation of the non-extractable property, is done in software, so the keys are less protected than hardware-backed keys.
|
||||
*
|
||||
* Only non-extractable keys can be generated. The only supported key type is RSASSA-PKCS1-V1_5 with `modulusLength` up to 2048. up to 2048. Each key can be used for signing data at most once, unless the extension is allowlisted through the KeyPermissions policy, in which case the key can be used indefinitely.
|
||||
*
|
||||
* Keys generated on a specific `Token` cannot be used with any other Tokens, nor can they be used with `window.crypto.subtle`. Equally, `Key` objects created with `window.crypto.subtle` cannot be used with this interface.
|
||||
* @since Chrome 97
|
||||
*/
|
||||
@@ -4356,9 +4380,12 @@ export namespace Browser {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the network details of the device's default network. If the user is not affiliated or the device is not connected to a network, runtime.lastError will be set with a failure reason.
|
||||
* Retrieves the network details of the device's default network. If the user is not affiliated or the device is not connected to a network, {@link runtime.lastError} will be set with a failure reason.
|
||||
* @param callback Called with the device's default network's NetworkDetails.
|
||||
*
|
||||
* Can return its result via Promise in Manifest V3 or later since Chrome 96.
|
||||
*/
|
||||
export function getNetworkDetails(): Promise<NetworkDetails>;
|
||||
export function getNetworkDetails(callback: (networkDetails: NetworkDetails) => void): void;
|
||||
}
|
||||
|
||||
@@ -6300,7 +6327,7 @@ export namespace Browser {
|
||||
* This API is different from identity.getAccounts in two ways. The information returned is available offline, and it only applies to the primary account for the profile.
|
||||
* @param details Profile options.
|
||||
*
|
||||
* Can return its result via Promise since Chrome 105.
|
||||
* Can return its result via Promise since Chrome 106.
|
||||
*/
|
||||
export function getProfileUserInfo(details?: ProfileDetails): Promise<ProfileUserInfo>;
|
||||
export function getProfileUserInfo(
|
||||
@@ -6315,7 +6342,7 @@ export namespace Browser {
|
||||
* If an access token is discovered to be invalid, it should be passed to removeCachedAuthToken to remove it from the cache. The app may then retrieve a fresh token with `getAuthToken`.
|
||||
* @param details Token information.
|
||||
*
|
||||
* Can return its result via Promise since Chrome 105.
|
||||
* Can return its result via Promise since Chrome 106.
|
||||
*/
|
||||
export function removeCachedAuthToken(details: InvalidTokenDetails): Promise<void>;
|
||||
export function removeCachedAuthToken(details: InvalidTokenDetails, callback: () => void): void;
|
||||
@@ -7903,7 +7930,7 @@ export namespace Browser {
|
||||
|
||||
export enum ClientCertificateType {
|
||||
ECDSA_SIGN = "ecdsaSign",
|
||||
RAS_SIGN = "rasSign",
|
||||
RSA_SIGN = "rsaSign",
|
||||
}
|
||||
|
||||
export interface SelectDetails {
|
||||
@@ -8798,7 +8825,7 @@ export namespace Browser {
|
||||
export type QueryInfo =
|
||||
& {
|
||||
/** String to query with the default search provider. */
|
||||
text?: string | undefined;
|
||||
text: string;
|
||||
}
|
||||
& (
|
||||
| {
|
||||
@@ -9239,36 +9266,37 @@ export namespace Browser {
|
||||
|
||||
export interface ManifestBase {
|
||||
// Required
|
||||
/** An integer specifying the version of the manifest file format that your extension uses. */
|
||||
manifest_version: number;
|
||||
/** A string that identifies the extension in the Chrome Web Store, the install dialog, and the user's Chrome Extensions page (`chrome://extensions`). The maximum length is 75 characters. */
|
||||
name: string;
|
||||
/** A string that identifies the extension's version number. */
|
||||
version: string;
|
||||
|
||||
// Recommended
|
||||
/** A string that defines the default language of an extension that supports multiple locales. Examples include "en" and "pt_BR". This key is required in localized extensions, and must not be used in extensions that aren't localized. */
|
||||
default_locale?: string | undefined;
|
||||
/** A string that describes the extension on both the Chrome Web Store and the user's extension management page. The maximum length is 132 characters. */
|
||||
description?: string | undefined;
|
||||
/** One or more icons that represent your extension. */
|
||||
icons?: ManifestIcons | undefined;
|
||||
|
||||
// Optional
|
||||
author?: {
|
||||
email: string;
|
||||
} | undefined;
|
||||
background_page?: string | undefined;
|
||||
/** @deprecated As of February 2024, the `author` key is no longer supported by Chrome or the Chrome Web Store. If present, it's silently ignored. */
|
||||
author?: { email: string } | undefined;
|
||||
/** Defines overrides for selected Chrome settings. */
|
||||
chrome_settings_overrides?: {
|
||||
homepage?: string | undefined;
|
||||
search_provider?: SearchProvider | undefined;
|
||||
startup_pages?: string[] | undefined;
|
||||
} | undefined;
|
||||
chrome_ui_overrides?: {
|
||||
bookmarks_ui?: {
|
||||
remove_bookmark_shortcut?: boolean | undefined;
|
||||
remove_button?: boolean | undefined;
|
||||
} | undefined;
|
||||
} | undefined;
|
||||
/** Defines overrides for default Chrome pages. */
|
||||
chrome_url_overrides?: {
|
||||
bookmarks?: string | undefined;
|
||||
history?: string | undefined;
|
||||
newtab?: string | undefined;
|
||||
} | undefined;
|
||||
/** Defines keyboard shortcuts within the extension. */
|
||||
commands?: {
|
||||
[name: string]: {
|
||||
suggested_key?: {
|
||||
@@ -9286,38 +9314,30 @@ export namespace Browser {
|
||||
matches?: string[] | undefined;
|
||||
permissions?: string[] | undefined;
|
||||
} | undefined;
|
||||
content_scripts?:
|
||||
| Array<{
|
||||
matches?: string[] | undefined;
|
||||
exclude_matches?: string[] | undefined;
|
||||
css?: string[] | undefined;
|
||||
js?: string[] | undefined;
|
||||
run_at?: string | undefined;
|
||||
all_frames?: boolean | undefined;
|
||||
match_about_blank?: boolean | undefined;
|
||||
include_globs?: string[] | undefined;
|
||||
exclude_globs?: string[] | undefined;
|
||||
}>
|
||||
| undefined;
|
||||
converted_from_user_script?: boolean | undefined;
|
||||
/** Specifies a value for the Cross-Origin-Embedder-Policy HTTP header, which configures embedding of cross-origin resources in an extension page. */
|
||||
cross_origin_embedder_policy?: { value: string } | undefined;
|
||||
/** Specifies a value for the Cross-Origin-Opener-Policy HTTP header, which lets you ensure that a top-level extension page doesn't share a browsing context group with cross-origin documents. */
|
||||
cross_origin_opener_policy?: { value: string } | undefined;
|
||||
current_locale?: string | undefined;
|
||||
/** Defines static rules for the declarativeNetRequest API, which allows blocking and modifying of network requests. */
|
||||
declarative_net_request?: { rule_resources?: declarativeNetRequest.Ruleset[] } | undefined;
|
||||
/** Defines pages that use the DevTools APIs. */
|
||||
devtools_page?: string | undefined;
|
||||
event_rules?:
|
||||
| Array<{
|
||||
event?: string | undefined;
|
||||
actions?:
|
||||
| Array<{
|
||||
type: string;
|
||||
}>
|
||||
| undefined;
|
||||
actions?: Array<{ type: string }> | undefined;
|
||||
conditions?: Browser.declarativeContent.PageStateMatcherProperties[] | undefined;
|
||||
}>
|
||||
| undefined;
|
||||
/** Specifies what other pages and extensions can connect to your extensions. */
|
||||
externally_connectable?: {
|
||||
ids?: string[] | undefined;
|
||||
matches?: string[] | undefined;
|
||||
accepts_tls_channel_id?: boolean | undefined;
|
||||
} | undefined;
|
||||
/** Provides access to the fileBrowserHandler API, which lets extensions access the ChromeOS file browser. */
|
||||
file_browser_handlers?:
|
||||
| Array<{
|
||||
id?: string | undefined;
|
||||
@@ -9325,35 +9345,44 @@ export namespace Browser {
|
||||
file_filters?: string[] | undefined;
|
||||
}>
|
||||
| undefined;
|
||||
/** Allows access to the fileSystemProvider API, which lets extensions create file systems that ChromeOS can use. */
|
||||
file_system_provider_capabilities?: {
|
||||
/** Whether configuring via onConfigureRequested is supported. By default: false. */
|
||||
configurable?: boolean | undefined;
|
||||
/** Whether setting watchers and notifying about changes is supported. By default: false. */
|
||||
watchable?: boolean | undefined;
|
||||
/** Whether multiple (more than one) mounted file systems are supported. By default: false. */
|
||||
multiple_mounts?: boolean | undefined;
|
||||
source?: string | undefined;
|
||||
/** Files app uses above information in order to render related UI elements appropriately. For example, if `configurable` is set to true, then a menu item for configuring volumes will be rendered. Similarly, if `multiple_mounts` is set to true, then Files app will allow to add more than one mount points from the UI. If `watchable` is false, then a refresh button will be rendered. Note, that if possible you should add support for watchers, so changes on the file system can be reflected immediately and automatically. */
|
||||
source: "file" | "device" | "network";
|
||||
} | undefined;
|
||||
/** string specifying a URL for the extension's homepage. If this is undefined, the homepage defaults to the extension's Chrome Web Store page. This field is particularly useful if you host the extension on your own site. */
|
||||
homepage_url?: string | undefined;
|
||||
/** Allows resources to be imported into the extension. */
|
||||
import?:
|
||||
| Array<{
|
||||
id: string;
|
||||
minimum_version?: string | undefined;
|
||||
}>
|
||||
| undefined;
|
||||
export?: {
|
||||
whitelist?: string[] | undefined;
|
||||
} | undefined;
|
||||
incognito?: string | undefined;
|
||||
/** Allows resources to be exported from the extension. */
|
||||
export?: { allowlist?: string[] | undefined } | undefined;
|
||||
/** Defines how the extension behaves in incognito mode. */
|
||||
incognito?: "spanning" | "split" | "not_allowed" | undefined;
|
||||
/** Allows the use of the Input Method Editor API. */
|
||||
input_components?:
|
||||
| Array<{
|
||||
name?: string | undefined;
|
||||
type?: string | undefined;
|
||||
name: string;
|
||||
id?: string | undefined;
|
||||
description?: string | undefined;
|
||||
language?: string[] | string | undefined;
|
||||
layouts?: string[] | undefined;
|
||||
indicator?: string | undefined;
|
||||
layouts?: string[] | string | undefined;
|
||||
input_view?: string | undefined;
|
||||
options_page?: string | undefined;
|
||||
}>
|
||||
| undefined;
|
||||
/** Specifies your extension's ID for various development use cases. */
|
||||
key?: string | undefined;
|
||||
/** Defines the oldest Chrome version that can install your extension. The value must be a substring of an existing Chrome browser version string, such as "107" or "107.0.5304.87". Users with versions of Chrome older than the minimum version see a "Not compatible" warning in the Chrome Web Store, and are unable to install your extension. If you add this to an existing extension, users whose Chrome version is older won't receive automatic updates to your extension. This includes business users in ephemeral mode. */
|
||||
minimum_chrome_version?: string | undefined;
|
||||
nacl_modules?:
|
||||
| Array<{
|
||||
@@ -9361,53 +9390,39 @@ export namespace Browser {
|
||||
mime_type: string;
|
||||
}>
|
||||
| undefined;
|
||||
/** Allows the use of an OAuth 2.0 security ID. The value of this key must be an object with "client_id" and "scopes" properties. */
|
||||
oauth2?: {
|
||||
client_id: string;
|
||||
scopes?: string[] | undefined;
|
||||
} | undefined;
|
||||
offline_enabled?: boolean | undefined;
|
||||
omnibox?: {
|
||||
keyword: string;
|
||||
} | undefined;
|
||||
/** Allows the extension to register a keyword in Chrome's address bar. */
|
||||
omnibox?: { keyword: string } | undefined;
|
||||
/** Specifies a path to an options.html file for the extension to use as an options page. */
|
||||
options_page?: string | undefined;
|
||||
/** Specifies a path to an HTML file that lets a user change extension options from the Chrome Extensions page. */
|
||||
options_ui?: {
|
||||
page?: string | undefined;
|
||||
chrome_style?: boolean | undefined;
|
||||
open_in_tab?: boolean | undefined;
|
||||
/** Path to the options page, relative to the extension's root. */
|
||||
page: string;
|
||||
/** Specify as `false` to declare an embedded options page. If `true`, the extension's options page will be opened in a new tab rather than embedded in `chrome://extensions`. */
|
||||
open_in_tab: boolean;
|
||||
} | undefined;
|
||||
platforms?:
|
||||
| Array<{
|
||||
nacl_arch?: string | undefined;
|
||||
sub_package_path: string;
|
||||
}>
|
||||
| undefined;
|
||||
plugins?:
|
||||
| Array<{
|
||||
path: string;
|
||||
}>
|
||||
| undefined;
|
||||
/** Lists technologies required to use the extension. */
|
||||
requirements?: {
|
||||
"3D"?: {
|
||||
features?: string[] | undefined;
|
||||
} | undefined;
|
||||
plugins?: {
|
||||
npapi?: boolean | undefined;
|
||||
} | undefined;
|
||||
"3D"?: { features?: string[] | undefined } | undefined;
|
||||
/** @deprecated NPAPI Plugin support for extensions has been discontinued as of Chrome version 45 */
|
||||
plugins?: { npapi?: boolean | undefined } | undefined;
|
||||
} | undefined;
|
||||
/** Defines a set of extension pages that don't have access to extension APIs or direct access to non-sandboxed pages. */
|
||||
sandbox?: {
|
||||
pages: string[];
|
||||
content_security_policy?: string | undefined;
|
||||
} | undefined;
|
||||
/** A string containing a shortened version of the extension's name to be used when character space is limited. The maximum length is 12 characters. If this is undefined, a truncated version of the "name" key displays instead. */
|
||||
short_name?: string | undefined;
|
||||
spellcheck?: {
|
||||
dictionary_language?: string | undefined;
|
||||
dictionary_locale?: string | undefined;
|
||||
dictionary_format?: string | undefined;
|
||||
dictionary_path?: string | undefined;
|
||||
} | undefined;
|
||||
storage?: {
|
||||
managed_schema: string;
|
||||
} | undefined;
|
||||
/** Declares a JSON schema for the managed storage area. */
|
||||
storage?: { managed_schema: string } | undefined;
|
||||
/** Registers the extension as a text to speech engine. */
|
||||
tts_engine?: {
|
||||
voices: Array<{
|
||||
voice_name: string;
|
||||
@@ -9416,13 +9431,14 @@ export namespace Browser {
|
||||
event_types?: string[] | undefined;
|
||||
}>;
|
||||
} | undefined;
|
||||
/** A string containing the URL of the extension's updates page. Use this key if you're hosting your extension outside the Chrome Web Store. */
|
||||
update_url?: string | undefined;
|
||||
/** A string describing the extension's version. Examples include "1.0 beta" and "build rc2". If this is unspecified, the "version" value displays on the extension management page instead. */
|
||||
version_name?: string | undefined;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface ManifestV2 extends ManifestBase {
|
||||
// Required
|
||||
manifest_version: 2;
|
||||
|
||||
// Pick one (or none)
|
||||
@@ -9437,24 +9453,50 @@ export namespace Browser {
|
||||
persistent?: boolean | undefined;
|
||||
}
|
||||
| undefined;
|
||||
/** Specifies JavaScript or CSS files to be used when the user opens certain web pages. */
|
||||
content_scripts?:
|
||||
| Array<{
|
||||
matches?: string[] | undefined;
|
||||
exclude_matches?: string[] | undefined;
|
||||
css?: string[] | undefined;
|
||||
js?: string[] | undefined;
|
||||
run_at?: string | undefined;
|
||||
all_frames?: boolean | undefined;
|
||||
match_about_blank?: boolean | undefined;
|
||||
include_globs?: string[] | undefined;
|
||||
exclude_globs?: string[] | undefined;
|
||||
}>
|
||||
| undefined;
|
||||
/** Defines restrictions on the scripts, styles, and other resources an extension can use. */
|
||||
content_security_policy?: string | undefined;
|
||||
/** Declares optional permissions for your extension. */
|
||||
optional_permissions?: (ManifestOptionalPermission | string)[] | undefined;
|
||||
/** Enables use of particular extension APIs. */
|
||||
permissions?: (ManifestPermission | string)[] | undefined;
|
||||
platforms?:
|
||||
| Array<{
|
||||
nacl_arch?: string | undefined;
|
||||
sub_package_path: string;
|
||||
}>
|
||||
| undefined;
|
||||
/** Defines files within the extension that can be accessed by web pages or other extensions. */
|
||||
web_accessible_resources?: string[] | undefined;
|
||||
}
|
||||
|
||||
export interface ManifestV3 extends ManifestBase {
|
||||
// Required
|
||||
manifest_version: 3;
|
||||
|
||||
// Optional
|
||||
/** Defines the appearance and behavior of the extension's icon in the Google Toolbar. */
|
||||
action?: ManifestAction | undefined;
|
||||
/** Specifies the JavaScript file containing the extension's service worker, which acts as an event handler. */
|
||||
background?:
|
||||
| {
|
||||
service_worker: string;
|
||||
type?: "module"; // If the service worker uses ES modules
|
||||
type?: "module";
|
||||
}
|
||||
| undefined;
|
||||
/** Specifies JavaScript or CSS files to be used when the user opens certain web pages. */
|
||||
content_scripts?:
|
||||
| Array<{
|
||||
matches?: string[] | undefined;
|
||||
@@ -9469,14 +9511,35 @@ export namespace Browser {
|
||||
world?: "ISOLATED" | "MAIN" | undefined;
|
||||
}>
|
||||
| undefined;
|
||||
/** Defines restrictions on the scripts, styles, and other resources an extension can use. */
|
||||
content_security_policy?: {
|
||||
extension_pages?: string;
|
||||
sandbox?: string;
|
||||
};
|
||||
} | undefined;
|
||||
/** Specifies file types for ChromeOS extensions to handle. */
|
||||
file_handlers?:
|
||||
| Array<{
|
||||
/** Specifies an HTML file to show when a file is opened. The file must be within your extension. Processing the file, whether it's displayed or used in some other way, is done with JavaScript using appropriate web platform APIs. This code must be in a separate JavaScript file included via a `<script>` tag. */
|
||||
action: string;
|
||||
/** A user friendly description of the action. */
|
||||
name: string;
|
||||
/** The file types that can be processed by the page specified in "action". The items in the dictionary are a key/value pair where the key is a MIME type and the value is an array of file extensions. Only known MIME types are allowed for the key. Custom file types are allowed but the key for a custom type must be a known MIME type, and the mapping between the MIME type and the custom file type must be supported by the underlying operating system. */
|
||||
accept: { [mime_type: string]: string[] };
|
||||
/** Specifies whether multiple files should be opened in a single client or multiple clients. The default value is "single-client". */
|
||||
launch_type?: "multiple-clients" | "single-client" | undefined;
|
||||
}>
|
||||
| undefined;
|
||||
/** Lists the web pages your extension is allowed to interact with, defined using URL match patterns. User permission for these sites is requested at install time. */
|
||||
host_permissions?: string[] | undefined;
|
||||
/** Declares optional permissions for your extension. */
|
||||
optional_permissions?: ManifestOptionalPermission[] | undefined;
|
||||
/** Declares optional host permissions for your extension. */
|
||||
optional_host_permissions?: string[] | undefined;
|
||||
/** Enables use of particular extension APIs. */
|
||||
permissions?: ManifestPermission[] | undefined;
|
||||
/** Identifies an HTML file to display in a sidePanel. */
|
||||
side_panel?: { default_path: string } | undefined;
|
||||
/** Defines files within the extension that can be accessed by web pages or other extensions. */
|
||||
web_accessible_resources?:
|
||||
| Array<
|
||||
& {
|
||||
@@ -9685,7 +9748,7 @@ export namespace Browser {
|
||||
/** Sent after onSuspend to indicate that the app won't be unloaded after all. */
|
||||
export const onSuspendCanceled: events.Event<() => void>;
|
||||
|
||||
/** Fired when a message is sent from either an extension process (by {@link runtime.sendMessage}) or a content script (by {@link tabs.sendMessage}). */
|
||||
/** Fired when a message is sent from either {@link runtime.sendMessage} or {@link tabs.sendMessage}. */
|
||||
export const onMessage: events.Event<
|
||||
(message: any, sender: MessageSender, sendResponse: (response?: any) => void) => void
|
||||
>;
|
||||
@@ -11049,6 +11112,11 @@ export namespace Browser {
|
||||
height?: number | undefined;
|
||||
/** The session ID used to uniquely identify a tab obtained from the {@link sessions} API. */
|
||||
sessionId?: string | undefined;
|
||||
/**
|
||||
* The ID of the Split View that the tab belongs to.
|
||||
* @since Chrome 140
|
||||
*/
|
||||
splitViewId?: number | undefined;
|
||||
/**
|
||||
* The ID of the group that the tab belongs to.
|
||||
* @since Chrome 88
|
||||
@@ -11120,6 +11188,12 @@ export namespace Browser {
|
||||
*/
|
||||
export const MAX_CAPTURE_VISIBLE_TAB_CALLS_PER_SECOND = 2;
|
||||
|
||||
/**
|
||||
* An ID that represents the absence of a split tab.
|
||||
* @since Chrome 140
|
||||
*/
|
||||
export const SPLIT_VIEW_ID_NONE: -1;
|
||||
|
||||
/**
|
||||
* An ID that represents the absence of a browser tab.
|
||||
* @since Chrome 46
|
||||
@@ -11563,7 +11637,7 @@ export namespace Browser {
|
||||
export function duplicate(tabId: number, callback: (tab?: Tab) => void): void;
|
||||
|
||||
/**
|
||||
* Sends a single message to the content script(s) in the specified tab, with an optional callback to run when a response is sent back. The {@link runtime.onMessage} event is fired in each content script running in the specified tab for the current extension.
|
||||
* Sends a single message to the content script(s) in the specified tab. The {@link runtime.onMessage} event is fired in each content script running in the specified tab for the current extension.
|
||||
*
|
||||
* Can return its result via Promise in Manifest V3 or later since Chrome 99.
|
||||
*/
|
||||
@@ -12123,7 +12197,7 @@ export namespace Browser {
|
||||
* Called when the list of {@link TtsVoice} that would be returned by getVoices has changed.
|
||||
* @since Chrome 124
|
||||
*/
|
||||
const onVoicesChanged: Browser.events.Event<() => void>;
|
||||
export const onVoicesChanged: Browser.events.Event<() => void>;
|
||||
}
|
||||
|
||||
////////////////////
|
||||
@@ -13075,7 +13149,7 @@ export namespace Browser {
|
||||
EXTRA_HEADERS = "extraHeaders",
|
||||
}
|
||||
|
||||
/** @since Chrome 44 */
|
||||
/** @since Chrome 79 */
|
||||
export enum OnErrorOccurredOptions {
|
||||
/** Specifies that headers can violate Cross-Origin Resource Sharing (CORS). */
|
||||
EXTRA_HEADERS = "extraHeaders",
|
||||
@@ -13267,14 +13341,14 @@ export namespace Browser {
|
||||
extends SetPartial<WebRequestDetails, "documentId" | "documentLifecycle" | "frameType">
|
||||
{
|
||||
/** Contains the HTTP request body data. Only provided if extraInfoSpec contains 'requestBody'. */
|
||||
requestBody: {
|
||||
requestBody?: {
|
||||
/** Errors when obtaining request body data. */
|
||||
error?: string;
|
||||
/** If the request method is POST and the body is a sequence of key-value pairs encoded in UTF8, encoded as either multipart/form-data, or application/x-www-form-urlencoded, this dictionary is present and for each key contains the list of all values for that key. If the data is of another media type, or if it is malformed, the dictionary is not present. An example value of this dictionary is {'key': \['value1', 'value2'\]}. */
|
||||
formData?: { [key: string]: FormDataItem[] };
|
||||
/** If the request method is PUT or POST, and the body is not already parsed in formData, then the unparsed request body elements are contained in this array. */
|
||||
raw?: UploadData[];
|
||||
} | undefined;
|
||||
};
|
||||
}
|
||||
|
||||
export interface OnBeforeSendHeadersDetails extends WebRequestDetails {
|
||||
@@ -14085,6 +14159,30 @@ export namespace Browser {
|
||||
responseHeaders?: HeaderInfo[];
|
||||
}
|
||||
|
||||
/** @since Chrome 145 */
|
||||
export enum RuleConditionKeys {
|
||||
URL_FILTER = "urlFilter",
|
||||
REGEX_FILTER = "regexFilter",
|
||||
IS_URL_FILTER_CASE_SENSITIVE = "isUrlFilterCaseSensitive",
|
||||
INITIATOR_DOMAINS = "initiatorDomains",
|
||||
EXCLUDED_INITIATOR_DOMAINS = "excludedInitiatorDomains",
|
||||
REQUEST_DOMAINS = "requestDomains",
|
||||
EXCLUDED_REQUEST_DOMAINS = "excludedRequestDomains",
|
||||
TOP_DOMAINS = "topDomains",
|
||||
EXCLUDED_TOP_DOMAINS = "excludedTopDomains",
|
||||
DOMAINS = "domains",
|
||||
EXCLUDED_DOMAINS = "excludedDomains",
|
||||
RESOURCE_TYPES = "resourceTypes",
|
||||
EXCLUDED_RESOURCE_TYPES = "excludedResourceTypes",
|
||||
REQUEST_METHODS = "requestMethods",
|
||||
EXCLUDED_REQUEST_METHODS = "excludedRequestMethods",
|
||||
DOMAIN_TYPE = "domainType",
|
||||
TAB_IDS = "tabIds",
|
||||
EXCLUDED_TAB_IDS = "excludedTabIds",
|
||||
RESPONSE_HEADERS = "responseHeaders",
|
||||
EXCLUDED_RESPONSE_HEADERS = "excludedResponseHeaders",
|
||||
}
|
||||
|
||||
export interface MatchedRule {
|
||||
/** A matching rule's ID. */
|
||||
ruleId: number;
|
||||
@@ -14302,9 +14400,14 @@ export namespace Browser {
|
||||
* The headers provided by a hypothetical response if the request does not get blocked or redirected before it is sent. Represented as an object which maps a header name to a list of string values. If not specified, the hypothetical response would return empty response headers, which can match rules which match on the non-existence of headers. E.g. `{"content-type": ["text/html; charset=utf-8", "multipart/form-data"]}`
|
||||
* @since Chrome 129
|
||||
*/
|
||||
responseHeaders?: { [name: string]: unknown };
|
||||
responseHeaders?: { [name: string]: string[] };
|
||||
/** The ID of the tab in which the hypothetical request takes place. Does not need to correspond to a real tab ID. Default is -1, meaning that the request isn't related to a tab. */
|
||||
tabId?: number;
|
||||
/**
|
||||
* The associated top-level frame URL (if any) for the request.
|
||||
* @since Chrome 145
|
||||
*/
|
||||
topUrl?: string;
|
||||
/** The resource type of the hypothetical request. */
|
||||
type: `${ResourceType}`;
|
||||
/** The URL of the hypothetical request. */
|
||||
@@ -14635,13 +14738,13 @@ export namespace Browser {
|
||||
* Fired when the extension's side panel is closed.
|
||||
* @since Chrome 142
|
||||
*/
|
||||
const onClosed: events.Event<(info: PanelClosedInfo) => void>;
|
||||
export const onClosed: events.Event<(info: PanelClosedInfo) => void>;
|
||||
|
||||
/**
|
||||
* Fired when the extension's side panel is opened.
|
||||
* @since Chrome 141
|
||||
*/
|
||||
const onOpened: events.Event<(info: PanelOpenedInfo) => void>;
|
||||
export const onOpened: events.Event<(info: PanelOpenedInfo) => void>;
|
||||
}
|
||||
|
||||
////////////////////
|
||||
@@ -14747,7 +14850,7 @@ export namespace Browser {
|
||||
/** Specifies wildcard patterns for pages this user script will be injected into. */
|
||||
includeGlobs?: string[] | undefined;
|
||||
/** The list of ScriptSource objects defining sources of scripts to be injected into matching pages. This property must be specified for {@link register}, and when specified it must be a non-empty array.*/
|
||||
js: ScriptSource[];
|
||||
js?: ScriptSource[] | undefined;
|
||||
/** Specifies which pages this user script will be injected into. See Match Patterns for more details on the syntax of these strings. This property must be specified for {@link register}. */
|
||||
matches?: string[] | undefined;
|
||||
/** Specifies when JavaScript files are injected into the web page. The preferred and default value is `document_idle` */
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
},
|
||||
"scripts": {
|
||||
"check": "check",
|
||||
"gen": "tsx scripts/generate.ts"
|
||||
"gen": "bun run scripts/generate.ts",
|
||||
"test": "echo 'noop'",
|
||||
"test:coverage": "echo 'noop'"
|
||||
},
|
||||
"author": {
|
||||
"name": "Aaron Klinker",
|
||||
@@ -24,7 +26,7 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/chrome": "{{chromeTypesVersion}}",
|
||||
"fs-extra": "^11.3.3",
|
||||
"@types/node": "^20.0.0",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# Changelog
|
||||
|
||||
## v0.2.5
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/i18n-v0.2.4...i18n-v0.2.5)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Add `.jsonc` support for locale files to match docs ([#2066](https://github.com/wxt-dev/wxt/pull/2066))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Manually fix markdownlint errors ([#1711](https://github.com/wxt-dev/wxt/pull/1711))
|
||||
- **deps:** Upgrade oxlint from 0.16.8 to 1.14.0 ([a01928e0](https://github.com/wxt-dev/wxt/commit/a01928e0))
|
||||
- **deps:** Upgrade typescript from 5.8.3 to 5.9.2 ([a6eef643](https://github.com/wxt-dev/wxt/commit/a6eef643))
|
||||
- Create script for managing dependency upgrades ([#1875](https://github.com/wxt-dev/wxt/pull/1875))
|
||||
- **deps:** Upgrade all dev dependencies ([#1876](https://github.com/wxt-dev/wxt/pull/1876))
|
||||
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
|
||||
- Use `tsdown` to build packages ([#2006](https://github.com/wxt-dev/wxt/pull/2006))
|
||||
- Move script-only dev dependencies to top-level `package.json` ([#2007](https://github.com/wxt-dev/wxt/pull/2007))
|
||||
- Update dependencies ([#2069](https://github.com/wxt-dev/wxt/pull/2069))
|
||||
- Upgrade major deps ([#2070](https://github.com/wxt-dev/wxt/pull/2070))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Ilya Kubariev ([@gymnasy55](https://github.com/gymnasy55))
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v0.2.4
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/i18n-v0.2.3...i18n-v0.2.4)
|
||||
|
||||
@@ -161,7 +161,7 @@ hello: Hello $1!
|
||||
order: Thanks for ordering your $1
|
||||
```
|
||||
|
||||
#### Escapting `$`
|
||||
#### Escaping `$`
|
||||
|
||||
To escape the dollar sign, put another `$` in front of it:
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@wxt-dev/i18n",
|
||||
"description": "Type-safe wrapper around browser.i18n.getMessage with additional features",
|
||||
"version": "0.2.4",
|
||||
"version": "0.2.5",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -23,13 +23,15 @@
|
||||
"scripts": {
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "buildc --deps-only -- check",
|
||||
"test": "buildc --deps-only -- vitest"
|
||||
"test": "buildc --deps-only -- vitest",
|
||||
"test:coverage": "bun run test run --coverage",
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wxt-dev/browser": "workspace:^",
|
||||
"chokidar": "^5.0.0",
|
||||
"confbox": "^0.1.8 || ^0.2.2",
|
||||
"fast-glob": "^3.3.3"
|
||||
"confbox": "^0.2.4",
|
||||
"tinyglobby": "^0.2.15"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.19.7"
|
||||
@@ -40,9 +42,11 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@types/node": "^20.17.6",
|
||||
"oxlint": "^1.43.0",
|
||||
"publint": "^0.3.17",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18",
|
||||
"vitest-plugin-random-seed": "^1.1.2",
|
||||
|
||||
@@ -43,7 +43,7 @@ describe('Utils', () => {
|
||||
});
|
||||
|
||||
describe('getSubstitutionCount', () => {
|
||||
it('should return the last substution present in the message', () => {
|
||||
it('should return the last substitution present in the message', () => {
|
||||
expect(getSubstitutionCount('I like $1, but I like $2 better')).toBe(2);
|
||||
});
|
||||
|
||||
|
||||
+31
-18
@@ -16,12 +16,25 @@ export { SUPPORTED_LOCALES } from './supported-locales';
|
||||
// TYPES
|
||||
//
|
||||
|
||||
export type SimpleMessage = string;
|
||||
|
||||
export type PluralMessage = Record<'n' | number, string>;
|
||||
|
||||
export interface ChromeMessage {
|
||||
message: string;
|
||||
description?: string;
|
||||
placeholders?: Record<string, { content: string; example?: string }>;
|
||||
}
|
||||
|
||||
export type Message =
|
||||
| SimpleMessage
|
||||
| PluralMessage
|
||||
| ChromeMessage
|
||||
| Message[]
|
||||
| { [key: string]: Message };
|
||||
|
||||
export type MessagesObject = Record<string, Message>;
|
||||
|
||||
export interface ParsedBaseMessage {
|
||||
key: string[];
|
||||
substitutions: number;
|
||||
@@ -51,13 +64,14 @@ export type MessageFormat = 'JSON5' | 'YAML' | 'TOML';
|
||||
//
|
||||
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/reference/api/i18n#overview-predefined
|
||||
* See
|
||||
* https://developer.chrome.com/docs/extensions/reference/api/i18n#overview-predefined
|
||||
*/
|
||||
const PREDEFINED_MESSAGES: Record<string, ChromeMessage> = {
|
||||
'@@extension_id': {
|
||||
message: '<browser.runtime.id>',
|
||||
description:
|
||||
"The extension or app ID; you might use this string to construct URLs for resources inside the extension. Even unlocalized extensions can use this message.\nNote: You can't use this message in a manifest file.",
|
||||
"The extension or app ID; you might use this string to construct URLs for resources inside the extension. Even non-localized extensions can use this message.\nNote: You can't use this message in a manifest file.",
|
||||
},
|
||||
'@@ui_locale': {
|
||||
message: '<browser.i18n.getUiLocale()>',
|
||||
@@ -94,7 +108,7 @@ const EXT_FORMATS_MAP: Record<string, MessageFormat> = {
|
||||
'.toml': 'TOML',
|
||||
};
|
||||
|
||||
const PARSERS: Record<MessageFormat, (text: string) => any> = {
|
||||
const PARSERS: Record<MessageFormat, (text: string) => MessagesObject> = {
|
||||
YAML: parseYAML,
|
||||
JSON5: parseJSON5,
|
||||
TOML: parseTOML,
|
||||
@@ -110,20 +124,17 @@ const ALLOWED_CHROME_MESSAGE_KEYS: Set<string> = new Set<keyof ChromeMessage>([
|
||||
// PARSING
|
||||
//
|
||||
|
||||
/**
|
||||
* Parse a messages file, extract the messages. Supports JSON, JSON5, and YAML.
|
||||
*/
|
||||
/** Parse a messages file, extract the messages. Supports JSON, JSON5, and YAML. */
|
||||
export async function parseMessagesFile(
|
||||
file: string,
|
||||
): Promise<ParsedMessage[]> {
|
||||
const text = await readFile(file, 'utf8');
|
||||
const ext = extname(file).toLowerCase();
|
||||
|
||||
return parseMessagesText(text, EXT_FORMATS_MAP[ext] ?? 'JSON5');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string, extracting the messages. Supports JSON, JSON5, and YAML.
|
||||
*/
|
||||
/** Parse a string, extracting the messages. Supports JSON, JSON5, and YAML. */
|
||||
export function parseMessagesText(
|
||||
text: string,
|
||||
format: 'JSON5' | 'YAML' | 'TOML',
|
||||
@@ -131,10 +142,8 @@ export function parseMessagesText(
|
||||
return parseMessagesObject(PARSERS[format](text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the JS object form of a raw messages file, extract the messages.
|
||||
*/
|
||||
export function parseMessagesObject(object: any): ParsedMessage[] {
|
||||
/** Given the JS object form of a raw messages file, extract the messages. */
|
||||
export function parseMessagesObject(object: MessagesObject): ParsedMessage[] {
|
||||
return _parseMessagesObject(
|
||||
[],
|
||||
{
|
||||
@@ -147,7 +156,7 @@ export function parseMessagesObject(object: any): ParsedMessage[] {
|
||||
|
||||
function _parseMessagesObject(
|
||||
path: string[],
|
||||
object: any,
|
||||
object: Message,
|
||||
depth: number,
|
||||
): ParsedMessage[] {
|
||||
switch (typeof object) {
|
||||
@@ -168,15 +177,17 @@ function _parseMessagesObject(
|
||||
];
|
||||
}
|
||||
case 'object':
|
||||
if ([null, undefined].includes(object)) {
|
||||
if (object == null) {
|
||||
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),
|
||||
);
|
||||
|
||||
if (isPluralMessage(object)) {
|
||||
const message = Object.values(object).join('|');
|
||||
const substitutions = getSubstitutionCount(message);
|
||||
@@ -189,6 +200,7 @@ function _parseMessagesObject(
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (depth === 1 && isChromeMessage(object)) {
|
||||
const message = applyChromeMessagePlaceholders(object);
|
||||
const substitutions = getSubstitutionCount(message);
|
||||
@@ -201,6 +213,7 @@ function _parseMessagesObject(
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return Object.entries(object).flatMap(([key, value]) =>
|
||||
_parseMessagesObject(path.concat(key), value, depth + 1),
|
||||
);
|
||||
@@ -209,13 +222,13 @@ function _parseMessagesObject(
|
||||
}
|
||||
}
|
||||
|
||||
function isPluralMessage(object: any): object is Record<number | 'n', string> {
|
||||
function isPluralMessage(object: Message): object is PluralMessage {
|
||||
return Object.keys(object).every(
|
||||
(key) => key === 'n' || isFinite(Number(key)),
|
||||
);
|
||||
}
|
||||
|
||||
function isChromeMessage(object: any): object is ChromeMessage {
|
||||
function isChromeMessage(object: Message): object is ChromeMessage {
|
||||
return Object.keys(object).every((key) =>
|
||||
ALLOWED_CHROME_MESSAGE_KEYS.has(key),
|
||||
);
|
||||
@@ -227,7 +240,7 @@ function isChromeMessage(object: any): object is ChromeMessage {
|
||||
|
||||
export function generateTypeText(messages: ParsedMessage[]): string {
|
||||
const renderMessageEntry = (message: ParsedMessage): string => {
|
||||
// Use . for deep keys at runtime and types
|
||||
// Use '.' for deep keys at runtime and types
|
||||
const key = message.key.join('.');
|
||||
|
||||
const features = [
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/**
|
||||
* @module @wxt-dev/i18n
|
||||
*/
|
||||
/** @module @wxt-dev/i18n */
|
||||
import {
|
||||
I18nStructure,
|
||||
DefaultI18nStructure,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* ```ts
|
||||
* export default defineConfig({
|
||||
* modules: ["@wxt-dev/i18n/module"],
|
||||
* modules: ['@wxt-dev/i18n/module'],
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
generateTypeText,
|
||||
SUPPORTED_LOCALES,
|
||||
} from './build';
|
||||
import glob from 'fast-glob';
|
||||
import { glob } from 'tinyglobby';
|
||||
import { basename, extname, join, resolve } from 'node:path';
|
||||
import { watch } from 'chokidar';
|
||||
import { GeneratedPublicFile, WxtDirFileEntry } from 'wxt';
|
||||
@@ -47,6 +47,7 @@ export default defineWxtModule<I18nOptions>({
|
||||
const files = await glob('*.{json,json5,jsonc,yml,yaml,toml}', {
|
||||
cwd: localesDir,
|
||||
absolute: true,
|
||||
expandDirectories: false,
|
||||
});
|
||||
|
||||
const unsupportedLocales: string[] = [];
|
||||
@@ -180,13 +181,12 @@ export { type GeneratedI18nStructure }
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Options for the i18n module
|
||||
*/
|
||||
/** Options for the i18n module */
|
||||
export interface I18nOptions {
|
||||
/**
|
||||
* Directory containing files that define the translations.
|
||||
* @default "${config.srcDir}/locales"
|
||||
*
|
||||
* @default '${config.srcDir}/locales'
|
||||
*/
|
||||
localesDir?: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# `@wxt-dev/is-background`
|
||||
|
||||
Exports a getter to determine if the current JS context is the background or not.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
pnpm add @wxt-dev/is-background
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { isBackground } from '@wxt-dev/is-background';
|
||||
|
||||
isBackground(); // true | false
|
||||
```
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "@wxt-dev/is-background",
|
||||
"type": "module",
|
||||
"version": "1.0.0",
|
||||
"description": "Check if the current context is the background or not.",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "bun run build && check",
|
||||
"test": "buildc --deps-only -- vitest",
|
||||
"test:coverage": "bun run test run --coverage",
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wxt-dev/browser": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wxt-dev/wxt.git",
|
||||
"directory": "packages/is-background"
|
||||
},
|
||||
"keywords": [
|
||||
"wxt",
|
||||
"chrome",
|
||||
"web",
|
||||
"extension",
|
||||
"is",
|
||||
"background",
|
||||
"script",
|
||||
"page",
|
||||
"service",
|
||||
"worker"
|
||||
],
|
||||
"author": {
|
||||
"name": "Aaron Klinker",
|
||||
"email": "aaronklinker1+wxt@gmail.com"
|
||||
},
|
||||
"funding": "https://github.com/sponsors/wxt-dev",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"types": "./dist/index.d.mts",
|
||||
"module": "./dist/index.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.mts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import { describe, it, vi, expect } from 'vitest';
|
||||
import { getIsBackground } from '../getter';
|
||||
|
||||
let mockBrowser: any;
|
||||
vi.mock('@wxt-dev/browser', () => ({
|
||||
get browser() {
|
||||
return mockBrowser;
|
||||
},
|
||||
}));
|
||||
|
||||
const backgroundWindow = Symbol('Background');
|
||||
const otherWindow = Symbol('Other');
|
||||
|
||||
function setupEnv(options: {
|
||||
window: symbol | undefined;
|
||||
hasExtensionApis: boolean;
|
||||
hasGetBackgroundPage: boolean;
|
||||
hasServiceWorkerGlobalScope: boolean;
|
||||
}): void {
|
||||
vi.unstubAllGlobals();
|
||||
mockBrowser = undefined;
|
||||
|
||||
if (options.window) {
|
||||
vi.stubGlobal('window', options.window);
|
||||
}
|
||||
|
||||
if (options.hasExtensionApis) {
|
||||
mockBrowser = {
|
||||
runtime: {
|
||||
id: 'test',
|
||||
},
|
||||
...(options.hasGetBackgroundPage && {
|
||||
extension: {
|
||||
getBackgroundPage: () => backgroundWindow,
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (options.hasServiceWorkerGlobalScope) {
|
||||
class ServiceWorkerGlobalScope {}
|
||||
vi.stubGlobal('ServiceWorkerGlobalScope', ServiceWorkerGlobalScope);
|
||||
vi.stubGlobal('self', new ServiceWorkerGlobalScope());
|
||||
}
|
||||
}
|
||||
|
||||
describe('isBackground Getter', () => {
|
||||
describe('Non-extension contexts', () => {
|
||||
it('should return false', () => {
|
||||
setupEnv({
|
||||
hasServiceWorkerGlobalScope: false,
|
||||
hasExtensionApis: false,
|
||||
hasGetBackgroundPage: false,
|
||||
window: otherWindow,
|
||||
});
|
||||
|
||||
expect(getIsBackground()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Chromium & Safari', () => {
|
||||
const hasExtensionApis = true;
|
||||
|
||||
describe('MV2', () => {
|
||||
const hasServiceWorkerGlobalScope = false;
|
||||
const hasGetBackgroundPage = true;
|
||||
|
||||
it('should return true inside the background page', () => {
|
||||
setupEnv({
|
||||
hasServiceWorkerGlobalScope,
|
||||
hasExtensionApis,
|
||||
hasGetBackgroundPage,
|
||||
window: backgroundWindow,
|
||||
});
|
||||
|
||||
expect(getIsBackground()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false outside the background page', () => {
|
||||
setupEnv({
|
||||
hasServiceWorkerGlobalScope,
|
||||
hasExtensionApis,
|
||||
hasGetBackgroundPage,
|
||||
window: otherWindow,
|
||||
});
|
||||
|
||||
expect(getIsBackground()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('MV3', () => {
|
||||
const hasGetBackgroundPage = false;
|
||||
|
||||
it('should return true inside the service worker', () => {
|
||||
setupEnv({
|
||||
hasServiceWorkerGlobalScope: true,
|
||||
hasExtensionApis,
|
||||
hasGetBackgroundPage,
|
||||
window: undefined,
|
||||
});
|
||||
|
||||
expect(getIsBackground()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false outside the service worker', () => {
|
||||
setupEnv({
|
||||
hasServiceWorkerGlobalScope: false,
|
||||
hasExtensionApis,
|
||||
hasGetBackgroundPage,
|
||||
window: otherWindow,
|
||||
});
|
||||
|
||||
expect(getIsBackground()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Firefox, MV2 & MV3', () => {
|
||||
const hasServiceWorkerGlobalScope = false;
|
||||
const hasExtensionApis = true;
|
||||
const hasGetBackgroundPage = true;
|
||||
|
||||
it('should return true inside the background page', () => {
|
||||
setupEnv({
|
||||
hasServiceWorkerGlobalScope,
|
||||
hasExtensionApis,
|
||||
hasGetBackgroundPage,
|
||||
window: backgroundWindow,
|
||||
});
|
||||
|
||||
expect(getIsBackground()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false outside the background page', () => {
|
||||
setupEnv({
|
||||
hasServiceWorkerGlobalScope,
|
||||
hasExtensionApis,
|
||||
hasGetBackgroundPage,
|
||||
window: otherWindow,
|
||||
});
|
||||
|
||||
expect(getIsBackground()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import { browser } from '@wxt-dev/browser';
|
||||
|
||||
declare class ServiceWorkerGlobalScope {}
|
||||
|
||||
export function getIsBackground(): boolean {
|
||||
// Are we in an extension context?
|
||||
if (!browser?.runtime?.id) return false;
|
||||
|
||||
// Is this a true MV3 service worker?
|
||||
//
|
||||
// - ✅ Chromium MV3
|
||||
// - ❌ Firefox MV3 - Uses a non-persistent HTML page instead of a service worker.
|
||||
// - ✅ Safari MV3
|
||||
if (
|
||||
typeof ServiceWorkerGlobalScope !== 'undefined' &&
|
||||
self instanceof ServiceWorkerGlobalScope
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Is this the background page?
|
||||
//
|
||||
// - ✅ Chromium MV2
|
||||
// - ✅ Firefox MV2
|
||||
// - ✅ Firefox MV3 - Works with the non-persistent HTML page
|
||||
// - ✅ Safari MV2
|
||||
return (
|
||||
typeof window !== 'undefined' &&
|
||||
typeof browser.extension?.getBackgroundPage === 'function' &&
|
||||
browser.extension.getBackgroundPage() === window
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* This module uses a lazy getter function so the logic isn't ran until it's
|
||||
* needed.
|
||||
*
|
||||
* This has a few benefits:
|
||||
*
|
||||
* 1. Easier to mock in tests
|
||||
* 2. Safe to import in NodeJS environments (but it should be safe to run just
|
||||
* in-case)
|
||||
* 3. Keeps startup fast by waiting to run the slow functions (`instanceof` or
|
||||
* `browser.extension.getBackgroundPage`) until needed
|
||||
*
|
||||
* @module @wxt-dev/is-background
|
||||
*/
|
||||
import { getIsBackground } from './getter';
|
||||
|
||||
let cached: boolean | undefined;
|
||||
|
||||
/**
|
||||
* Getter that returns if the current context is apart of an extension's
|
||||
* background or not.
|
||||
*
|
||||
* > This function caches the result when called for the first time so it doesn't
|
||||
* > have to recalculate.
|
||||
*
|
||||
* @returns True when in a background page or service worker.
|
||||
*/
|
||||
export function isBackground(): boolean {
|
||||
if (cached == null) cached = getIsBackground();
|
||||
return cached;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"exclude": ["node_modules/**", "dist/**"]
|
||||
}
|
||||
@@ -1,5 +1,51 @@
|
||||
# Changelog
|
||||
|
||||
## v1.2.2
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.2.1...module-react-v1.2.2)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Add `@vitejs/plugin-react` v6 support ([c61fa8f8](https://github.com/wxt-dev/wxt/commit/c61fa8f8))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v1.2.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.2.0...module-react-v1.2.1)
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Add prepack script to all packages ([032f7931](https://github.com/wxt-dev/wxt/commit/032f7931))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v1.2.0
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.5...module-react-v1.2.0)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add `vitePluginsBefore` option ([#2170](https://github.com/wxt-dev/wxt/pull/2170))
|
||||
- Vite 8 support ([bfd4af5d](https://github.com/wxt-dev/wxt/commit/bfd4af5d))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
|
||||
- Use `tsdown` to build packages ([#2006](https://github.com/wxt-dev/wxt/pull/2006))
|
||||
- Move script-only dev dependencies to top-level `package.json` ([#2007](https://github.com/wxt-dev/wxt/pull/2007))
|
||||
- Update dependencies ([#2069](https://github.com/wxt-dev/wxt/pull/2069))
|
||||
- **deps:** Upgrade deps ([#2175](https://github.com/wxt-dev/wxt/pull/2175))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Martin Broder <hello@martinbroder.com>
|
||||
|
||||
## v1.1.5
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.4...module-react-v1.1.5)
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import 'wxt';
|
||||
import { addImportPreset, addViteConfig, defineWxtModule } from 'wxt/modules';
|
||||
import react, { Options as PluginOptions } from '@vitejs/plugin-react';
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
export default defineWxtModule<ReactModuleOptions>({
|
||||
name: '@wxt-dev/module-react',
|
||||
configKey: 'react',
|
||||
setup(wxt, options) {
|
||||
const { vite } = options ?? {};
|
||||
const { vite, vitePluginsBefore } = options ?? {};
|
||||
|
||||
addViteConfig(wxt, () => ({
|
||||
plugins: [react(vite)],
|
||||
plugins: [...(vitePluginsBefore ?? []), react(vite)],
|
||||
}));
|
||||
|
||||
addImportPreset(wxt, 'react');
|
||||
@@ -30,6 +31,12 @@ export default defineWxtModule<ReactModuleOptions>({
|
||||
|
||||
export interface ReactModuleOptions {
|
||||
vite?: PluginOptions;
|
||||
/**
|
||||
* Vite plugins to add before the `react()` plugin. Some plugins like the
|
||||
* `@tanstack/router-plugin` need to be added before `react()` to work
|
||||
* correctly.
|
||||
*/
|
||||
vitePluginsBefore?: PluginOption[];
|
||||
}
|
||||
|
||||
declare module 'wxt' {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/wxt-dev",
|
||||
"version": "1.1.5",
|
||||
"version": "1.2.2",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -40,23 +40,29 @@
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "wxt",
|
||||
"check": "pnpm build && check",
|
||||
"check": "bun run build && check",
|
||||
"build": "buildc -- tsdown",
|
||||
"prepare": "buildc --deps-only -- wxt prepare"
|
||||
"postinstall": "buildc --deps-only -- wxt prepare",
|
||||
"test": "echo 'noop'",
|
||||
"test:coverage": "echo 'noop'",
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^5.4.19 || ^6.3.4 || ^7.0.0 || ^8.0.0-0",
|
||||
"wxt": ">=0.19.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-react": "^4.4.1 || ^5.0.0"
|
||||
"@vitejs/plugin-react": "^4.4.1 || ^5.0.0 || ^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.13",
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"publint": "^0.3.17",
|
||||
"publint": "^0.3.18",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.0.0",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +40,12 @@
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "wxt",
|
||||
"check": "pnpm build && check",
|
||||
"check": "bun run build && check",
|
||||
"build": "buildc -- tsdown",
|
||||
"prepare": "buildc --deps-only -- wxt prepare"
|
||||
"postinstall": "buildc --deps-only -- wxt prepare",
|
||||
"test": "echo 'noop'",
|
||||
"test:coverage": "echo 'noop'",
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.19.16"
|
||||
@@ -51,8 +54,10 @@
|
||||
"vite-plugin-solid": "^2.11.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"publint": "^0.3.17",
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"publint": "^0.3.18",
|
||||
"solid-js": "^1.9.11",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# Changelog
|
||||
|
||||
## v2.0.5
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v2.0.4...module-svelte-v2.0.5)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Update `@sveltejs/vite-plugin-svelte` version range to support Vite 8 ([48571f50](https://github.com/wxt-dev/wxt/commit/48571f50))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
|
||||
- Use `tsdown` to build packages ([#2006](https://github.com/wxt-dev/wxt/pull/2006))
|
||||
- Move script-only dev dependencies to top-level `package.json` ([#2007](https://github.com/wxt-dev/wxt/pull/2007))
|
||||
- Update dependencies ([#2069](https://github.com/wxt-dev/wxt/pull/2069))
|
||||
- **deps:** Upgrade deps ([#2175](https://github.com/wxt-dev/wxt/pull/2175))
|
||||
- Add prepack script to all packages ([032f7931](https://github.com/wxt-dev/wxt/commit/032f7931))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v2.0.4
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v2.0.3...module-svelte-v2.0.4)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/wxt-dev",
|
||||
"version": "2.0.4",
|
||||
"version": "2.0.5",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -40,17 +40,22 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "pnpm build && check"
|
||||
"check": "bun run build && check",
|
||||
"test": "echo 'noop'",
|
||||
"test:coverage": "echo 'noop'",
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.18.6",
|
||||
"svelte": ">=5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0 || ^5.0.0 || ^6.0.0"
|
||||
"@sveltejs/vite-plugin-svelte": ">=4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"publint": "^0.3.17",
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
|
||||
@@ -40,7 +40,10 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "pnpm build && check"
|
||||
"check": "bun run build && check",
|
||||
"test": "echo 'noop'",
|
||||
"test:coverage": "echo 'noop'",
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.19.16"
|
||||
@@ -49,7 +52,9 @@
|
||||
"@vitejs/plugin-vue": "^5.2.3 || ^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"publint": "^0.3.17",
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//
|
||||
// USAGE:
|
||||
// pnpm dev
|
||||
// pnpm dev firefox-nightly
|
||||
// pnpm dev <target>
|
||||
// bun run dev
|
||||
// bun run dev firefox-nightly
|
||||
// bun run dev <target>
|
||||
//
|
||||
|
||||
import { run } from './src';
|
||||
|
||||
@@ -21,15 +21,18 @@
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/wxt-dev",
|
||||
"scripts": {
|
||||
"check": "pnpm build && check",
|
||||
"check": "bun run build && check",
|
||||
"test": "buildc --deps-only -- vitest",
|
||||
"dev": "tsx --trace-warnings dev.ts",
|
||||
"test:coverage": "bun run test run --coverage",
|
||||
"dev": "bun run dev.ts",
|
||||
"build": "buildc -- tsdown",
|
||||
"prepublishOnly": "pnpm build"
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"oxlint": "^1.43.0",
|
||||
"publint": "^0.3.17",
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ export type BrowserPlatform = 'windows' | 'mac' | 'linux';
|
||||
|
||||
export type KnownTarget =
|
||||
| 'arc'
|
||||
| 'brave'
|
||||
| 'chromium'
|
||||
| 'chrome'
|
||||
| 'chrome-beta'
|
||||
@@ -28,6 +29,11 @@ export const KNOWN_BROWSER_PATHS: Record<
|
||||
linux: [],
|
||||
windows: [],
|
||||
},
|
||||
brave: {
|
||||
mac: ['Applications/Brave Browser.app/Contents/MacOS/Brave Browser'],
|
||||
linux: [],
|
||||
windows: [],
|
||||
},
|
||||
chromium: {
|
||||
mac: [],
|
||||
linux: [
|
||||
@@ -124,7 +130,8 @@ export const KNOWN_BROWSER_PATHS: Record<
|
||||
};
|
||||
|
||||
/**
|
||||
* When targeting a browser, this map contains the other targets to fall back on when a binary could not be found for the primary target.
|
||||
* When targeting a browser, this map contains the other targets to fall back on
|
||||
* when a binary could not be found for the primary target.
|
||||
*/
|
||||
export const FALLBACK_TARGETS: Partial<Record<KnownTarget, KnownTarget[]>> = {
|
||||
chrome: [
|
||||
|
||||
@@ -4,8 +4,11 @@ import { createCdpConnection } from './cdp';
|
||||
|
||||
/**
|
||||
* Install an extension to an already running instance of Firefox.
|
||||
* @param debuggerUrl The URL of the Firefox BiDi server (ex: `ws://127.0.0.1:45912`).
|
||||
* @param extensionDir Absolute path to the directory containing the extension to be installed.
|
||||
*
|
||||
* @param debuggerUrl The URL of the Firefox BiDi server (ex:
|
||||
* `ws://127.0.0.1:45912`).
|
||||
* @param extensionDir Absolute path to the directory containing the extension
|
||||
* to be installed.
|
||||
*/
|
||||
export async function installFirefox(
|
||||
debuggerUrl: string,
|
||||
@@ -33,7 +36,8 @@ export type BidiWebExtensionInstallResponse = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a child process of Chrome, install an extension. The process must be started with the following flags:
|
||||
* Given a child process of Chrome, install an extension. The process must be
|
||||
* started with the following flags:
|
||||
*
|
||||
* - `--remote-debugging-pipe`
|
||||
* - `--user-data-dir=...`
|
||||
|
||||
@@ -8,6 +8,7 @@ import { resolve, join } from 'node:path';
|
||||
import { homedir, tmpdir } from 'node:os';
|
||||
import { debug } from './debug';
|
||||
import { mkdtemp, open } from 'node:fs/promises';
|
||||
import { styleText } from 'node:util';
|
||||
|
||||
const debugOptions = debug.scoped('options');
|
||||
|
||||
@@ -17,21 +18,46 @@ export type Target = KnownTarget | UnknownTarget;
|
||||
export type RunOptions = {
|
||||
/** Paths to binaries to use for each target. */
|
||||
browserBinaries?: Record<string, string>;
|
||||
/** Customize the arguments passed to the chromium binary. Conflicting arguments with required ones to install extensions are ignored. */
|
||||
/**
|
||||
* Customize the arguments passed to the chromium binary. Conflicting
|
||||
* arguments with required ones to install extensions are ignored.
|
||||
*/
|
||||
chromiumArgs?: string[];
|
||||
/** Control how data is persisted between launches. Either save data at a user level, project level, or don't persist data at all. Defaults to `project`. */
|
||||
/**
|
||||
* Control how data is persisted between launches. Either save data at a user
|
||||
* level, project level, or don't persist data at all. Defaults to `project`.
|
||||
*/
|
||||
dataPersistence?: 'user' | 'project' | 'none';
|
||||
/** Customize where your profile's data is stored when using `dataPersistence: 'project'`. Can be absolute or relative to the current working directory. */
|
||||
/**
|
||||
* Customize where your profile's data is stored when using `dataPersistence:
|
||||
* 'project'`. Can be absolute or relative to the current working directory.
|
||||
*/
|
||||
projectDataDir?: string;
|
||||
/** Customize the port Chrome's debugger is listening on. Defaults to a random open port. */
|
||||
/**
|
||||
* Customize the port Chrome's debugger is listening on. Defaults to a random
|
||||
* open port.
|
||||
*/
|
||||
chromiumRemoteDebuggingPort?: number;
|
||||
/** Directory where the extension will be installed from. Should contain a `manifest.json` file. Can be relative to the current working directory. Defaults to the current working directory. */
|
||||
/**
|
||||
* Directory where the extension will be installed from. Should contain a
|
||||
* `manifest.json` file. Can be relative to the current working directory.
|
||||
* Defaults to the current working directory.
|
||||
*/
|
||||
extensionDir?: string;
|
||||
/** Customize the arguments passed to the firefox binary. Conflicting arguments with required ones to install extensions are ignored. */
|
||||
/**
|
||||
* Customize the arguments passed to the firefox binary. Conflicting arguments
|
||||
* with required ones to install extensions are ignored.
|
||||
*/
|
||||
firefoxArgs?: string[];
|
||||
/** Customize the port Firefox's debugger is listening on. Defaults to a random open port. */
|
||||
/**
|
||||
* Customize the port Firefox's debugger is listening on. Defaults to a random
|
||||
* open port.
|
||||
*/
|
||||
firefoxRemoteDebuggingPort?: number;
|
||||
/** Specify the browser to open. Defaults to `"chrome"`, but you can pass any string. */
|
||||
/**
|
||||
* Specify the browser to open. Defaults to `"chrome"`, but you can pass any
|
||||
* string.
|
||||
*/
|
||||
target?: Target;
|
||||
};
|
||||
|
||||
@@ -148,10 +174,8 @@ function resolveChromiumArgs(
|
||||
],
|
||||
userArgs,
|
||||
{
|
||||
'--remote-debugging-port':
|
||||
'\x1b[1m\x1b[33mCustom Chromium --remote-debugging-port argument ignored.\x1b[0m Use \x1b[36mchromiumRemoteDebuggingPort\x1b[0m option instead.',
|
||||
'--user-data-dir':
|
||||
'\x1b[1m\x1b[33mCustom Chromium --user-data-dir argument ignored.\x1b[0m Use \x1b[36mdataPersistence\x1b[0m option instead.',
|
||||
'--remote-debugging-port': `${styleText(['bold', 'yellow'], 'Custom Chromium --remote-debugging-port argument ignored.')} Use ${styleText('cyan', 'chromiumRemoteDebuggingPort')} option instead.`,
|
||||
'--user-data-dir': `${styleText(['bold', 'yellow'], 'Custom Chromium --user-data-dir argument ignored.')} Use ${styleText('cyan', 'dataPersistence')} option instead.`,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -175,10 +199,8 @@ function resolveFirefoxArgs(
|
||||
],
|
||||
userArgs,
|
||||
{
|
||||
'--remote-debugging-port':
|
||||
'\x1b[1m\x1b[33mCustom Firefox --remote-debugging-port argument ignored.\x1b[0m Use \x1b[36mfirefoxDebuggerPort\x1b[0m option instead.',
|
||||
'--profile':
|
||||
'\x1b[1m\x1b[33mCustom Firefox --profile argument ignored.\x1b[0m Use \x1b[36mdataPersistence\x1b[0m option instead.',
|
||||
'--remote-debugging-port': `${styleText(['bold', 'yellow'], 'Custom Firefox --remote-debugging-port argument ignored.')} Use ${styleText('cyan', 'firefoxRemoteDebuggingPort')} option instead.`,
|
||||
'--profile': `${styleText(['bold', 'yellow'], 'Custom Firefox --profile argument ignored.')} Use ${styleText('cyan', 'dataPersistence')} option instead.`,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -219,8 +241,9 @@ async function pathExists(path: string): Promise<boolean> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Copied from https://github.com/GoogleChrome/chrome-launcher/blob/main/src/flags.ts
|
||||
* with some flags commented out. Run tests after updating to compare.
|
||||
* Copied from
|
||||
* https://github.com/GoogleChrome/chrome-launcher/blob/main/src/flags.ts with
|
||||
* some flags commented out. Run tests after updating to compare.
|
||||
*/
|
||||
const CHROME_LAUNCHER_DEFAULT_FLAGS = [
|
||||
'--disable-features=' +
|
||||
|
||||
@@ -1,5 +1,49 @@
|
||||
# Changelog
|
||||
|
||||
## v1.2.8
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.2.7...storage-v1.2.8)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Correctly update version metadata when setting a value for the first time ([#2139](https://github.com/wxt-dev/wxt/pull/2139))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v1.2.7
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.2.6...storage-v1.2.7)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Add another `defineItem` signature when `init` function is passed ([#1909](https://github.com/wxt-dev/wxt/pull/1909))
|
||||
- **storage:** Set version number on init ([#1996](https://github.com/wxt-dev/wxt/pull/1996))
|
||||
|
||||
### 💅 Refactors
|
||||
|
||||
- Code cleanup in analytics package ([#2084](https://github.com/wxt-dev/wxt/pull/2084))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Rename keys name of getMetas() to be proper ([#2105](https://github.com/wxt-dev/wxt/pull/2105))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Fix type errors after `chrome` type upgrades ([6036c6e8](https://github.com/wxt-dev/wxt/commit/6036c6e8))
|
||||
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
|
||||
- Use `tsdown` to build packages ([#2006](https://github.com/wxt-dev/wxt/pull/2006))
|
||||
- Move script-only dev dependencies to top-level `package.json` ([#2007](https://github.com/wxt-dev/wxt/pull/2007))
|
||||
- Update dependencies ([#2069](https://github.com/wxt-dev/wxt/pull/2069))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Willow ([@42willow](https://github.com/42willow))
|
||||
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Dan McGee <dpmcgee@gmail.com>
|
||||
|
||||
## v1.2.6
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.2.5...storage-v1.2.6)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@wxt-dev/storage",
|
||||
"description": "Web extension storage API provided by WXT, supports all browsers.",
|
||||
"version": "1.2.6",
|
||||
"version": "1.2.8",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -27,7 +27,9 @@
|
||||
"scripts": {
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "buildc --deps-only -- check",
|
||||
"test": "buildc --deps-only -- vitest"
|
||||
"test": "buildc --deps-only -- vitest",
|
||||
"test:coverage": "bun run test run --coverage",
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wxt-dev/browser": "workspace:^",
|
||||
@@ -35,9 +37,11 @@
|
||||
"dequal": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@webext-core/fake-browser": "^1.3.4",
|
||||
"oxlint": "^1.43.0",
|
||||
"publint": "^0.3.17",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { fakeBrowser } from '@webext-core/fake-browser';
|
||||
import { describe, it, expect, beforeEach, vi, expectTypeOf } from 'vitest';
|
||||
import { MigrationError, type WxtStorageItem, storage } from '../index';
|
||||
import { browser } from '@wxt-dev/browser';
|
||||
import { beforeEach, describe, expect, expectTypeOf, it, vi } from 'vitest';
|
||||
import { MigrationError, type WxtStorageItem, storage } from '../index';
|
||||
|
||||
/**
|
||||
* This works because fakeBrowser is synchronous, and is will finish any number of chained
|
||||
* calls within a single tick of the event loop, ie: a timeout of 0.
|
||||
* This works because fakeBrowser is synchronous, and is will finish any number
|
||||
* of chained calls within a single tick of the event loop, ie: a timeout of 0.
|
||||
*/
|
||||
async function waitForMigrations() {
|
||||
return new Promise((res) => setTimeout(res));
|
||||
@@ -23,6 +23,9 @@ describe('Storage Utils', () => {
|
||||
beforeEach(() => {
|
||||
fakeBrowser.reset();
|
||||
storage.unwatch();
|
||||
|
||||
// Setup a spy to check for excessive calls
|
||||
fakeBrowser.storage.local.set = vi.spyOn(fakeBrowser.storage.local, 'set');
|
||||
});
|
||||
|
||||
describe.each(['local', 'sync', 'managed', 'session'] as const)(
|
||||
@@ -662,7 +665,7 @@ describe('Storage Utils', () => {
|
||||
// @ts-expect-error
|
||||
await storage.getItem('test').catch(() => {});
|
||||
// @ts-expect-error
|
||||
await storage.getItem('loca:test').catch(() => {});
|
||||
await storage.getItem('not-local:test').catch(() => {});
|
||||
});
|
||||
|
||||
it('should throw an error when using an invalid storage area', async () => {
|
||||
@@ -949,6 +952,7 @@ describe('Storage Utils', () => {
|
||||
{ migratedValue: expect.any(Number) },
|
||||
);
|
||||
});
|
||||
|
||||
it('should not print migration logs if debug option is undefined or false', async () => {
|
||||
await fakeBrowser.storage.local.set({
|
||||
count: 2,
|
||||
@@ -980,6 +984,62 @@ describe('Storage Utils', () => {
|
||||
await waitForMigrations();
|
||||
expect(consoleSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
describe('calling setValue', () => {
|
||||
const migrateToV2 = vi.fn((v1) => v1);
|
||||
const defineTestItem = () =>
|
||||
storage.defineItem<number>('local:count', {
|
||||
version: 2,
|
||||
migrations: {
|
||||
2: migrateToV2,
|
||||
},
|
||||
});
|
||||
|
||||
it('should set the version metadata when setting the value for the first time', async () => {
|
||||
const item = defineTestItem();
|
||||
|
||||
expect(await item.getValue()).toBeNull();
|
||||
expect(await item.getMeta()).toEqual({});
|
||||
expect(fakeBrowser.storage.local.set).toBeCalledTimes(0);
|
||||
|
||||
await item.setValue(1);
|
||||
|
||||
expect(await item.getValue()).toBe(1);
|
||||
expect(await item.getMeta()).toEqual({ v: 2 });
|
||||
// Called twice, once for setting the value, once for updating the metadata.
|
||||
expect(fakeBrowser.storage.local.set).toBeCalledTimes(2);
|
||||
expect(fakeBrowser.storage.local.set).toBeCalledWith({ count: 1 });
|
||||
expect(fakeBrowser.storage.local.set).toBeCalledWith({
|
||||
count$: { v: 2 },
|
||||
});
|
||||
|
||||
await item.setValue(2);
|
||||
expect(await item.getValue()).toBe(2);
|
||||
expect(await item.getMeta()).toEqual({ v: 2 });
|
||||
// Only called one more time, just for setting the value
|
||||
expect(fakeBrowser.storage.local.set).toBeCalledTimes(3);
|
||||
expect(fakeBrowser.storage.local.set).toBeCalledWith({ count: 2 });
|
||||
|
||||
// Migration function never called throughout the whole test
|
||||
expect(migrateToV2).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should not set the version metadata when a value is already in storage', async () => {
|
||||
await fakeBrowser.storage.local.set({ count: 1, count$: { v: 2 } });
|
||||
vi.mocked(fakeBrowser.storage.local.set).mockClear();
|
||||
|
||||
const item = defineTestItem();
|
||||
await item.setValue(2);
|
||||
|
||||
expect(await item.getValue()).toEqual(2);
|
||||
expect(await item.getMeta()).toEqual({ v: 2 });
|
||||
|
||||
expect(fakeBrowser.storage.local.set).toBeCalledTimes(1);
|
||||
expect(fakeBrowser.storage.local.set).toBeCalledWith({ count: 2 });
|
||||
|
||||
expect(migrateToV2).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getValue', () => {
|
||||
|
||||
+84
-103
@@ -1,12 +1,14 @@
|
||||
/**
|
||||
* Simplified storage APIs with support for versioned fields, snapshots, metadata, and item definitions.
|
||||
* Simplified storage APIs with support for versioned fields, snapshots,
|
||||
* metadata, and item definitions.
|
||||
*
|
||||
* See [the guide](https://wxt.dev/storage.html) for more information.
|
||||
*
|
||||
* @module @wxt-dev/storage
|
||||
*/
|
||||
import { dequal } from 'dequal/lite';
|
||||
import { Mutex } from 'async-mutex';
|
||||
import { browser, type Browser } from '@wxt-dev/browser';
|
||||
import { Mutex } from 'async-mutex';
|
||||
import { dequal } from 'dequal/lite';
|
||||
|
||||
export const storage = createStorage();
|
||||
|
||||
@@ -409,12 +411,18 @@ function createStorage(): WxtStorage {
|
||||
);
|
||||
}
|
||||
|
||||
let needsVersionSet = false;
|
||||
|
||||
const migrate: WxtStorageItem<any, any>['migrate'] = async () => {
|
||||
const driverMetaKey = getMetaKey(driverKey);
|
||||
const [{ value }, { value: meta }] = await driver.getItems([
|
||||
driverKey,
|
||||
driverMetaKey,
|
||||
]);
|
||||
|
||||
// Used in setValue to also set the version when needed
|
||||
needsVersionSet = value == null && meta?.v == null && !!targetVersion;
|
||||
|
||||
if (value == null) return;
|
||||
|
||||
const currentVersion = meta?.v ?? 1;
|
||||
@@ -529,7 +537,18 @@ function createStorage(): WxtStorage {
|
||||
setValue: async (value) => {
|
||||
await migrationsDone;
|
||||
|
||||
return await setItem(driver, driverKey, value);
|
||||
if (needsVersionSet) {
|
||||
needsVersionSet = false;
|
||||
await Promise.all([
|
||||
// Note: These calls cannot be done in a single `setItems` call;
|
||||
// metadata needs to be merged together with existing data and
|
||||
// setItems overwrites the whole value without merging.
|
||||
setItem(driver, driverKey, value),
|
||||
setMeta(driver, driverKey, { v: targetVersion }),
|
||||
]);
|
||||
} else {
|
||||
await setItem(driver, driverKey, value);
|
||||
}
|
||||
},
|
||||
|
||||
setMeta: async (properties) => {
|
||||
@@ -672,7 +691,7 @@ export interface WxtStorage {
|
||||
* Get an item from storage, or return `null` if it doesn't exist.
|
||||
*
|
||||
* @example
|
||||
* await storage.getItem<number>("local:installDate");
|
||||
* await storage.getItem<number>('local:installDate');
|
||||
*/
|
||||
getItem<TValue>(
|
||||
key: StorageItemKey,
|
||||
@@ -685,11 +704,11 @@ export interface WxtStorage {
|
||||
): Promise<TValue | null>;
|
||||
|
||||
/**
|
||||
* Get multiple items from storage. The return order is guaranteed to be the same as the order
|
||||
* requested.
|
||||
* Get multiple items from storage. The return order is guaranteed to be the
|
||||
* same as the order requested.
|
||||
*
|
||||
* @example
|
||||
* await storage.getItems(["local:installDate", "session:someCounter"]);
|
||||
* await storage.getItems(['local:installDate', 'session:someCounter']);
|
||||
*/
|
||||
getItems(
|
||||
keys: Array<
|
||||
@@ -700,11 +719,11 @@ export interface WxtStorage {
|
||||
): Promise<Array<{ key: StorageItemKey; value: any }>>;
|
||||
|
||||
/**
|
||||
* Return an object containing metadata about the key. Object is stored at `key + "$"`. If value
|
||||
* is not an object, it returns an empty object.
|
||||
* Return an object containing metadata about the key. Object is stored at
|
||||
* `key + "$"`. If value is not an object, it returns an empty object.
|
||||
*
|
||||
* @example
|
||||
* await storage.getMeta("local:installDate");
|
||||
* await storage.getMeta('local:installDate');
|
||||
*/
|
||||
getMeta<T extends Record<string, unknown>>(key: StorageItemKey): Promise<T>;
|
||||
|
||||
@@ -719,22 +738,23 @@ export interface WxtStorage {
|
||||
): Promise<Array<{ key: StorageItemKey; meta: any }>>;
|
||||
|
||||
/**
|
||||
* Set a value in storage. Setting a value to `null` or `undefined` is equivalent to calling
|
||||
* `removeItem`.
|
||||
* Set a value in storage. Setting a value to `null` or `undefined` is
|
||||
* equivalent to calling `removeItem`.
|
||||
*
|
||||
* @example
|
||||
* await storage.setItem<number>("local:installDate", Date.now());
|
||||
* await storage.setItem<number>('local:installDate', Date.now());
|
||||
*/
|
||||
setItem<T>(key: StorageItemKey, value: T | null): Promise<void>;
|
||||
|
||||
/**
|
||||
* Set multiple values in storage. If a value is set to `null` or `undefined`, the key is removed.
|
||||
* Set multiple values in storage. If a value is set to `null` or `undefined`,
|
||||
* the key is removed.
|
||||
*
|
||||
* @example
|
||||
* await storage.setItem([
|
||||
* await storage.setItem([
|
||||
* { key: "local:installDate", value: Date.now() },
|
||||
* { key: "session:someCounter, value: 5 },
|
||||
* ]);
|
||||
* ]);
|
||||
*/
|
||||
setItems(
|
||||
values: Array<
|
||||
@@ -744,11 +764,11 @@ export interface WxtStorage {
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Sets metadata properties. If some properties are already set, but are not included in the
|
||||
* `properties` parameter, they will not be removed.
|
||||
* Sets metadata properties. If some properties are already set, but are not
|
||||
* included in the `properties` parameter, they will not be removed.
|
||||
*
|
||||
* @example
|
||||
* await storage.setMeta("local:installDate", { appVersion });
|
||||
* await storage.setMeta('local:installDate', { appVersion });
|
||||
*/
|
||||
setMeta<T extends Record<string, unknown>>(
|
||||
key: StorageItemKey,
|
||||
@@ -771,13 +791,11 @@ export interface WxtStorage {
|
||||
* Removes an item from storage.
|
||||
*
|
||||
* @example
|
||||
* await storage.removeItem("local:installDate");
|
||||
* await storage.removeItem('local:installDate');
|
||||
*/
|
||||
removeItem(key: StorageItemKey, opts?: RemoveItemOptions): Promise<void>;
|
||||
|
||||
/**
|
||||
* Remove a list of keys from storage.
|
||||
*/
|
||||
/** Remove a list of keys from storage. */
|
||||
removeItems(
|
||||
keys: Array<
|
||||
| StorageItemKey
|
||||
@@ -787,48 +805,41 @@ export interface WxtStorage {
|
||||
>,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Removes all items from the provided storage area.
|
||||
*/
|
||||
/** Removes all items from the provided storage area. */
|
||||
clear(base: StorageArea): Promise<void>;
|
||||
|
||||
/**
|
||||
* Remove the entire metadata for a key, or specific properties by name.
|
||||
*
|
||||
* @example
|
||||
* // Remove all metadata properties from the item
|
||||
* await storage.removeMeta("local:installDate");
|
||||
* // Remove all metadata properties from the item
|
||||
* await storage.removeMeta('local:installDate');
|
||||
*
|
||||
* // Remove only specific the "v" field
|
||||
* await storage.removeMeta("local:installDate", "v")
|
||||
* // Remove only specific the "v" field
|
||||
* await storage.removeMeta('local:installDate', 'v');
|
||||
*/
|
||||
removeMeta(
|
||||
key: StorageItemKey,
|
||||
properties?: string | string[],
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Return all the items in storage.
|
||||
*/
|
||||
/** Return all the items in storage. */
|
||||
snapshot(
|
||||
base: StorageArea,
|
||||
opts?: SnapshotOptions,
|
||||
): Promise<Record<string, unknown>>;
|
||||
|
||||
/**
|
||||
* Restores the results of `snapshot`. If new properties have been saved since the snapshot, they are
|
||||
* not overridden. Only values existing in the snapshot are overridden.
|
||||
* Restores the results of `snapshot`. If new properties have been saved since
|
||||
* the snapshot, they are not overridden. Only values existing in the snapshot
|
||||
* are overridden.
|
||||
*/
|
||||
restoreSnapshot(base: StorageArea, data: any): Promise<void>;
|
||||
|
||||
/**
|
||||
* Watch for changes to a specific key in storage.
|
||||
*/
|
||||
/** Watch for changes to a specific key in storage. */
|
||||
watch<T>(key: StorageItemKey, cb: WatchCallback<T | null>): Unwatch;
|
||||
|
||||
/**
|
||||
* Remove all watch listeners.
|
||||
*/
|
||||
/** Remove all watch listeners. */
|
||||
unwatch(): void;
|
||||
|
||||
/**
|
||||
@@ -877,61 +888,42 @@ export interface WxtStorageItem<
|
||||
TValue,
|
||||
TMetadata extends Record<string, unknown>,
|
||||
> {
|
||||
/**
|
||||
* The storage key passed when creating the storage item.
|
||||
*/
|
||||
/** The storage key passed when creating the storage item. */
|
||||
key: StorageItemKey;
|
||||
|
||||
/**
|
||||
* @deprecated Renamed to fallback, use it instead.
|
||||
*/
|
||||
/** @deprecated Renamed to fallback, use it instead. */
|
||||
defaultValue: TValue;
|
||||
|
||||
/**
|
||||
* The value provided by the `fallback` option.
|
||||
*/
|
||||
/** The value provided by the `fallback` option. */
|
||||
fallback: TValue;
|
||||
|
||||
/**
|
||||
* Get the latest value from storage.
|
||||
*/
|
||||
/** Get the latest value from storage. */
|
||||
getValue(): Promise<TValue>;
|
||||
|
||||
/**
|
||||
* Get metadata.
|
||||
*/
|
||||
/** Get metadata. */
|
||||
getMeta(): Promise<NullablePartial<TMetadata>>;
|
||||
|
||||
/**
|
||||
* Set the value in storage.
|
||||
*/
|
||||
/** Set the value in storage. */
|
||||
setValue(value: TValue): Promise<void>;
|
||||
|
||||
/**
|
||||
* Set metadata properties.
|
||||
*/
|
||||
/** Set metadata properties. */
|
||||
setMeta(properties: NullablePartial<TMetadata>): Promise<void>;
|
||||
|
||||
/**
|
||||
* Remove the value from storage.
|
||||
*/
|
||||
/** Remove the value from storage. */
|
||||
removeValue(opts?: RemoveItemOptions): Promise<void>;
|
||||
|
||||
/**
|
||||
* Remove all metadata or certain properties from metadata.
|
||||
*/
|
||||
/** Remove all metadata or certain properties from metadata. */
|
||||
removeMeta(properties?: string[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Listen for changes to the value in storage.
|
||||
*/
|
||||
/** Listen for changes to the value in storage. */
|
||||
watch(cb: WatchCallback<TValue>): Unwatch;
|
||||
|
||||
/**
|
||||
* If there are migrations defined on the storage item, migrate to the latest version.
|
||||
* If there are migrations defined on the storage item, migrate to the latest
|
||||
* version.
|
||||
*
|
||||
* **This function is ran automatically whenever the extension updates**, so you don't have to call it
|
||||
* manually.
|
||||
* **This function is ran automatically whenever the extension updates**, so
|
||||
* you don't have to call it manually.
|
||||
*/
|
||||
migrate(): Promise<void>;
|
||||
}
|
||||
@@ -940,14 +932,10 @@ export type StorageArea = 'local' | 'session' | 'sync' | 'managed';
|
||||
export type StorageItemKey = `${StorageArea}:${string}`;
|
||||
|
||||
export interface GetItemOptions<T> {
|
||||
/**
|
||||
* @deprecated Renamed to `fallback`, use it instead.
|
||||
*/
|
||||
/** @deprecated Renamed to `fallback`, use it instead. */
|
||||
defaultValue?: T;
|
||||
|
||||
/**
|
||||
* Default value returned when `getItem` would otherwise return `null`.
|
||||
*/
|
||||
/** Default value returned when `getItem` would otherwise return `null`. */
|
||||
fallback?: T;
|
||||
}
|
||||
|
||||
@@ -962,21 +950,17 @@ export interface RemoveItemOptions {
|
||||
|
||||
export interface SnapshotOptions {
|
||||
/**
|
||||
* Exclude a list of keys. The storage area prefix should be removed since the snapshot is for a
|
||||
* specific storage area already.
|
||||
* Exclude a list of keys. The storage area prefix should be removed since the
|
||||
* snapshot is for a specific storage area already.
|
||||
*/
|
||||
excludeKeys?: string[];
|
||||
}
|
||||
|
||||
export interface WxtStorageItemOptions<T> {
|
||||
/**
|
||||
* @deprecated Renamed to `fallback`, use it instead.
|
||||
*/
|
||||
/** @deprecated Renamed to `fallback`, use it instead. */
|
||||
defaultValue?: T;
|
||||
|
||||
/**
|
||||
* Default value returned when `getValue` would otherwise return `null`.
|
||||
*/
|
||||
/** Default value returned when `getValue` would otherwise return `null`. */
|
||||
fallback?: T;
|
||||
|
||||
/**
|
||||
@@ -988,25 +972,26 @@ export interface WxtStorageItemOptions<T> {
|
||||
init?: () => T | Promise<T>;
|
||||
|
||||
/**
|
||||
* Provide a version number for the storage item to enable migrations. When changing the version
|
||||
* in the future, migration functions will be ran on application startup.
|
||||
* Provide a version number for the storage item to enable migrations. When
|
||||
* changing the version in the future, migration functions will be ran on
|
||||
* application startup.
|
||||
*/
|
||||
version?: number;
|
||||
|
||||
/**
|
||||
* A map of version numbers to the functions used to migrate the data to that version.
|
||||
* A map of version numbers to the functions used to migrate the data to that
|
||||
* version.
|
||||
*/
|
||||
migrations?: Record<number, (oldValue: any) => any>;
|
||||
|
||||
/**
|
||||
* Print debug logs, such as migration process.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
debug?: boolean;
|
||||
|
||||
/**
|
||||
* A callback function that runs on migration complete.
|
||||
*/
|
||||
/** A callback function that runs on migration complete. */
|
||||
onMigrationComplete?: (migratedValue: T, targetVersion: number) => void;
|
||||
}
|
||||
|
||||
@@ -1015,21 +1000,17 @@ export type StorageAreaChanges = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as `Partial`, but includes `| null`. It makes all the properties of an object optional and
|
||||
* nullable.
|
||||
* Same as `Partial`, but includes `| null`. It makes all the properties of an
|
||||
* object optional and nullable.
|
||||
*/
|
||||
type NullablePartial<T> = {
|
||||
[key in keyof T]+?: T[key] | undefined | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback called when a value in storage is changed.
|
||||
*/
|
||||
/** Callback called when a value in storage is changed. */
|
||||
export type WatchCallback<T> = (newValue: T, oldValue: T) => void;
|
||||
|
||||
/**
|
||||
* Call to remove a watch listener
|
||||
*/
|
||||
/** Call to remove a watch listener */
|
||||
export type Unwatch = () => void;
|
||||
|
||||
export class MigrationError extends Error {
|
||||
|
||||
@@ -10,7 +10,7 @@ Install the package:
|
||||
npm i --save-dev @wxt-dev/unocss unocss
|
||||
pnpm i -D @wxt-dev/unocss unocss
|
||||
yarn add --dev @wxt-dev/unocss unocss
|
||||
bun i -D @wxt-dev/unocss unocss
|
||||
bun add -D @wxt-dev/unocss unocss
|
||||
```
|
||||
|
||||
Add the module to `wxt.config.ts`:
|
||||
|
||||
@@ -33,21 +33,26 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "buildc --deps-only -- check"
|
||||
"check": "buildc --deps-only -- check",
|
||||
"test": "echo 'noop'",
|
||||
"test:coverage": "echo 'noop'",
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"unocss": ">=0.60.0",
|
||||
"wxt": ">=0.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"oxlint": "^1.43.0",
|
||||
"publint": "^0.3.17",
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"unocss": "^0.64.0 || ^0.65.0 || ^65.0.0 || ^66.0.0",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"defu": "^6.1.4",
|
||||
"fast-glob": "^3.3.3"
|
||||
"tinyglobby": "^0.2.15"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,12 +38,11 @@ export default defineWxtModule<UnoCSSOptions>({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Options for the UnoCSS module
|
||||
*/
|
||||
/** Options for the UnoCSS module */
|
||||
export interface UnoCSSOptions<Theme extends object = object> {
|
||||
/**
|
||||
* Enable UnoCSS
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
enabled?: boolean;
|
||||
@@ -51,12 +50,17 @@ export interface UnoCSSOptions<Theme extends object = object> {
|
||||
* List of entrypoint names that UnoCSS is not used in. By default, the UnoCSS
|
||||
* vite plugin is added to all build steps, but this option is used to exclude
|
||||
* it from specific builds.
|
||||
* @example ["popup", "options"]
|
||||
* @default []
|
||||
*
|
||||
* @example
|
||||
* {undefined} ('popup',
|
||||
* 'options');
|
||||
*
|
||||
* @default [ ]
|
||||
*/
|
||||
excludeEntrypoints?: string[];
|
||||
/**
|
||||
* The path to your `unocss.config.ts` file, relative to <rootDir>, or inline configuration.
|
||||
* The path to your `unocss.config.ts` file, relative to <rootDir>, or inline
|
||||
* configuration.
|
||||
*/
|
||||
configOrPath?: Parameters<typeof UnoCSS<Theme>>[0];
|
||||
}
|
||||
|
||||
@@ -36,17 +36,22 @@
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "wxt",
|
||||
"check": "pnpm build && check",
|
||||
"check": "bun run build && check",
|
||||
"build": "buildc -- tsdown",
|
||||
"prepare": "buildc --deps-only -- wxt prepare"
|
||||
"postinstall": "buildc --deps-only -- wxt prepare",
|
||||
"test": "echo 'noop'",
|
||||
"test:coverage": "echo 'noop'",
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"webextension-polyfill": "*",
|
||||
"wxt": ">=0.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/webextension-polyfill": "^0.12.4",
|
||||
"publint": "^0.3.17",
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@types/webextension-polyfill": "^0.12.5",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"webextension-polyfill": "^0.12.0",
|
||||
"wxt": "workspace:*"
|
||||
|
||||
@@ -5,13 +5,16 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "buildc --deps-only -- wxt",
|
||||
"dev:firefox": "buildc --deps-only -- wxt -b firefox",
|
||||
"dev:firefox-mv3": "buildc --deps-only -- wxt -b firefox --mv3",
|
||||
"build": "buildc --deps-only -- wxt build",
|
||||
"build:all": "buildc --deps-only -- pnpm run --reporter-hide-prefix /^build:all:.*/",
|
||||
"build:all:chrome-mv3": "wxt build",
|
||||
"build:all:chrome-mv2": "wxt build --mv2",
|
||||
"build:all:firefox-mv3": "wxt build -b firefox --mv3",
|
||||
"build:all:firefox-mv2": "wxt build -b firefox",
|
||||
"build:all": "buildc --deps-only -- bun run --sequential 'build:*-*'",
|
||||
"build:chrome-mv3": "wxt build",
|
||||
"build:chrome-mv2": "wxt build --mv2",
|
||||
"build:firefox-mv3": "wxt build -b firefox --mv3",
|
||||
"build:firefox-mv2": "wxt build -b firefox",
|
||||
"test": "buildc --deps-only -- vitest",
|
||||
"test:coverage": "bun run test run --coverage",
|
||||
"zip": "buildc --deps-only -- wxt zip",
|
||||
"check": "buildc --deps-only -- check",
|
||||
"postinstall": "buildc --deps-only -- wxt prepare"
|
||||
@@ -22,7 +25,9 @@
|
||||
"react-dom": "^19.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.13",
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@aklinker1/check": "^2.2.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@wxt-dev/auto-icons": "workspace:*",
|
||||
"@wxt-dev/unocss": "workspace:*",
|
||||
|
||||
@@ -12,7 +12,7 @@ export default defineBackground({
|
||||
manifestVersion: import.meta.env.MANIFEST_VERSION,
|
||||
});
|
||||
|
||||
console.log(useAppConfig());
|
||||
console.log(getAppConfig());
|
||||
|
||||
browser.runtime.getURL('/');
|
||||
browser.runtime.getURL('/background.js');
|
||||
@@ -43,3 +43,14 @@ export default defineBackground({
|
||||
storage.setItem('session:startTime', Date.now());
|
||||
},
|
||||
});
|
||||
|
||||
function _otherTypeChecksNotEvaluated() {
|
||||
browser.scripting.executeScript({
|
||||
target: { tabId: 1 },
|
||||
files: [
|
||||
'/background.js',
|
||||
// @ts-expect-error: Should error for non-existing paths
|
||||
'/other.js',
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export default defineContentScript({
|
||||
matches: ['*://*.example.com/*'],
|
||||
|
||||
async main() {
|
||||
console.log('Injecting...');
|
||||
await injectScript('/unlisted.js', {
|
||||
keepInDom: true,
|
||||
});
|
||||
console.log('After injection');
|
||||
},
|
||||
});
|
||||
@@ -1,3 +1,3 @@
|
||||
export default defineUnlistedScript(() => {
|
||||
console.log('injected');
|
||||
console.log('Injected');
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ export default defineConfig({
|
||||
default_locale: 'en',
|
||||
web_accessible_resources: [
|
||||
{
|
||||
resources: ['/iframe-src.html'],
|
||||
resources: ['iframe-src.html', 'unlisted.js'],
|
||||
matches: ['*://*.google.com/*'],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,5 +1,201 @@
|
||||
# Changelog
|
||||
|
||||
## v0.20.21
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.20...wxt-v0.20.21)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Replace `dotenv` with native `node:util` `parseEnv` ([#2181](https://github.com/wxt-dev/wxt/pull/2181))
|
||||
- Type `/_favicon/` paths when `favicon` permission is declared ([#2241](https://github.com/wxt-dev/wxt/pull/2241))
|
||||
- Auto-discover Firefox `theme_icons` from public assets ([#2242](https://github.com/wxt-dev/wxt/pull/2242))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Add `engines` field to WXT's `package.json` ([ce551fbd](https://github.com/wxt-dev/wxt/commit/ce551fbd))
|
||||
- Output content scripts in ascii to avoid utf8 encoding errors ([#2204](https://github.com/wxt-dev/wxt/pull/2204))
|
||||
- Resolve `url:` imports in Vite 8 ([#2218](https://github.com/wxt-dev/wxt/pull/2218))
|
||||
- Support template literals in prepare:publicPaths ([#2248](https://github.com/wxt-dev/wxt/pull/2248))
|
||||
- Support `page_action` for Firefox MV3 ([#2200](https://github.com/wxt-dev/wxt/pull/2200))
|
||||
- Add Firefox action manifest typings ([#2217](https://github.com/wxt-dev/wxt/pull/2217))
|
||||
|
||||
### 💅 Refactors
|
||||
|
||||
- Rename internal popup `mv2Key` option to `actionType` ([#2249](https://github.com/wxt-dev/wxt/pull/2249))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Document augmenting the `browser` type for Firefox ([#2199](https://github.com/wxt-dev/wxt/pull/2199))
|
||||
- Add note about adding the browser package as a dependency for module augmentation ([41a687a6](https://github.com/wxt-dev/wxt/commit/41a687a6))
|
||||
- Simplify storage docs ([5376cfd6](https://github.com/wxt-dev/wxt/commit/5376cfd6))
|
||||
- Add Artemis II photo to hero section ([70fdb04d](https://github.com/wxt-dev/wxt/commit/70fdb04d))
|
||||
- Fix undocumented constants link from 0.14.0 -> 0.15.0 migration guide ([#2244](https://github.com/wxt-dev/wxt/pull/2244))
|
||||
- Fix deploy command after bun upgrade ([d7cc8dc2](https://github.com/wxt-dev/wxt/commit/d7cc8dc2))
|
||||
- Fix non-replaced `{{version}}` in CLI reference ([799bd92b](https://github.com/wxt-dev/wxt/commit/799bd92b))
|
||||
- Add Firefox Add-on support to homepage showcase ([#2208](https://github.com/wxt-dev/wxt/pull/2208))
|
||||
- Add 5 Whiteboard Works extensions to the homepage ([#2207](https://github.com/wxt-dev/wxt/pull/2207))
|
||||
- Added "SoundCloud Enhanced Pro" to the homepage ([#2238](https://github.com/wxt-dev/wxt/pull/2238))
|
||||
- Add AlarmBot extension to the list of extensions ([#2251](https://github.com/wxt-dev/wxt/pull/2251))
|
||||
- Move homepage extensions to separate YAML file ([#2255](https://github.com/wxt-dev/wxt/pull/2255))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Replace minimatch with picomatch ([#2188](https://github.com/wxt-dev/wxt/pull/2188))
|
||||
- Add cspell and fix all typos ([6621aaf8](https://github.com/wxt-dev/wxt/commit/6621aaf8))
|
||||
- Use `deps.neverBundle` instead of `external` ([8f71e174](https://github.com/wxt-dev/wxt/commit/8f71e174))
|
||||
- Replace `picocolors` with builtins ([#2210](https://github.com/wxt-dev/wxt/pull/2210))
|
||||
- **deps-dev:** Bump eslint from 10.0.2 to 10.1.0 ([#2224](https://github.com/wxt-dev/wxt/pull/2224))
|
||||
- **deps:** Bump filesize from 11.0.13 to 11.0.15 ([#2226](https://github.com/wxt-dev/wxt/pull/2226))
|
||||
- **deps:** Bump hookable from 6.0.1 to 6.1.0 ([#2222](https://github.com/wxt-dev/wxt/pull/2222))
|
||||
- Migrate monorepo to use Bun instead of PNPM ([#2009](https://github.com/wxt-dev/wxt/pull/2009))
|
||||
- Use `bun run --sequential` to simplify package scripts ([#2252](https://github.com/wxt-dev/wxt/pull/2252))
|
||||
- Fix flakey test ([#2254](https://github.com/wxt-dev/wxt/pull/2254))
|
||||
|
||||
### 🤖 CI
|
||||
|
||||
- Enable bun package manager tests on windows ([#2253](https://github.com/wxt-dev/wxt/pull/2253))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Kamer DINC <kamerdinc@gmail.com>
|
||||
- Sullivan ([@Epic-R-R](https://github.com/Epic-R-R))
|
||||
- Skylar Bolton <skylar.bolton@gmail.com>
|
||||
- Vaughn Bosu ([@VaughnBosu](https://github.com/VaughnBosu))
|
||||
- SmAsHeD ([@smashedr](https://github.com/smashedr))
|
||||
- Eupthere ([@eupthere](https://github.com/eupthere))
|
||||
- Ofer Itzhaki <ofer.webdev@gmail.com>
|
||||
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
|
||||
- Alexharding-ux ([@alexharding-ux](https://github.com/alexharding-ux))
|
||||
- Harryson ([@HarrysonLadines](https://github.com/HarrysonLadines))
|
||||
- Aditi ([@Adiii-15](https://github.com/Adiii-15))
|
||||
|
||||
## v0.20.20
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.19...wxt-v0.20.20)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Unlisted script return values broken with Vite 8 sourcemaps ([#2197](https://github.com/wxt-dev/wxt/pull/2197))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Add Dymo extension ID to UsingWxtSection.vue ([#2187](https://github.com/wxt-dev/wxt/pull/2187))
|
||||
- Add Extension Rank Checker to the list of extensions ([#2193](https://github.com/wxt-dev/wxt/pull/2193))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps:** Add vite-node 6 support ([64b68671](https://github.com/wxt-dev/wxt/commit/64b68671))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Joseph Hu ([@KiJO94GO](https://github.com/KiJO94GO))
|
||||
- FJRG2007 ツ ([@FJRG2007](https://github.com/FJRG2007))
|
||||
- Hampus Tågerud ([@hampustagerud](https://github.com/hampustagerud))
|
||||
|
||||
## v0.20.19
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.18...wxt-v0.20.19)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Vite 8 support ([bfd4af5d](https://github.com/wxt-dev/wxt/commit/bfd4af5d))
|
||||
|
||||
### 🔥 Performance
|
||||
|
||||
- Use filter to improve plugin performance with rolldown ([#1787](https://github.com/wxt-dev/wxt/pull/1787))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- List eslint 10 as supported ([55dc263d](https://github.com/wxt-dev/wxt/commit/55dc263d))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Added "Scrape Similar" to the homepage ([#2158](https://github.com/wxt-dev/wxt/pull/2158))
|
||||
- Added "isTrust" to the homepage ([#2161](https://github.com/wxt-dev/wxt/pull/2161))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Add `prettier-plugin-jsdoc` to project ([#2171](https://github.com/wxt-dev/wxt/pull/2171))
|
||||
- Replace fast-glob and ora with lighter alternatives ([#2173](https://github.com/wxt-dev/wxt/pull/2173))
|
||||
- **deps:** Upgrade deps ([#2175](https://github.com/wxt-dev/wxt/pull/2175))
|
||||
- Replace `fs-extra` with `node:fs/promises` ([#2174](https://github.com/wxt-dev/wxt/pull/2174))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Florian Metz ([@Timeraa](https://github.com/Timeraa))
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
|
||||
- Etoome ([@etoome](https://github.com/etoome))
|
||||
- Kuba ([@zizzfizzix](https://github.com/zizzfizzix))
|
||||
|
||||
## v0.20.18
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.17...wxt-v0.20.18)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Create tests for all func of `network.ts` ([#2132](https://github.com/wxt-dev/wxt/pull/2132))
|
||||
- Use Navigation API for location change detection with polling fallback ([#2136](https://github.com/wxt-dev/wxt/pull/2136))
|
||||
- **modules:** Add support for augumenting entrypoint options ([#2149](https://github.com/wxt-dev/wxt/pull/2149))
|
||||
- New `@wxt-dev/is-background` package ([#2152](https://github.com/wxt-dev/wxt/pull/2152))
|
||||
- Add `globalName` entrypoint option. ([#2017](https://github.com/wxt-dev/wxt/pull/2017))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Add `getAppConfig` as an alias to `useAppConfig` ([#2144](https://github.com/wxt-dev/wxt/pull/2144))
|
||||
- Enable mv3 dev mode with firefox 147 ([#2135](https://github.com/wxt-dev/wxt/pull/2135))
|
||||
- **types:** Add type safety to `browser.runtime.executeScript` `files` option ([#2142](https://github.com/wxt-dev/wxt/pull/2142))
|
||||
- **types:** Include CSS entrypoints in PublicPath generation ([#2150](https://github.com/wxt-dev/wxt/pull/2150))
|
||||
- Fix submit command ([#2157](https://github.com/wxt-dev/wxt/pull/2157))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Added "Glossy New Tab" to the homepage ([#2133](https://github.com/wxt-dev/wxt/pull/2133))
|
||||
- Add All API Hub to showcase extensions ([#2137](https://github.com/wxt-dev/wxt/pull/2137))
|
||||
- Add `wxt-local-analytics` to community resources ([#2141](https://github.com/wxt-dev/wxt/pull/2141))
|
||||
- Add deep outline to modules page to show recipes ([653608c9](https://github.com/wxt-dev/wxt/commit/653608c9))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Add missing `async` to network.ts ([#2107](https://github.com/wxt-dev/wxt/pull/2107))
|
||||
- Make type for `__ENTRYPOINT__` of vitest.globalSetup.ts ([#2128](https://github.com/wxt-dev/wxt/pull/2128))
|
||||
- Remove unnecessary `@ts-ignore` of `builders/vite` index.ts ([#2127](https://github.com/wxt-dev/wxt/pull/2127))
|
||||
- Rename `zipdir` to `zipDir` ([#2126](https://github.com/wxt-dev/wxt/pull/2126))
|
||||
- Simplify imports in `wxt/e2e` ([#2122](https://github.com/wxt-dev/wxt/pull/2122))
|
||||
- Remove `@ts-expect-error` from manifest.test.ts and fix typo ([#2123](https://github.com/wxt-dev/wxt/pull/2123))
|
||||
|
||||
### 🤖 CI
|
||||
|
||||
- Fix VHS action ([#2155](https://github.com/wxt-dev/wxt/pull/2155))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Jonathan Viney ([@jviney](https://github.com/jviney))
|
||||
- Tam Dang ([@dahomita](https://github.com/dahomita))
|
||||
- Nick Doan ([@nickbar01234](https://github.com/nickbar01234))
|
||||
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
|
||||
- Anthony Ciccarello ([@aciccarello](https://github.com/aciccarello))
|
||||
- Henrik Wenz ([@HaNdTriX](https://github.com/HaNdTriX))
|
||||
- Sheng Zhang ([@Arktomson](https://github.com/Arktomson))
|
||||
- Oleksii ([@Aler1x](https://github.com/Aler1x))
|
||||
- Qixing-jk <vq3d5d8c@duck.com>
|
||||
- Muzammil ([@oyzamil](https://github.com/oyzamil))
|
||||
|
||||
## v0.20.17
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.16...wxt-v0.20.17)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Update left-over CJS require of `publish-browser-extension` to ESM ([#2120](https://github.com/wxt-dev/wxt/pull/2120))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v0.20.16
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.15...wxt-v0.20.16)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user