diff --git a/.gitignore b/.gitignore
index 06ff8833d8..2b74da590d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,8 @@ packages/desktop-electron/build
packages/desktop-electron/.electron-symbols
packages/desktop-electron/dist
packages/desktop-electron/loot-core
+packages/desktop-client/service-worker
+packages/plugins-service/dist
bundle.desktop.js
bundle.desktop.js.map
bundle.mobile.js
diff --git a/bin/package-browser b/bin/package-browser
index ddc93c0bfd..526393a139 100755
--- a/bin/package-browser
+++ b/bin/package-browser
@@ -16,6 +16,7 @@ packages/desktop-client/bin/remove-untranslated-languages
export NODE_OPTIONS="--max-old-space-size=4096"
+yarn workspace plugins-service build
yarn workspace loot-core build:browser
yarn workspace @actual-app/web build:browser
diff --git a/bin/package-electron b/bin/package-electron
index c37b79bf77..ff56e9336c 100755
--- a/bin/package-electron
+++ b/bin/package-electron
@@ -41,6 +41,7 @@ packages/desktop-client/bin/remove-untranslated-languages
export NODE_OPTIONS="--max-old-space-size=4096"
+yarn workspace plugins-service build
yarn workspace loot-core build:node
yarn workspace @actual-app/web build --mode=desktop # electron specific build
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 223d854041..c1562c7a90 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -83,6 +83,7 @@ export default pluginTypescript.config(
'packages/component-library/src/icons/**/*',
'packages/desktop-client/bundle.browser.js',
'packages/desktop-client/build/',
+ 'packages/desktop-client/service-worker/*',
'packages/desktop-client/build-electron/',
'packages/desktop-client/build-stats/',
'packages/desktop-client/public/kcab/',
@@ -98,6 +99,7 @@ export default pluginTypescript.config(
'packages/loot-core/**/lib-dist/*',
'packages/loot-core/**/proto/*',
'packages/sync-server/build/',
+ 'packages/plugins-service/dist/',
'.yarn/*',
'.github/*',
],
diff --git a/package.json b/package.json
index 2b8bca71d3..2bab1fde7c 100644
--- a/package.json
+++ b/package.json
@@ -23,18 +23,20 @@
"start:server-monitor": "yarn workspace @actual-app/sync-server start-monitor",
"start:server-dev": "NODE_ENV=development BROWSER_OPEN=localhost:5006 yarn npm-run-all --parallel 'start:server-monitor' 'start'",
"start:desktop": "yarn desktop-dependencies && npm-run-all --parallel 'start:desktop-*'",
- "desktop-dependencies": "npm-run-all --parallel rebuild-electron build:browser-backend",
+ "desktop-dependencies": "npm-run-all --parallel rebuild-electron build:browser-backend build:plugins-service",
"start:desktop-node": "yarn workspace loot-core watch:node",
"start:desktop-client": "yarn workspace @actual-app/web watch",
"start:desktop-server-client": "yarn workspace @actual-app/web build:browser",
"start:desktop-electron": "yarn workspace desktop-electron watch",
- "start:browser": "npm-run-all --parallel 'start:browser-*'",
+ "start:browser": "yarn workspace plugins-service build-dev && npm-run-all --parallel 'start:browser-*'",
+ "start:service-plugins": "yarn workspace plugins-service watch",
"start:browser-backend": "yarn workspace loot-core watch:browser",
"start:browser-frontend": "yarn workspace @actual-app/web start:browser",
"build:browser-backend": "yarn workspace loot-core build:browser",
"build:server": "yarn build:browser && yarn workspace @actual-app/sync-server build",
"build:browser": "./bin/package-browser",
"build:desktop": "./bin/package-electron",
+ "build:plugins-service": "yarn workspace plugins-service build",
"build:api": "yarn workspace @actual-app/api build",
"generate:i18n": "yarn workspace @actual-app/web generate:i18n",
"generate:release-notes": "ts-node ./bin/release-note-generator.ts",
diff --git a/packages/desktop-client/.gitignore b/packages/desktop-client/.gitignore
index a67335f517..eb9b164f0d 100644
--- a/packages/desktop-client/.gitignore
+++ b/packages/desktop-client/.gitignore
@@ -14,6 +14,9 @@ build-electron
build-stats
stats.json
+# generated service worker
+service-worker/
+
# misc
.DS_Store
.env
diff --git a/packages/desktop-client/bin/build-browser b/packages/desktop-client/bin/build-browser
index 3e6e18aa6c..f53151b442 100755
--- a/packages/desktop-client/bin/build-browser
+++ b/packages/desktop-client/bin/build-browser
@@ -9,6 +9,7 @@ rm -fr build
export IS_GENERIC_BROWSER=1
export REACT_APP_BACKEND_WORKER_HASH=`ls "$ROOT"/../public/kcab/kcab.worker.*.js | sed 's/.*kcab\.worker\.\(.*\)\.js/\1/'`
+export REACT_APP_PLUGIN_SERVICE_WORKER_HASH=`ls "$ROOT"/../service-worker/plugin-sw.*.js | sed 's/.*plugin-sw\.\(.*\)\.js/\1/'`
yarn build
diff --git a/packages/desktop-client/bin/watch-browser b/packages/desktop-client/bin/watch-browser
index 2f2861696f..d90eba99c1 100755
--- a/packages/desktop-client/bin/watch-browser
+++ b/packages/desktop-client/bin/watch-browser
@@ -6,5 +6,6 @@ cd "$ROOT/.."
export IS_GENERIC_BROWSER=1
export PORT=3001
export REACT_APP_BACKEND_WORKER_HASH="dev"
+export REACT_APP_PLUGIN_SERVICE_WORKER_HASH="dev"
yarn start
diff --git a/packages/desktop-client/tsconfig.service-worker.json b/packages/desktop-client/tsconfig.service-worker.json
new file mode 100644
index 0000000000..56ebc5b10a
--- /dev/null
+++ b/packages/desktop-client/tsconfig.service-worker.json
@@ -0,0 +1,18 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "noEmit": false,
+ "outDir": "./service-worker",
+ "target": "ES2022",
+ "lib": ["ES2022", "WebWorker", "DOM", "DOM.Iterable"],
+ "module": "ES2022",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "strict": false,
+ "types": ["vite/client"]
+ },
+ "include": ["src/plugin-service-worker.ts"],
+ "exclude": ["**/*.test.ts", "**/*.spec.ts"]
+}
diff --git a/packages/desktop-client/vite.config.mts b/packages/desktop-client/vite.config.mts
index 719aaf4df3..ca117fb285 100644
--- a/packages/desktop-client/vite.config.mts
+++ b/packages/desktop-client/vite.config.mts
@@ -26,6 +26,15 @@ const addWatchers = (): Plugin => ({
},
});
+// Get service worker filename from environment variable
+function getServiceWorkerFilename(): string {
+ const hash = process.env.REACT_APP_PLUGIN_SERVICE_WORKER_HASH;
+ if (hash) {
+ return `plugin-sw.${hash}.js`;
+ }
+ return 'plugin-sw.js'; // fallback
+}
+
// Inject build shims using the inject plugin
const injectShims = (): Plugin[] => {
const buildShims = path.resolve('./src/build-shims.js');
@@ -159,18 +168,41 @@ export default defineConfig(async ({ mode }) => {
? undefined
: VitePWA({
registerType: 'prompt',
+ strategies: 'injectManifest',
+ srcDir: 'service-worker',
+ filename: getServiceWorkerFilename(),
+ manifest: {
+ name: 'Actual',
+ short_name: 'Actual',
+ description: 'A local-first personal finance tool',
+ theme_color: '#8812E1',
+ background_color: '#8812E1',
+ display: 'standalone',
+ start_url: './',
+ },
+ injectManifest: {
+ maximumFileSizeToCacheInBytes: 10 * 1024 * 1024, // 10MB
+ swSrc: `service-worker/${getServiceWorkerFilename()}`,
+ },
+ devOptions: {
+ enabled: true, // We need service worker in dev mode to work with plugins
+ type: 'module',
+ },
workbox: {
globPatterns: [
'**/*.{js,css,html,txt,wasm,sql,sqlite,ico,png,woff2,webmanifest}',
],
ignoreURLParametersMatching: [/^v$/],
navigateFallback: '/index.html',
- maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, // 5MB
+ maximumFileSizeToCacheInBytes: 10 * 1024 * 1024, // 10MB
navigateFallbackDenylist: [
/^\/account\/.*$/,
/^\/admin\/.*$/,
/^\/secret\/.*$/,
/^\/openid\/.*$/,
+ /^\/plugins\/.*$/,
+ /^\/kcab\/.*$/,
+ /^\/plugin-data\/.*$/,
],
},
}),
diff --git a/packages/plugins-service/bin/build-service-worker b/packages/plugins-service/bin/build-service-worker
new file mode 100755
index 0000000000..813463f3f7
--- /dev/null
+++ b/packages/plugins-service/bin/build-service-worker
@@ -0,0 +1,28 @@
+#!/bin/bash -e
+
+cd `dirname "$0"`
+ROOT=`pwd -P`
+VITE_ARGS=""
+
+DESKTOP_DIR="$ROOT"/../../desktop-client
+SERVICE_WORKER_DIR="$DESKTOP_DIR"/service-worker
+mkdir -p "$SERVICE_WORKER_DIR"
+
+# Clean out previous build files
+rm -f ../dist/*
+rm -rf "$DESKTOP_DIR"/service-worker/*
+
+if [ $NODE_ENV == 'development' ]; then
+ if [ "$OSTYPE" == "msys" ]; then
+ # Ensure symlinks are created as native Windows symlinks.
+ export MSYS=winsymlinks:nativestrict
+ fi
+ ln -snf "$ROOT"/../dist "$DESKTOP_DIR"/service-worker
+fi
+
+yarn vite build --config ../vite.config.ts --mode $NODE_ENV $VITE_ARGS
+
+if [ $NODE_ENV == 'production' ]; then
+ # In production, just copy the built files
+ cp -r ../dist/* "$DESKTOP_DIR"/service-worker
+fi
\ No newline at end of file
diff --git a/packages/plugins-service/package.json b/packages/plugins-service/package.json
new file mode 100644
index 0000000000..f32ed44b27
--- /dev/null
+++ b/packages/plugins-service/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "plugins-service",
+ "version": "0.0.1",
+ "description": "Plugin service worker for Actual",
+ "main": "plugin-sw.js",
+ "scripts": {
+ "build": "cross-env NODE_ENV=production ./bin/build-service-worker",
+ "build-dev": "cross-env NODE_ENV=development ./bin/build-service-worker",
+ "watch": "cross-env NODE_ENV=development ./bin/build-service-worker --watch"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "workbox-precaching": "^7.0.0"
+ },
+ "devDependencies": {
+ "@types/node": "^22.17.0",
+ "cross-env": "^7.0.3",
+ "typescript": "^5.9.2",
+ "vite": "^6.3.6"
+ }
+}
diff --git a/packages/plugins-service/src/plugin-service-worker.ts b/packages/plugins-service/src/plugin-service-worker.ts
new file mode 100644
index 0000000000..02d116ddff
--- /dev/null
+++ b/packages/plugins-service/src/plugin-service-worker.ts
@@ -0,0 +1,159 @@
+///
+import { precacheAndRoute } from 'workbox-precaching';
+
+// Service Worker Global Types
+declare const self: ServiceWorkerGlobalScope & {
+ __WB_DISABLE_DEV_LOGS: boolean;
+};
+
+type PluginFile = {
+ name: string;
+ content: string;
+};
+
+type PluginMessage = {
+ type: string;
+ eventData?: {
+ pluginUrl: string;
+ };
+};
+
+self.__WB_DISABLE_DEV_LOGS = true;
+
+// Injected by VitePWA
+precacheAndRoute(self.__WB_MANIFEST);
+
+const fileList = new Map();
+
+// Log installation event
+self.addEventListener('install', (_event: ExtendableEvent) => {
+ console.log('Plugins Worker installing...');
+ self.skipWaiting(); // Forces activation immediately
+});
+
+// Log activation event
+self.addEventListener('activate', (_event: ExtendableEvent) => {
+ self.clients.claim();
+
+ self.clients.matchAll().then(clients => {
+ clients.forEach(client => {
+ client.postMessage({
+ type: 'service-worker-ready',
+ timestamp: Date.now(),
+ });
+ });
+ });
+});
+
+self.addEventListener('message', (event: ExtendableMessageEvent) => {
+ if (event.data && (event.data as PluginMessage).type === 'SKIP_WAITING') {
+ self.skipWaiting();
+ }
+});
+
+self.addEventListener('fetch', (event: FetchEvent) => {
+ const url = new URL(event.request.url);
+ const pathSegments = url.pathname.split('/').filter(Boolean); // Split and remove empty segments
+
+ const pluginsIndex = pathSegments.indexOf('plugin-data');
+ const slugIndex = pluginsIndex + 1;
+ if (pluginsIndex !== -1 && pathSegments[slugIndex]) {
+ const slug = pathSegments[slugIndex];
+ const fileName =
+ pathSegments.length > slugIndex + 1
+ ? pathSegments[slugIndex + 1].split('?')[0]
+ : '';
+ event.respondWith(handlePlugin(slug, fileName.replace('?import', '')));
+ }
+});
+
+async function handlePlugin(slug: string, fileName: string): Promise {
+ for (const key of fileList.keys()) {
+ if (key.startsWith(`${slug}/`)) {
+ if (key.endsWith(`/${fileName}`)) {
+ const content = fileList.get(key);
+ const contentType = getContentType(fileName);
+ return new Response(content, {
+ headers: { 'Content-Type': contentType },
+ });
+ }
+ }
+ }
+
+ const clientsList = await self.clients.matchAll();
+ if (clientsList.length === 0) {
+ return new Response(
+ JSON.stringify({ error: 'No active clients to process' }),
+ {
+ status: 404,
+ headers: { 'Content-Type': 'application/json' },
+ },
+ );
+ }
+
+ const client = clientsList[0];
+ return new Promise(resolve => {
+ const channel = new MessageChannel();
+ channel.port1.onmessage = (messageEvent: MessageEvent) => {
+ const responseData = messageEvent.data as PluginFile[];
+
+ if (responseData && Array.isArray(responseData)) {
+ responseData.forEach(({ name, content }) => {
+ fileList.set(`${slug}/${encodeURIComponent(name)}`, content);
+ });
+ }
+
+ const fileToCheck = fileName.length > 0 ? fileName : 'mf-manifest.json';
+
+ if (fileList.has(`${slug}/${fileToCheck}`)) {
+ let content = fileList.get(`${slug}/${fileToCheck}`)!;
+ const contentType = getContentType(fileToCheck);
+ const headers: Record = { 'Content-Type': contentType };
+
+ if (fileToCheck === 'mf-manifest.json') {
+ try {
+ const manifest = JSON.parse(content);
+ if (manifest.metaData?.publicPath) {
+ manifest.metaData.publicPath = `/plugin-data/${slug}/`;
+ content = JSON.stringify(manifest);
+ }
+ } catch (error) {
+ console.error(
+ 'Failed to parse manifest for publicPath rewrite:',
+ error,
+ );
+ }
+
+ headers['Cache-Control'] = 'no-cache, no-store, must-revalidate';
+ headers['Pragma'] = 'no-cache';
+ headers['Expires'] = '0';
+ }
+
+ resolve(new Response(content, { headers }));
+ } else {
+ resolve(new Response('File not found', { status: 404 }));
+ }
+ };
+
+ client.postMessage(
+ { type: 'plugin-files', eventData: { pluginUrl: slug } },
+ [channel.port2],
+ );
+ });
+}
+
+function getContentType(fileName: string): string {
+ const extension = fileName.split('.').pop()?.toLowerCase() || '';
+ const mimeTypes: Record = {
+ html: 'text/html',
+ css: 'text/css',
+ js: 'application/javascript',
+ json: 'application/json',
+ png: 'image/png',
+ jpg: 'image/jpeg',
+ jpeg: 'image/jpeg',
+ gif: 'image/gif',
+ svg: 'image/svg+xml',
+ };
+ return mimeTypes[extension] || 'application/octet-stream';
+}
diff --git a/packages/plugins-service/tsconfig.json b/packages/plugins-service/tsconfig.json
new file mode 100644
index 0000000000..fab078e6e6
--- /dev/null
+++ b/packages/plugins-service/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2022", "WebWorker", "DOM", "DOM.Iterable"],
+ "module": "ES2022",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "strict": false,
+ "types": ["vite/client"],
+ "outDir": "dist",
+ "rootDir": "src"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules"]
+}
diff --git a/packages/plugins-service/vite.config.ts b/packages/plugins-service/vite.config.ts
new file mode 100644
index 0000000000..1d32177b34
--- /dev/null
+++ b/packages/plugins-service/vite.config.ts
@@ -0,0 +1,40 @@
+// @ts-strict-ignore
+import path from 'path';
+
+import { defineConfig } from 'vite';
+
+// eslint-disable-next-line import/no-default-export
+export default defineConfig(({ mode }) => {
+ const isDev = mode === 'development';
+ const outDir = path.resolve(__dirname, 'dist');
+
+ return {
+ mode,
+ build: {
+ target: 'es2020',
+ outDir,
+ emptyOutDir: true,
+ lib: {
+ entry: path.resolve(__dirname, 'src/plugin-service-worker.ts'),
+ name: 'plugin_sw',
+ formats: ['iife'],
+ fileName: () => (isDev ? 'plugin-sw.dev.js' : 'plugin-sw.[hash].js'),
+ },
+ sourcemap: true,
+ minify: isDev ? false : 'terser',
+ terserOptions: {
+ compress: {
+ drop_debugger: false,
+ },
+ mangle: false,
+ },
+ },
+ resolve: {
+ extensions: ['.js', '.ts', '.json'],
+ },
+ define: {
+ 'process.env': '{}',
+ 'process.env.IS_DEV': JSON.stringify(isDev),
+ },
+ };
+});
diff --git a/packages/sync-server/docker/alpine.Dockerfile b/packages/sync-server/docker/alpine.Dockerfile
index 72b9f4fa35..c3ce965260 100644
--- a/packages/sync-server/docker/alpine.Dockerfile
+++ b/packages/sync-server/docker/alpine.Dockerfile
@@ -16,6 +16,7 @@ COPY packages/desktop-electron/package.json packages/desktop-electron/package.js
COPY packages/eslint-plugin-actual/package.json packages/eslint-plugin-actual/package.json
COPY packages/loot-core/package.json packages/loot-core/package.json
COPY packages/sync-server/package.json packages/sync-server/package.json
+COPY packages/plugins-service/package.json packages/plugins-service/package.json
# Avoiding memory issues with ARMv7
RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi
diff --git a/packages/sync-server/docker/ubuntu.Dockerfile b/packages/sync-server/docker/ubuntu.Dockerfile
index 5759eb42c3..c84a4ba7fd 100644
--- a/packages/sync-server/docker/ubuntu.Dockerfile
+++ b/packages/sync-server/docker/ubuntu.Dockerfile
@@ -16,6 +16,7 @@ COPY packages/desktop-electron/package.json packages/desktop-electron/package.js
COPY packages/eslint-plugin-actual/package.json packages/eslint-plugin-actual/package.json
COPY packages/loot-core/package.json packages/loot-core/package.json
COPY packages/sync-server/package.json packages/sync-server/package.json
+COPY packages/plugins-service/package.json packages/plugins-service/package.json
# Avoiding memory issues with ARMv7
RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi
diff --git a/sync-server.Dockerfile b/sync-server.Dockerfile
index 1175cd555b..c8879c6569 100644
--- a/sync-server.Dockerfile
+++ b/sync-server.Dockerfile
@@ -16,6 +16,7 @@ COPY packages/desktop-electron/package.json packages/desktop-electron/package.js
COPY packages/eslint-plugin-actual/package.json packages/eslint-plugin-actual/package.json
COPY packages/loot-core/package.json packages/loot-core/package.json
COPY packages/sync-server/package.json packages/sync-server/package.json
+COPY packages/plugins-service/package.json packages/plugins-service/package.json
COPY ./bin/package-browser ./bin/package-browser
diff --git a/tsconfig.json b/tsconfig.json
index bd1112bbbd..32f514cb1d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -52,7 +52,8 @@
"**/dist/*",
"**/lib-dist/*",
"**/test-results/*",
- "**/playwright-report/*"
+ "**/playwright-report/*",
+ "**/service-worker/*"
],
"ts-node": {
"compilerOptions": {
diff --git a/upcoming-release-notes/5784.md b/upcoming-release-notes/5784.md
new file mode 100644
index 0000000000..85b9b43c03
--- /dev/null
+++ b/upcoming-release-notes/5784.md
@@ -0,0 +1,7 @@
+---
+category: Features
+authors: [lelemm]
+---
+
+Introduce a Workbox-based service worker for enhanced plugin support and caching functionality.
+
diff --git a/yarn.lock b/yarn.lock
index c526d16c52..e8d448071d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6717,7 +6717,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/node@npm:^22.18.8":
+"@types/node@npm:^22.17.0, @types/node@npm:^22.18.8":
version: 22.18.8
resolution: "@types/node@npm:22.18.8"
dependencies:
@@ -9479,6 +9479,18 @@ __metadata:
languageName: node
linkType: hard
+"cross-env@npm:^7.0.3":
+ version: 7.0.3
+ resolution: "cross-env@npm:7.0.3"
+ dependencies:
+ cross-spawn: "npm:^7.0.1"
+ bin:
+ cross-env: src/bin/cross-env.js
+ cross-env-shell: src/bin/cross-env-shell.js
+ checksum: 10/e99911f0d31c20e990fd92d6fd001f4b01668a303221227cc5cb42ed155f086351b1b3bd2699b200e527ab13011b032801f8ce638e6f09f854bdf744095e604c
+ languageName: node
+ linkType: hard
+
"cross-spawn@npm:^6.0.5":
version: 6.0.5
resolution: "cross-spawn@npm:6.0.5"
@@ -16670,6 +16682,18 @@ __metadata:
languageName: node
linkType: hard
+"plugins-service@workspace:packages/plugins-service":
+ version: 0.0.0-use.local
+ resolution: "plugins-service@workspace:packages/plugins-service"
+ dependencies:
+ "@types/node": "npm:^22.17.0"
+ cross-env: "npm:^7.0.3"
+ typescript: "npm:^5.9.2"
+ vite: "npm:^6.3.6"
+ workbox-precaching: "npm:^7.0.0"
+ languageName: unknown
+ linkType: soft
+
"possible-typed-array-names@npm:^1.0.0":
version: 1.0.0
resolution: "possible-typed-array-names@npm:1.0.0"
@@ -16677,7 +16701,7 @@ __metadata:
languageName: node
linkType: hard
-"postcss@npm:^8.5.5, postcss@npm:^8.5.6":
+"postcss@npm:^8.5.3, postcss@npm:^8.5.5, postcss@npm:^8.5.6":
version: 8.5.6
resolution: "postcss@npm:8.5.6"
dependencies:
@@ -19661,7 +19685,7 @@ __metadata:
languageName: node
linkType: hard
-"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15":
+"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.13, tinyglobby@npm:^0.2.15":
version: 0.2.15
resolution: "tinyglobby@npm:0.2.15"
dependencies:
@@ -20144,7 +20168,7 @@ __metadata:
languageName: node
linkType: hard
-"typescript@npm:^5.9.3":
+"typescript@npm:^5.9.2, typescript@npm:^5.9.3":
version: 5.9.3
resolution: "typescript@npm:5.9.3"
bin:
@@ -20174,7 +20198,7 @@ __metadata:
languageName: node
linkType: hard
-"typescript@patch:typescript@npm%3A^5.9.3#optional!builtin":
+"typescript@patch:typescript@npm%3A^5.9.2#optional!builtin, typescript@patch:typescript@npm%3A^5.9.3#optional!builtin":
version: 5.9.3
resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5"
bin:
@@ -20940,6 +20964,61 @@ __metadata:
languageName: node
linkType: hard
+"vite@npm:^6.3.6":
+ version: 6.3.6
+ resolution: "vite@npm:6.3.6"
+ dependencies:
+ esbuild: "npm:^0.25.0"
+ fdir: "npm:^6.4.4"
+ fsevents: "npm:~2.3.3"
+ picomatch: "npm:^4.0.2"
+ postcss: "npm:^8.5.3"
+ rollup: "npm:^4.34.9"
+ tinyglobby: "npm:^0.2.13"
+ peerDependencies:
+ "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0
+ jiti: ">=1.21.0"
+ less: "*"
+ lightningcss: ^1.21.0
+ sass: "*"
+ sass-embedded: "*"
+ stylus: "*"
+ sugarss: "*"
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ dependenciesMeta:
+ fsevents:
+ optional: true
+ peerDependenciesMeta:
+ "@types/node":
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+ bin:
+ vite: bin/vite.js
+ checksum: 10/8b8b6fe12318ca457396bf2053df7056cf4810f1d4a43b36b6afe59860e32b749c0685a290fe8a973b0d3da179ceec4c30cebbd3c91d0c47fbcf6436b17bdeef
+ languageName: node
+ linkType: hard
+
"vite@npm:^7.1.9":
version: 7.1.9
resolution: "vite@npm:7.1.9"
@@ -21460,7 +21539,7 @@ __metadata:
languageName: node
linkType: hard
-"workbox-precaching@npm:7.3.0":
+"workbox-precaching@npm:7.3.0, workbox-precaching@npm:^7.0.0":
version: 7.3.0
resolution: "workbox-precaching@npm:7.3.0"
dependencies: