Compare commits

..

7 Commits

Author SHA1 Message Date
GitHub Actions 6fbfc15be7 chore(release): v0.17.3 2024-02-24 16:43:31 +00:00
Aaron c793d3cf6b fix(storage): Revert #478 and run migrations when item is defined and properly wait for migrations before allowing read/writes (#487) 2024-02-24 10:40:03 -06:00
Aaron Klinker b5f4d8cc1b feat(storage): Guarantee storage.getItems returns values in the same order as requested 2024-02-24 08:56:45 -06:00
Aaron Klinker 77e6d1f5a4 fix: Content scripts crash when using storage.defineItem 2024-02-24 08:23:12 -06:00
GitHub Actions 3f149ac4fa chore(release): v0.17.2 2024-02-23 21:49:55 +00:00
Aaron c0dbffaa82 fix: Don't use sub-dependency binaries directly (#482) 2024-02-23 15:45:18 -06:00
Aaron Klinker 707f03a3eb Update changelog 2024-02-23 12:55:55 -06:00
14 changed files with 136 additions and 154 deletions
+22 -1
View File
@@ -1,5 +1,26 @@
# Changelog
## v0.17.3
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.2...v0.17.3)
### 🚀 Enhancements
- **storage:** Guarantee `storage.getItems` returns values in the same order as requested ([b5f4d8c](https://github.com/wxt-dev/wxt/commit/b5f4d8c))
### 🩹 Fixes
- Content scripts crash when using `storage.defineItem` ([77e6d1f](https://github.com/wxt-dev/wxt/commit/77e6d1f))
- **storage:** Revert #478 and run migrations when item is defined and properly wait for migrations before allowing read/writes ([#487](https://github.com/wxt-dev/wxt/pull/487), [#478](https://github.com/wxt-dev/wxt/issues/478))
## v0.17.2
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.1...v0.17.2)
### 🩹 Fixes
- Don't use sub-dependency binaries directly ([#482](https://github.com/wxt-dev/wxt/pull/482))
## v0.17.1
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.0...v0.17.1)
@@ -14,7 +35,7 @@
### ❤️ Contributors
- Leo Shklovskii <leo@thermopylae.net>
- Leo Shklovskii ([@leos](https://github.com/leos))
## v0.17.0
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env node
/**
* A alias around `publish-extension` that is always installed on the path without having to install
* `publish-browser-extension` as a direct dependency (like for PNPM, which doesn't link
* sub-dependency binaries to "node_modules/.bin")
*/
require('publish-browser-extension/cli');
+1 -24
View File
@@ -263,27 +263,4 @@ export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV2[]>( // [!code
### Running Migrations
To run migrations, you have two options:
1. Import all versioned storage items into your background script and they will run automatically whenever your extension updates
2. Manually call `item.migrate()`
The first approach is recommended. To make importing all your storage items easy, you can define all of them in a single file, `utils/storage.ts`, and import that file into your background entrypoint:
```ts
// utils/storage.ts
export countStorage = storage.defineItem(...);
export themeStorage = storage.defineItem(...);
export someOtherStorage = storage.defineItem(...);
```
```ts
// entrypoints/background.ts
import '@/utils/storage'; // This import runs migrations on updates
export default defineBackground({
// ...
});
```
> When you call `storage.defineItem`, a `browser.runtime.onInstalled` listener is added. `onInstalled` listeners are only triggered in the background, which is why you must import them into the background.
As soon as `storage.defineItem` is called, WXT checks if migrations need to be ran, and if so, runs them. Calls to get or update the storage item's value or metadata (`getValue`, `setValue`, `removeValue`, `getMeta`, etc) will automatically wait for the migration process to finish before actually reading or writing values.
+7 -5
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.17.1",
"version": "0.17.3",
"description": "Next gen framework for developing web extensions",
"engines": {
"node": ">=18",
@@ -30,7 +30,10 @@
"bin",
"dist"
],
"bin": "./bin/wxt.mjs",
"bin": {
"wxt": "./bin/wxt.mjs",
"wxt-publish-extension": "./bin/wxt-publish-extension.cjs"
},
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -103,6 +106,7 @@
"sync-releases": "pnpx changelogen@latest gh release"
},
"dependencies": {
"@aklinker1/rollup-plugin-visualizer": "5.12.0",
"@types/webextension-polyfill": "^0.10.5",
"@webext-core/fake-browser": "^1.3.1",
"@webext-core/isolated-element": "^1.1.1",
@@ -125,15 +129,13 @@
"jiti": "^1.21.0",
"json5": "^2.2.3",
"linkedom": "^0.16.1",
"manage-path": "^2.0.0",
"minimatch": "^9.0.3",
"natural-compare": "^1.4.0",
"normalize-path": "^3.0.0",
"ora": "^7.0.1",
"picocolors": "^1.0.0",
"prompts": "^2.4.2",
"publish-browser-extension": "^2.1.2",
"rollup-plugin-visualizer": "^5.9.2",
"publish-browser-extension": "^2.1.3",
"unimport": "^3.4.0",
"vite": "^5.1.3",
"web-ext-run": "^0.2.0",
+23 -30
View File
@@ -8,6 +8,9 @@ importers:
.:
dependencies:
'@aklinker1/rollup-plugin-visualizer':
specifier: 5.12.0
version: 5.12.0
'@types/webextension-polyfill':
specifier: ^0.10.5
version: 0.10.7
@@ -74,9 +77,6 @@ importers:
linkedom:
specifier: ^0.16.1
version: 0.16.1
manage-path:
specifier: ^2.0.0
version: 2.0.0
minimatch:
specifier: ^9.0.3
version: 9.0.3
@@ -96,11 +96,8 @@ importers:
specifier: ^2.4.2
version: 2.4.2
publish-browser-extension:
specifier: ^2.1.2
version: 2.1.2
rollup-plugin-visualizer:
specifier: ^5.9.2
version: 5.12.0
specifier: ^2.1.3
version: 2.1.3
unimport:
specifier: ^3.4.0
version: 3.4.0
@@ -226,6 +223,22 @@ importers:
packages:
/@aklinker1/rollup-plugin-visualizer@5.12.0:
resolution: {integrity: sha512-X24LvEGw6UFmy0lpGJDmXsMyBD58XmX1bbwsaMLhNoM+UMQfQ3b2RtC+nz4b/NoRK5r6QJSKJHBNVeUdwqybaQ==}
engines: {node: '>=14'}
hasBin: true
peerDependencies:
rollup: 2.x || 3.x || 4.x
peerDependenciesMeta:
rollup:
optional: true
dependencies:
open: 8.4.2
picomatch: 2.3.1
source-map: 0.7.4
yargs: 17.7.2
dev: false
/@algolia/autocomplete-core@1.9.3(algoliasearch@4.20.0):
resolution: {integrity: sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==}
dependencies:
@@ -3544,10 +3557,6 @@ packages:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: false
/manage-path@2.0.0:
resolution: {integrity: sha512-NJhyB+PJYTpxhxZJ3lecIGgh4kwIY2RAh44XvAz9UlqthlQwtPBf62uBVR8XaD8CRuSjQ6TnZH2lNJkbLPZM2A==}
dev: false
/mark.js@8.11.1:
resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==}
dev: true
@@ -4175,8 +4184,8 @@ packages:
sade: 1.8.1
dev: true
/publish-browser-extension@2.1.2:
resolution: {integrity: sha512-g6+mtdR4Z+GYHPIrfaAwC7Kbt1oQlpJ8r0x1PAytScy33OFdK+HVUeDDYorBpPAiQlmYJRQga7rY9QVTyTw34g==}
/publish-browser-extension@2.1.3:
resolution: {integrity: sha512-qisnXUUwjvu5kMvObfG7UQ9rPU3t0XfbKdCdCrwAXaLUySdC25nlM4gxi+CBvL7LiwvK494GJyEK/weQBhIyTQ==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
dependencies:
@@ -4391,22 +4400,6 @@ packages:
glob: 7.1.6
dev: false
/rollup-plugin-visualizer@5.12.0:
resolution: {integrity: sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==}
engines: {node: '>=14'}
hasBin: true
peerDependencies:
rollup: 2.x || 3.x || 4.x
peerDependenciesMeta:
rollup:
optional: true
dependencies:
open: 8.4.2
picomatch: 2.3.1
source-map: 0.7.4
yargs: 17.7.2
dev: false
/rollup@4.6.1:
resolution: {integrity: sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+13 -15
View File
@@ -3,11 +3,12 @@ import { describe, it, expect, beforeEach, vi, expectTypeOf } from 'vitest';
import { browser } from '~/browser';
import { WxtStorageItem, storage } from '~/storage';
async function triggerUpdate() {
await fakeBrowser.runtime.onInstalled.trigger({
reason: 'update',
temporary: false,
});
/**
* 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));
}
describe('Storage Utils', () => {
@@ -75,10 +76,7 @@ describe('Storage Utils', () => {
const actual = await storage.getItems(params);
expect(actual).toHaveLength(3);
expected.forEach((item) => {
expect(actual).toContainEqual(item);
});
expect(actual).toEqual(expected);
});
});
@@ -412,7 +410,7 @@ describe('Storage Utils', () => {
3: migrateToV3,
},
});
await triggerUpdate();
await waitForMigrations();
const actualValue = await item.getValue();
const actualMeta = await item.getMeta();
@@ -439,7 +437,7 @@ describe('Storage Utils', () => {
3: migrateToV3,
},
});
await triggerUpdate();
await waitForMigrations();
const actualValue = await item.getValue();
const actualMeta = await item.getMeta();
@@ -464,7 +462,7 @@ describe('Storage Utils', () => {
2: migrateToV2,
},
});
await triggerUpdate();
await waitForMigrations();
const actualValue = await item.getValue();
const actualMeta = await item.getMeta();
@@ -492,7 +490,7 @@ describe('Storage Utils', () => {
3: migrateToV3,
},
});
await triggerUpdate();
await waitForMigrations();
expect(migrateToV2).not.toBeCalled();
expect(migrateToV3).not.toBeCalled();
@@ -514,7 +512,7 @@ describe('Storage Utils', () => {
3: migrateToV3,
},
});
await triggerUpdate();
await waitForMigrations();
const actualValue = await item.getValue();
const actualMeta = await item.getMeta();
@@ -541,7 +539,7 @@ describe('Storage Utils', () => {
defaultValue: 0,
version: nextVersion,
});
await triggerUpdate();
await waitForMigrations();
await expect(item.migrate()).rejects.toThrow(
'Version downgrade detected (v2 -> v1) for "local:count"',
+10 -2
View File
@@ -1,6 +1,5 @@
import { CAC, Command } from 'cac';
import consola, { LogLevels } from 'consola';
import { exec } from '~/core/utils/exec';
import { printHeader } from '~/core/utils/log';
import { formatDuration } from '~/core/utils/time';
import { ValidationError } from '~/core/utils/validation';
@@ -63,10 +62,18 @@ export function getArrayFromFlags<T>(
}
const aliasCommandNames = new Set<string>();
/**
* @param base Command to add this one to
* @param name The command name to add
* @param alias The CLI tool being aliased
* @param bin The CLI tool binary name. Usually the same as the alias
* @param docsUrl URL to the docs for the aliased CLI tool
*/
export function createAliasedCommand(
base: CAC,
name: string,
alias: string,
bin: string,
docsUrl: string,
) {
const aliasedCommand = base
@@ -79,7 +86,8 @@ export function createAliasedCommand(
const args = process.argv.slice(
process.argv.indexOf(aliasedCommand.name) + 1,
);
await exec(alias, args, {
const { execa } = await import('execa');
await execa(bin, args, {
stdio: 'inherit',
});
} catch {
+1
View File
@@ -140,6 +140,7 @@ createAliasedCommand(
cli,
'submit',
'publish-extension',
'wxt-publish-extension',
'https://www.npmjs.com/publish-browser-extension',
);
@@ -1,5 +1,5 @@
import type * as vite from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';
import { visualizer } from '@aklinker1/rollup-plugin-visualizer';
import { ResolvedConfig } from '~/types';
import path from 'node:path';
+6 -12
View File
@@ -16,8 +16,8 @@ import {
validateEntrypoints,
} from '../validation';
import consola from 'consola';
import { exec } from '../exec';
import { wxt } from '../../wxt';
import { mergeJsonOutputs } from '@aklinker1/rollup-plugin-visualizer';
/**
* Builds the extension based on an internal config. No more config discovery is performed, the
@@ -93,17 +93,11 @@ async function combineAnalysisStats(): Promise<void> {
});
const absolutePaths = unixFiles.map(unnormalizePath);
await exec(
'rollup-plugin-visualizer',
[
...absolutePaths,
'--template',
wxt.config.analysis.template,
'--filename',
wxt.config.analysis.outputFile,
],
{ cwd: wxt.config.root, stdio: 'inherit' },
);
await mergeJsonOutputs({
inputs: absolutePaths,
template: wxt.config.analysis.template,
filename: wxt.config.analysis.outputFile,
});
if (!wxt.config.analysis.keepArtifacts) {
await Promise.all(absolutePaths.map((statsFile) => fs.remove(statsFile)));
-26
View File
@@ -1,26 +0,0 @@
import type { Options } from 'execa';
import managePath from 'manage-path';
import { resolve } from 'node:path';
import { wxt } from '../wxt';
const managedPath = managePath(process.env);
/**
* Wrapper around `execa` with a modified `PATH` variable containing CLI tools from WXT's dependencies.
*/
export const exec = async (
file: string,
args?: readonly string[],
options?: Options,
) => {
// Reset so the same path isn't added multiple times
managedPath.restore();
// Add subdependency path for PNPM shamefully-hoist=false
managedPath.push(
resolve(wxt.config.root, 'node_modules/wxt/node_modules/.bin'),
);
const { execa } = await import('execa');
return await execa(file, args, options);
};
+44 -25
View File
@@ -258,14 +258,14 @@ function createStorage(): WxtStorage {
);
}
const migrate = async () => {
const [value, meta] = await Promise.all([
// TODO: Optimize with getItems
getItem(driver, driverKey, undefined),
getMeta(driver, driverKey),
const driverMetaKey = getMetaKey(driverKey);
const [{ value }, { value: meta }] = await driver.getItems([
driverKey,
driverMetaKey,
]);
if (value == null) return;
const currentVersion = meta.v ?? 1;
const currentVersion = meta?.v ?? 1;
if (currentVersion > targetVersion) {
throw Error(
`Version downgrade detected (v${currentVersion} -> v${targetVersion}) for "${key}"`,
@@ -285,24 +285,21 @@ function createStorage(): WxtStorage {
(await migrations?.[migrateToVersion]?.(migratedValue)) ??
migratedValue;
}
await Promise.all([
// TODO: Optimize with `setItem`
setItem(driver, driverKey, migratedValue),
setMeta(driver, driverKey, { v: targetVersion }),
await driver.setItems([
{ key: driverKey, value: migratedValue },
{ key: driverMetaKey, value: { ...meta, v: targetVersion } },
]);
logger.debug(
`Storage migration completed for ${key} v${targetVersion}`,
{ migratedValue },
);
};
browser.runtime.onInstalled.addListener(async ({ reason }) => {
if (reason !== 'update') return;
try {
await migrate();
} catch (err) {
logger.error(`Migration failed for ${key}`, err);
}
});
const migrationsDone =
opts?.migrations == null
? Promise.resolve()
: migrate().catch((err) => {
logger.error(`Migration failed for ${key}`, err);
});
const getDefaultValue = () => opts?.defaultValue ?? null;
@@ -310,12 +307,30 @@ function createStorage(): WxtStorage {
get defaultValue() {
return getDefaultValue();
},
getValue: () => getItem(driver, driverKey, opts),
getMeta: () => getMeta(driver, driverKey),
setValue: (value) => setItem(driver, driverKey, value),
setMeta: (properties) => setMeta(driver, driverKey, properties),
removeValue: (opts) => removeItem(driver, driverKey, opts),
removeMeta: (properties) => removeMeta(driver, driverKey, properties),
getValue: async () => {
await migrationsDone;
return await getItem(driver, driverKey, opts);
},
getMeta: async () => {
await migrationsDone;
return await getMeta(driver, driverKey);
},
setValue: async (value) => {
await migrationsDone;
return await setItem(driver, driverKey, value);
},
setMeta: async (properties) => {
await migrationsDone;
return await setMeta(driver, driverKey, properties);
},
removeValue: async (opts) => {
await migrationsDone;
return await removeItem(driver, driverKey, opts);
},
removeMeta: async (properties) => {
await migrationsDone;
return await removeMeta(driver, driverKey, properties);
},
watch: (cb) =>
watch(driver, driverKey, (newValue, oldValue) =>
cb(newValue ?? getDefaultValue(), oldValue ?? getDefaultValue()),
@@ -346,7 +361,10 @@ function createDriver(
);
}
return browser.storage[storageArea];
const area = browser.storage[storageArea];
if (area == null)
throw Error(`"browser.storage.${storageArea}" is undefined`);
return area;
};
const watchListeners = new Set<
(changes: Storage.StorageAreaOnChangedChangesType) => void
@@ -421,7 +439,8 @@ export interface WxtStorage {
*/
getItem<T>(key: string, opts?: GetItemOptions<T>): Promise<T | null>;
/**
* Get multiple items from storage. There is no guarantee of order in the returned array.
* 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"]);
+1 -1
View File
@@ -3,7 +3,7 @@ import type { Manifest, Scripting } from '~/browser';
import { UnimportOptions } from 'unimport';
import { LogLevel } from 'consola';
import { ContentScriptContext } from '../client/content-scripts/content-script-context';
import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer';
import type { PluginVisualizerOptions } from '@aklinker1/rollup-plugin-visualizer';
import type { FSWatcher } from 'chokidar';
import { ResolvedConfig as C12ResolvedConfig } from 'c12';
import { Hookable, NestedHooks } from 'hookable';
-12
View File
@@ -50,18 +50,6 @@ declare module 'web-ext-run/util/logger' {
export const consoleStream: IConsoleStream;
}
declare module 'manage-path' {
export interface ManagedPath {
push(...paths: string[]);
push(paths: string[]);
shift(...paths: string[]);
shift(paths: string[]);
get(): string;
restore(): void;
}
export default function managePath(env: object): ManagedPath;
}
declare module 'wxt/browser' {
// Overridden when types are generated per project
export type PublicPath = string;