diff --git a/.github/actions/setup-pnpm-with-dependencies/action.yaml b/.github/actions/setup-pnpm-with-dependencies/action.yaml index e600d16..9fcc37a 100644 --- a/.github/actions/setup-pnpm-with-dependencies/action.yaml +++ b/.github/actions/setup-pnpm-with-dependencies/action.yaml @@ -71,3 +71,26 @@ runs: with: path: './node_modules' key: ${{ steps.node-version.outputs.cache-key }} + + - name: Get Playwright Chromium version + id: get-playwright-version + run: echo "PLAYWRIGHT_VERSION=$(jq -r '.devDependencies["playwright-chromium"]' < package.json | sed 's/[^0-9.]//g' | sed 's/\./-/g')" >> $GITHUB_OUTPUT + shell: bash + + - name: Set Playwright path + id: playwright-path + shell: bash + run: echo "PLAYWRIGHT_BROWSERS_PATH=$HOME/.cache/playwright-bin" >> $GITHUB_ENV + + - name: Cache Playwright's binary + id: playwright-cache + uses: actions/cache@v4 + with: + key: ${{ runner.os }}-playwright-bin-v${{ steps.get-playwright-version.outputs.PLAYWRIGHT_VERSION }} + path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }} + + - name: Install Playwright + id: playwright-install + shell: bash + # does not need to explicitly set chromium after https://github.com/microsoft/playwright/issues/14862 is solved + run: pnpm playwright install chromium diff --git a/README.md b/README.md index e352d02..5535c33 100644 --- a/README.md +++ b/README.md @@ -94,13 +94,11 @@ pnpm lint:fix Unit tests are run with [Vitest](https://vitest.dev/). ```shell -# Run tests pnpm test - -# Run tests in the Vitest UI -pnpm test:ui ``` +See the [Steps to Test Your Plugin](./playground/README.md) for more information on how to write tests. + ### Build Build for production and inspect the files in the `/dist` directory. diff --git a/build.config.ts b/build.config.ts index b9f0f0b..5ac5440 100644 --- a/build.config.ts +++ b/build.config.ts @@ -6,6 +6,7 @@ export default defineBuildConfig({ // Each separate plugin's entry file should be listed here entries: [ './src/plugin-example-one/index.ts', + './src/plugin-dynamic-import-retry/index.ts', ], // Generates .d.ts declaration file(s) declaration: true, diff --git a/package.json b/package.json index db0fb89..57da199 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,7 @@ "scripts": { "lint": "eslint", "lint:fix": "eslint --fix", - "test": "vitest run --passWithNoTests", - "test:ui": "vitest --ui --passWithNoTests", + "test": "vitest run -c vitest.config.e2e.ts", "typecheck": "vue-tsc --noEmit", "build": "unbuild", "commit": "cz" @@ -16,9 +15,15 @@ "./plugin-example-one": { "import": "./dist/plugin-example-one/index.mjs", "types": "./dist/plugin-example-one/index.d.ts" + }, + "./plugin-dynamic-import-retry": { + "import": "./dist/plugin-dynamic-import-retry/index.mjs", + "types": "./dist/plugin-dynamic-import-retry/index.d.ts" } }, - "files": ["dist"], + "files": [ + "dist" + ], "author": "Kong, Inc.", "license": "Apache-2.0", "devDependencies": { @@ -27,11 +32,21 @@ "@digitalroute/cz-conventional-changelog-for-jira": "^8.0.1", "@evilmartians/lefthook": "^1.10.1", "@kong/eslint-config-kong-ui": "^1.2.6", + "@rollup/plugin-dynamic-import-vars": "^2.1.5", + "@types/fs-extra": "^11.0.4", + "@vitejs/plugin-vue": "^5.2.1", "@vitest/ui": "^2.1.8", + "@vue/tsconfig": "^0.7.0", "eslint": "^9.17.0", + "fs-extra": "^11.2.0", + "npm-run-all2": "^7.0.2", + "playwright-chromium": "^1.49.1", "typescript": "^5.7.2", "unbuild": "^3.2.0", + "vite": "^6.0.7", "vitest": "^2.1.8", + "vue": "^3.5.13", + "vue-router": "^4.5.0", "vue-tsc": "^2.2.0" }, "engines": { @@ -52,5 +67,14 @@ "jiraPrepend": "[", "jiraAppend": "]" } + }, + "dependencies": { + "@fatso83/retry-dynamic-import": "^2.1.4", + "@rollup/pluginutils": "^5.1.4", + "acorn-walk": "^8.3.4", + "magic-string": "^0.30.17" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0" } } diff --git a/playground/README.md b/playground/README.md new file mode 100644 index 0000000..96ce4d3 --- /dev/null +++ b/playground/README.md @@ -0,0 +1,19 @@ +## Directory Overview + +This directory serves as a testing environment for plugins. Each subdirectory corresponds to a specific plugin, identified by its name. + +* `vitestGlobalSetup.ts`: This is the global setup file for Vitest, executed once before all tests. It initializes a headless browser environment. +* `vitestSetup.ts`: This is the per-test setup file for Vitest, executed before each individual test. It provides references to the browser instance and the page context for use during testing. + +## Steps to Test Your Plugin +1. Create a new subdirectory within the playground directory, using your plugin’s name as the folder name. +2. Within this folder, set up one or more Vite projects for testing. +3. Write test files with the `.spec.ts` extension to validate your plugin’s functionality. +4. If browser-based testing is required, you can import `page` or `browser` from the `vitestSetup.ts` file to interact with the headless browser environment. + +## Run playground project manually +```bash +pnpm vite dev ./playground// +pnpm vite build ./playground// +pnpm vite preview ./playground// +``` diff --git a/playground/dynamic-import-retry/dynamic-import-vars/dynamic-import-vars.spec.ts b/playground/dynamic-import-retry/dynamic-import-vars/dynamic-import-vars.spec.ts new file mode 100644 index 0000000..f9111ab --- /dev/null +++ b/playground/dynamic-import-retry/dynamic-import-vars/dynamic-import-vars.spec.ts @@ -0,0 +1,67 @@ +import { expect, test } from 'vitest' +import { build, preview } from 'vite' +import { resolve } from 'node:path' + +import { browserLogs, page } from '../../vitestSetup' + +import type { InlineConfig } from 'vite' +import { loadConfigFromFile } from 'vite' +import { beforeAll } from 'vitest' + +let viteTestUrl: string +const testDelay = 100 // delay for the test to take effect +const getJsTimeout = (retries: number) => { + let t = testDelay + for (let i = 0; i < retries; i++) { + t += 1000 * 2 ** (i - 1) + } + return t +} + +beforeAll(async () => { + const res = await loadConfigFromFile( + { + command: 'build', + mode: 'production', + }, + undefined, + resolve(__dirname), + ) + if (!res) throw new Error('Failed to load config') + + const testConfig: InlineConfig = { + ...res.config, + logLevel: 'silent', + configFile: false, + } + + await build(testConfig) + const previewServer = await preview(testConfig) + + viteTestUrl = previewServer.resolvedUrls!.local[0] + await page.goto(viteTestUrl) + + return async () => { + previewServer.close() + } +}) + +test('should work with @rollup/plugin-dynamic-import-vars', async () => { + let loadCount = 0 + const attempts = 2 + await page.route( + (url) => url.pathname.includes('en-') && url.pathname.includes('.js'), + route => { + loadCount++ + if (loadCount < attempts) { + return route.fulfill({ status: 404, body: 'Not Found' }) + } + return route.continue() + }, + ) + await page.click('#btn-vars') + await page.waitForTimeout(getJsTimeout(attempts)) + const log = browserLogs.find(log => log.includes('{title: Hello World}')) + expect(loadCount).toBe(attempts) + expect(log).toBeDefined() +}) diff --git a/playground/dynamic-import-retry/dynamic-import-vars/index.html b/playground/dynamic-import-retry/dynamic-import-vars/index.html new file mode 100644 index 0000000..f52ec54 --- /dev/null +++ b/playground/dynamic-import-retry/dynamic-import-vars/index.html @@ -0,0 +1,12 @@ + + + + + + Dynamic import vars + + + + + + diff --git a/playground/dynamic-import-retry/dynamic-import-vars/src/locales/en.ts b/playground/dynamic-import-retry/dynamic-import-vars/src/locales/en.ts new file mode 100644 index 0000000..42105d2 --- /dev/null +++ b/playground/dynamic-import-retry/dynamic-import-vars/src/locales/en.ts @@ -0,0 +1,3 @@ +export default { + title: 'Hello World', +} diff --git a/playground/dynamic-import-retry/dynamic-import-vars/src/main.ts b/playground/dynamic-import-retry/dynamic-import-vars/src/main.ts new file mode 100644 index 0000000..14709f8 --- /dev/null +++ b/playground/dynamic-import-retry/dynamic-import-vars/src/main.ts @@ -0,0 +1,7 @@ +const btn = document.querySelector('#btn-vars')! + +btn.addEventListener('click', async () => { + const lang = 'en' + const res = await import(`./locales/${lang}.ts`) + console.log(res.default) +}) diff --git a/playground/dynamic-import-retry/dynamic-import-vars/tsconfig.json b/playground/dynamic-import-retry/dynamic-import-vars/tsconfig.json new file mode 100644 index 0000000..2f16ef3 --- /dev/null +++ b/playground/dynamic-import-retry/dynamic-import-vars/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "dist", + "declaration": false, + "declarationDir": null, + "types": ["vite/client"] + }, + "include": ["vite.config.ts.bak"] +} diff --git a/playground/dynamic-import-retry/dynamic-import-vars/vite.config.ts b/playground/dynamic-import-retry/dynamic-import-vars/vite.config.ts new file mode 100644 index 0000000..03da678 --- /dev/null +++ b/playground/dynamic-import-retry/dynamic-import-vars/vite.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import { resolve } from 'node:path' +import dynamicImportVars from '@rollup/plugin-dynamic-import-vars' + +import { DynamicImportRetryPlugin } from '../../../src/plugin-dynamic-import-retry' + +export default defineConfig({ + root: resolve(__dirname), + build: { + // minify: false, + outDir: 'dist', + target: 'esnext', + emptyOutDir: true, + rollupOptions: { + plugins: [ + dynamicImportVars({ + exclude: /node_modules/, + }), + DynamicImportRetryPlugin(), + ], + }, + }, +}) diff --git a/playground/dynamic-import-retry/transitive/index.html b/playground/dynamic-import-retry/transitive/index.html new file mode 100644 index 0000000..df3caa6 --- /dev/null +++ b/playground/dynamic-import-retry/transitive/index.html @@ -0,0 +1,13 @@ + + + + + + Transitive + + + + + + + diff --git a/playground/dynamic-import-retry/transitive/src/main.ts b/playground/dynamic-import-retry/transitive/src/main.ts new file mode 100644 index 0000000..fd0482a --- /dev/null +++ b/playground/dynamic-import-retry/transitive/src/main.ts @@ -0,0 +1,13 @@ +const btnA = document.querySelector('#btn-simple')! +const btnB = document.querySelector('#btn-transitive')! + +btnA.addEventListener('click', async () => { + const { value } = await import('./simple') + console.log(value) +}) + +btnB.addEventListener('click', async () => { + const { value } = await import('./transitive') + console.log(value) +}) + diff --git a/playground/dynamic-import-retry/transitive/src/simple.ts b/playground/dynamic-import-retry/transitive/src/simple.ts new file mode 100644 index 0000000..7fea253 --- /dev/null +++ b/playground/dynamic-import-retry/transitive/src/simple.ts @@ -0,0 +1 @@ +export const value = 'a' diff --git a/playground/dynamic-import-retry/transitive/src/transitive.ts b/playground/dynamic-import-retry/transitive/src/transitive.ts new file mode 100644 index 0000000..bfe82ca --- /dev/null +++ b/playground/dynamic-import-retry/transitive/src/transitive.ts @@ -0,0 +1,2 @@ +import { value as valueA } from './simple' +export const value = valueA + 'b' diff --git a/playground/dynamic-import-retry/transitive/transitive.spec.ts b/playground/dynamic-import-retry/transitive/transitive.spec.ts new file mode 100644 index 0000000..9b9a1ef --- /dev/null +++ b/playground/dynamic-import-retry/transitive/transitive.spec.ts @@ -0,0 +1,74 @@ +import { expect, test } from 'vitest' +import { build, preview } from 'vite' +import { resolve } from 'node:path' + +import { browserLogs, page } from '../../vitestSetup' +import { defaultOptions } from '../../../src/plugin-dynamic-import-retry/index' + +import type { InlineConfig } from 'vite' +import { loadConfigFromFile } from 'vite' +import { beforeAll } from 'vitest' + +let viteTestUrl: string +const maxAttempts = defaultOptions.retries! +const testDelay = 100 // delay for the test to take effect +const getJsTimeout = (retries: number) => { + let t = testDelay + for (let i = 0; i < retries; i++) { + t += 1000 * 2 ** (i - 1) + } + return t +} + +beforeAll(async () => { + const res = await loadConfigFromFile( + { + command: 'build', + mode: 'production', + }, + undefined, + resolve(__dirname), + ) + if (!res) throw new Error('Failed to load config') + + const testConfig: InlineConfig = { + ...res.config, + logLevel: 'silent', + configFile: false, + } + + await build(testConfig) + const previewServer = await preview(testConfig) + + viteTestUrl = previewServer.resolvedUrls!.local[0] + await page.goto(viteTestUrl) + + return async () => { + previewServer.close() + } +}) + +test('should not work on transitive import', async () => { + let subModuleLoadCount = 0 + let entryModuleLoadCount = 0 + await page.route( + (url) => url.pathname.includes('simple') && url.pathname.includes('.js'), + route => { + subModuleLoadCount++ + return route.fulfill({ status: 404, body: 'Not Found' }) + }, + ) + await page.route( + (url) => url.pathname.includes('transitive') && url.pathname.includes('.js'), + route => { + entryModuleLoadCount++ + return route.continue() + }, + ) + await page.click('#btn-transitive') + await page.waitForTimeout(getJsTimeout(maxAttempts)) + expect(entryModuleLoadCount).toBe(4) + expect(subModuleLoadCount).toBe(1) + const e = browserLogs.find(m => m.includes('TypeError: Failed to fetch dynamically imported module')) + expect(e).toBeDefined() +}) diff --git a/playground/dynamic-import-retry/transitive/tsconfig.json b/playground/dynamic-import-retry/transitive/tsconfig.json new file mode 100644 index 0000000..2f16ef3 --- /dev/null +++ b/playground/dynamic-import-retry/transitive/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "dist", + "declaration": false, + "declarationDir": null, + "types": ["vite/client"] + }, + "include": ["vite.config.ts.bak"] +} diff --git a/playground/dynamic-import-retry/transitive/vite.config.ts b/playground/dynamic-import-retry/transitive/vite.config.ts new file mode 100644 index 0000000..5c44e0a --- /dev/null +++ b/playground/dynamic-import-retry/transitive/vite.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { resolve } from 'node:path' + +import { DynamicImportRetryPlugin } from '../../../src/plugin-dynamic-import-retry' + +export default defineConfig({ + root: resolve(__dirname), + plugins: [ + DynamicImportRetryPlugin(), + ], + build: { + // minify: false, + outDir: 'dist', + target: 'esnext', + emptyOutDir: true, + }, +}) diff --git a/playground/dynamic-import-retry/vue-async-component/index.html b/playground/dynamic-import-retry/vue-async-component/index.html new file mode 100644 index 0000000..e763020 --- /dev/null +++ b/playground/dynamic-import-retry/vue-async-component/index.html @@ -0,0 +1,12 @@ + + + + + + Vite + Vue + TS + + +
+ + + diff --git a/playground/dynamic-import-retry/vue-async-component/src/App.vue b/playground/dynamic-import-retry/vue-async-component/src/App.vue new file mode 100644 index 0000000..7e7e4eb --- /dev/null +++ b/playground/dynamic-import-retry/vue-async-component/src/App.vue @@ -0,0 +1,31 @@ + + + diff --git a/playground/dynamic-import-retry/vue-async-component/src/ErrorComponent.vue b/playground/dynamic-import-retry/vue-async-component/src/ErrorComponent.vue new file mode 100644 index 0000000..25cd2ef --- /dev/null +++ b/playground/dynamic-import-retry/vue-async-component/src/ErrorComponent.vue @@ -0,0 +1,3 @@ + diff --git a/playground/dynamic-import-retry/vue-async-component/src/HelloWorld.vue b/playground/dynamic-import-retry/vue-async-component/src/HelloWorld.vue new file mode 100644 index 0000000..7a8044e --- /dev/null +++ b/playground/dynamic-import-retry/vue-async-component/src/HelloWorld.vue @@ -0,0 +1,3 @@ + diff --git a/playground/dynamic-import-retry/vue-async-component/src/LoadingComponent.vue b/playground/dynamic-import-retry/vue-async-component/src/LoadingComponent.vue new file mode 100644 index 0000000..3df7615 --- /dev/null +++ b/playground/dynamic-import-retry/vue-async-component/src/LoadingComponent.vue @@ -0,0 +1,3 @@ + diff --git a/playground/dynamic-import-retry/vue-async-component/src/main.ts b/playground/dynamic-import-retry/vue-async-component/src/main.ts new file mode 100644 index 0000000..e0742a6 --- /dev/null +++ b/playground/dynamic-import-retry/vue-async-component/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App) + .mount('#app') diff --git a/playground/dynamic-import-retry/vue-async-component/tsconfig.json b/playground/dynamic-import-retry/vue-async-component/tsconfig.json new file mode 100644 index 0000000..2f16ef3 --- /dev/null +++ b/playground/dynamic-import-retry/vue-async-component/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "dist", + "declaration": false, + "declarationDir": null, + "types": ["vite/client"] + }, + "include": ["vite.config.ts.bak"] +} diff --git a/playground/dynamic-import-retry/vue-async-component/vite.config.ts b/playground/dynamic-import-retry/vue-async-component/vite.config.ts new file mode 100644 index 0000000..321da25 --- /dev/null +++ b/playground/dynamic-import-retry/vue-async-component/vite.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { resolve } from 'node:path' + +import { DynamicImportRetryPlugin } from '../../../src/plugin-dynamic-import-retry' + +export default defineConfig({ + root: resolve(__dirname), + plugins: [ + vue(), + DynamicImportRetryPlugin(), + ], + build: { + // minify: false, + outDir: 'dist', + target: 'esnext', + emptyOutDir: true, + }, +}) diff --git a/playground/dynamic-import-retry/vue-async-component/vue-async-component.spec.ts b/playground/dynamic-import-retry/vue-async-component/vue-async-component.spec.ts new file mode 100644 index 0000000..0216615 --- /dev/null +++ b/playground/dynamic-import-retry/vue-async-component/vue-async-component.spec.ts @@ -0,0 +1,88 @@ +import { beforeAll, expect, test } from 'vitest' +import { build, preview, loadConfigFromFile } from 'vite' +import { resolve } from 'node:path' + +import { page } from '../../vitestSetup' +import { defaultOptions } from '../../../src/plugin-dynamic-import-retry/index' + +import type { InlineConfig } from 'vite' + +let viteTestUrl: string +const maxAttempts = defaultOptions.retries! +const testDelay = 100 // delay for the test to take effect +const getJsTimeout = (retries: number) => { + let t = testDelay + for (let i = 0; i < retries; i++) { + t += 1000 * 2 ** (i - 1) + } + return t +} + +beforeAll(async () => { + const res = await loadConfigFromFile( + { + command: 'build', + mode: 'production', + }, + undefined, + resolve(__dirname), + ) + if (!res) throw new Error('Failed to load config') + + const testConfig: InlineConfig = { + ...res.config, + logLevel: 'silent', + configFile: false, + } + + await build(testConfig) + const previewServer = await preview(testConfig) + + viteTestUrl = previewServer.resolvedUrls!.local[0] + + return async () => { + previewServer.close() + } +}) + +test('should loading component displayed', async () => { + await page.goto(viteTestUrl) + await page.waitForTimeout(200) + expect(await page.textContent('#app')).toMatch('Loading...') + await page.waitForTimeout(500) + expect(await page.textContent('#app')).toMatch('Hello, World!') +}) + +test('should error component displayed when timeout', async () => { + await page.goto(`${viteTestUrl}?timeout=1`) + expect(await page.textContent('#app')).toMatch('Error') +}) + +test('should recover when dynamic import failed', async () => { + await page.goto(`${viteTestUrl}?latency=0`) + let retries = 0 + const attempts = 2 + await page.route( + (url) => url.pathname.includes('HelloWorld') && url.pathname.includes('.js'), + route => { + retries++ + if (retries < attempts) { + return route.fulfill({ status: 404, body: 'Not Found' }) + } + return route.continue() + }, + ) + await page.waitForTimeout(getJsTimeout(attempts)) + expect(await page.textContent('#app')).toMatch('Hello, World!') + expect(retries).toBe(attempts) +}) + +test('should display error when reach max attempts on dynamic import', async () => { + await page.goto(`${viteTestUrl}?latency=0&timeout=${getJsTimeout(maxAttempts) / 2}`) + await page.route( + (url) => url.pathname.includes('HelloWorld') && url.pathname.includes('.js'), + route => route.fulfill({ status: 404, body: 'Not Found' }), + ) + await page.waitForTimeout(getJsTimeout(maxAttempts)) + expect(await page.textContent('#app')).toMatch('Error') +}) diff --git a/playground/dynamic-import-retry/vue-router/index.html b/playground/dynamic-import-retry/vue-router/index.html new file mode 100644 index 0000000..e763020 --- /dev/null +++ b/playground/dynamic-import-retry/vue-router/index.html @@ -0,0 +1,12 @@ + + + + + + Vite + Vue + TS + + +
+ + + diff --git a/playground/dynamic-import-retry/vue-router/src/AboutView.vue b/playground/dynamic-import-retry/vue-router/src/AboutView.vue new file mode 100644 index 0000000..dec677f --- /dev/null +++ b/playground/dynamic-import-retry/vue-router/src/AboutView.vue @@ -0,0 +1,9 @@ + + + diff --git a/playground/dynamic-import-retry/vue-router/src/App.vue b/playground/dynamic-import-retry/vue-router/src/App.vue new file mode 100644 index 0000000..b4c4806 --- /dev/null +++ b/playground/dynamic-import-retry/vue-router/src/App.vue @@ -0,0 +1,28 @@ + + + diff --git a/playground/dynamic-import-retry/vue-router/src/HomeView.vue b/playground/dynamic-import-retry/vue-router/src/HomeView.vue new file mode 100644 index 0000000..e9b06fe --- /dev/null +++ b/playground/dynamic-import-retry/vue-router/src/HomeView.vue @@ -0,0 +1,9 @@ + + + diff --git a/playground/dynamic-import-retry/vue-router/src/main.ts b/playground/dynamic-import-retry/vue-router/src/main.ts new file mode 100644 index 0000000..febc5c0 --- /dev/null +++ b/playground/dynamic-import-retry/vue-router/src/main.ts @@ -0,0 +1,16 @@ +import { createApp } from 'vue' +import App from './App.vue' + +import { createRouter, createWebHistory } from 'vue-router' + +const router = createRouter({ + history: createWebHistory(), + routes: [ + { path: '/', component: () => import('./HomeView.vue') }, + { path: '/about', component: () => import('./AboutView.vue') }, + ], +}) + +createApp(App) + .use(router) + .mount('#app') diff --git a/playground/dynamic-import-retry/vue-router/tsconfig.json b/playground/dynamic-import-retry/vue-router/tsconfig.json new file mode 100644 index 0000000..2f16ef3 --- /dev/null +++ b/playground/dynamic-import-retry/vue-router/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "dist", + "declaration": false, + "declarationDir": null, + "types": ["vite/client"] + }, + "include": ["vite.config.ts.bak"] +} diff --git a/playground/dynamic-import-retry/vue-router/vite.config.ts b/playground/dynamic-import-retry/vue-router/vite.config.ts new file mode 100644 index 0000000..321da25 --- /dev/null +++ b/playground/dynamic-import-retry/vue-router/vite.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { resolve } from 'node:path' + +import { DynamicImportRetryPlugin } from '../../../src/plugin-dynamic-import-retry' + +export default defineConfig({ + root: resolve(__dirname), + plugins: [ + vue(), + DynamicImportRetryPlugin(), + ], + build: { + // minify: false, + outDir: 'dist', + target: 'esnext', + emptyOutDir: true, + }, +}) diff --git a/playground/dynamic-import-retry/vue-router/vue-router.spec.ts b/playground/dynamic-import-retry/vue-router/vue-router.spec.ts new file mode 100644 index 0000000..094b515 --- /dev/null +++ b/playground/dynamic-import-retry/vue-router/vue-router.spec.ts @@ -0,0 +1,138 @@ +import { beforeAll, expect, test } from 'vitest' +import { build, preview, loadConfigFromFile } from 'vite' +import { resolve } from 'node:path' + +import { page, browserErrors, browserLogs } from '../../vitestSetup' +import { defaultOptions } from '../../../src/plugin-dynamic-import-retry/index' + +import type { InlineConfig } from 'vite' + +let viteTestUrl: string +const maxAttempts = defaultOptions.retries! +const testDelay = 100 // delay for the test to take effect +const getJsTimeout = (retries: number) => { + let t = testDelay + for (let i = 0; i < retries; i++) { + t += 1000 * 2 ** (i - 1) + } + return t +} +const getCssTimeout = (retries: number) => { + let t = testDelay + for (let i = 0; i < retries; i++) { + t += (1 + i) * 200 + } + return t +} + +beforeAll(async () => { + const res = await loadConfigFromFile( + { + command: 'build', + mode: 'production', + }, + undefined, + resolve(__dirname), + ) + if (!res) throw new Error('Failed to load config') + + const testConfig: InlineConfig = { + ...res.config, + logLevel: 'silent', + configFile: false, + } + + await build(testConfig) + const previewServer = await preview(testConfig) + + viteTestUrl = previewServer.resolvedUrls!.local[0] + await page.goto(viteTestUrl) + + return async () => { + previewServer.close() + } +}) + +test('should dynamic import module successful', async () => { + await page.click('#nav-about') + await page.waitForTimeout(testDelay) + expect(await page.textContent('h2')).toMatch('AboutView') +}) + +test('should retry when dynamic import failed', async () => { + await page.goto(viteTestUrl) + let retries = 0 + await page.route( + (url) => url.pathname.includes('AboutView') && url.pathname.includes('.js'), + route => { + retries++ + if (retries < maxAttempts) { + return route.fulfill({ status: 404, body: 'Not Found' }) + } + return route.continue() + }, + ) + await page.click('#nav-about') + await page.waitForTimeout(getJsTimeout(maxAttempts)) + expect(await page.textContent('h2')).toMatch('AboutView') + expect(retries).toBe(maxAttempts) +}) + +test('should has an error when reach max attempts on dynamic import', async () => { + await page.goto(viteTestUrl) + await page.route( + (url) => url.pathname.includes('AboutView') && url.pathname.includes('.js'), + route => route.fulfill({ status: 404, body: 'Not Found' }), + ) + await page.click('#nav-about') + await page.waitForTimeout(getJsTimeout(maxAttempts)) + const e = browserLogs.find(m => m.includes('TypeError: Failed to fetch dynamically imported module')) + expect(e).toBeDefined() + + // can be recovered when network is available + await page.route( + (url) => url.pathname.includes('AboutView') && url.pathname.includes('.js'), + route => route.continue(), + ) + await page.click('#nav-about') + await page.waitForTimeout(getJsTimeout(1)) + expect(await page.textContent('h2')).toMatch('AboutView') +}) + +test('should retry when preload css failed', async () => { + await page.goto(viteTestUrl) + expect(await page.$eval('h2', (el) => getComputedStyle(el).color)).toBe('rgb(255, 0, 0)') + let retries = 0 + const attempts = 2 + await page.route( + (url) => url.pathname.includes('AboutView') && url.pathname.includes('.css'), + route => { + retries++ + if (retries < attempts) { + return route.fulfill({ status: 404, body: 'Not Found' }) + } + return route.continue() + }, + ) + await page.click('#nav-about') + await page.waitForTimeout(getJsTimeout(attempts)) + expect(await page.textContent('h2')).toMatch('AboutView') + expect(await page.$eval('h2', (el) => getComputedStyle(el).color)).toBe('rgb(0, 0, 255)') + expect(retries).toBe(attempts) +}) + +test('should has an error when reach max attempts on preload css', async () => { + await page.goto(viteTestUrl) + expect(await page.$eval('h2', (el) => getComputedStyle(el).color)).toBe('rgb(255, 0, 0)') + await page.route( + (url) => url.pathname.includes('AboutView') && url.pathname.includes('.css'), + route => route.fulfill({ status: 404, body: 'Not Found' }), + ) + await page.click('#nav-about') + await page.waitForTimeout(getCssTimeout(maxAttempts)) + expect(await page.textContent('h2')).toMatch('AboutView') + // should be the default body color + expect(await page.$eval('h2', (el) => getComputedStyle(el).color)).toBe('rgb(0, 0, 0)') + const e = browserErrors.find(e => e.message.includes('[preload-css-retried]')) + expect(e).toBeDefined() +}) diff --git a/playground/vitestGlobalSetup.ts b/playground/vitestGlobalSetup.ts new file mode 100644 index 0000000..6a62e57 --- /dev/null +++ b/playground/vitestGlobalSetup.ts @@ -0,0 +1,25 @@ +import os from 'node:os' +import path from 'node:path' +import fs from 'fs-extra' +import type { BrowserServer } from 'playwright-chromium' +import { chromium } from 'playwright-chromium' + +const DIR = path.join(os.tmpdir(), 'vitest_playwright_global_setup') + +let browserServer: BrowserServer | undefined + +export async function setup(): Promise { + browserServer = await chromium.launchServer({ + headless: !process.env.VITE_DEBUG_SERVE, + args: process.env.CI + ? ['--no-sandbox', '--disable-setuid-sandbox'] + : undefined, + }) + + await fs.mkdirp(DIR) + await fs.writeFile(path.join(DIR, 'wsEndpoint'), browserServer.wsEndpoint()) +} + +export async function teardown(): Promise { + await browserServer?.close() +} diff --git a/playground/vitestSetup.ts b/playground/vitestSetup.ts new file mode 100644 index 0000000..5f07728 --- /dev/null +++ b/playground/vitestSetup.ts @@ -0,0 +1,46 @@ +import { join } from 'node:path' +import os from 'node:os' +import fs from 'fs-extra' +import { chromium } from 'playwright-chromium' +import type { Browser, Page } from 'playwright-chromium' +import { beforeAll } from 'vitest' + +export const browserErrors: Error[] = [] +export const browserLogs: string[] = [] + +export let page: Page = undefined! +export let browser: Browser = undefined! + +const DIR = join(os.tmpdir(), 'vitest_playwright_global_setup') + +beforeAll(async () => { + const wsEndpoint = fs.readFileSync(join(DIR, 'wsEndpoint'), 'utf-8') + if (!wsEndpoint) { + throw new Error('wsEndpoint not found') + } + + browser = await chromium.connect(wsEndpoint) + page = await browser.newPage() + + page.on('console', (msg) => { + // ignore favicon request in headed browser + if ( + process.env.VITE_DEBUG_SERVE && + msg.location().url.includes('favicon.ico') + ) { + return + } + browserLogs.push(msg.text()) + }) + + page.on('pageerror', (error) => { + browserErrors.push(error) + }) + + return async () => { + await page?.close() + if (browser) { + await browser.close() + } + } +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b33e6d..f063237 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,19 @@ settings: importers: .: + dependencies: + '@fatso83/retry-dynamic-import': + specifier: ^2.1.4 + version: 2.1.4 + '@rollup/pluginutils': + specifier: ^5.1.4 + version: 5.1.4(rollup@4.29.1) + acorn-walk: + specifier: ^8.3.4 + version: 8.3.4 + magic-string: + specifier: ^0.30.17 + version: 0.30.17 devDependencies: '@commitlint/cli': specifier: ^19.6.1 @@ -23,21 +36,70 @@ importers: '@kong/eslint-config-kong-ui': specifier: ^1.2.6 version: 1.2.6(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2) + '@rollup/plugin-dynamic-import-vars': + specifier: ^2.1.5 + version: 2.1.5(rollup@4.29.1) + '@types/fs-extra': + specifier: ^11.0.4 + version: 11.0.4 + '@vitejs/plugin-vue': + specifier: ^5.2.1 + version: 5.2.1(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2))(vue@3.5.13(typescript@5.7.2)) '@vitest/ui': specifier: ^2.1.8 version: 2.1.8(vitest@2.1.9) + '@vue/tsconfig': + specifier: ^0.7.0 + version: 0.7.0(typescript@5.7.2)(vue@3.5.13(typescript@5.7.2)) eslint: specifier: ^9.17.0 version: 9.17.0(jiti@2.4.2) + fs-extra: + specifier: ^11.2.0 + version: 11.2.0 + npm-run-all2: + specifier: ^7.0.2 + version: 7.0.2 + playwright-chromium: + specifier: ^1.49.1 + version: 1.49.1 typescript: specifier: ^5.7.2 version: 5.7.2 unbuild: specifier: ^3.2.0 - version: 3.2.0(typescript@5.7.2)(vue-tsc@2.2.0(typescript@5.7.2)) + version: 3.2.0(typescript@5.7.2)(vue-tsc@2.2.0(typescript@5.7.2))(vue@3.5.13(typescript@5.7.2)) + vite: + specifier: ^6.0.7 + version: 6.0.7(@types/node@22.10.5)(jiti@2.4.2) vitest: specifier: ^2.1.8 - version: 2.1.9(@types/node@22.10.5)(@vitest/ui@2.1.8) + version: 2.1.9(@types/node@22.10.5)(@vitest/ui@2.1.8)(jsdom@25.0.1)(msw@2.7.0(@types/node@22.10.5)(typescript@5.7.2)) + vue: + specifier: ^3.5.13 + version: 3.5.13(typescript@5.7.2) + vue-router: + specifier: ^4.5.0 + version: 4.5.0(vue@3.5.13(typescript@5.7.2)) + vue-tsc: + specifier: ^2.2.0 + version: 2.2.0(typescript@5.7.2) + + playground/vue: + dependencies: + vue: + specifier: ^3.5.13 + version: 3.5.13(typescript@5.7.2) + vue-router: + specifier: ^4.5.0 + version: 4.5.0(vue@3.5.13(typescript@5.7.2)) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^5.2.1 + version: 5.2.1(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2))(vue@3.5.13(typescript@5.7.2)) + '@vue/tsconfig': + specifier: ^0.7.0 + version: 0.7.0(typescript@5.7.2)(vue@3.5.13(typescript@5.7.2)) vue-tsc: specifier: ^2.2.0 version: 2.2.0(typescript@5.7.2) @@ -115,6 +177,15 @@ packages: resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} engines: {node: '>=6.9.0'} + '@bundled-es-modules/cookie@2.0.1': + resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} + + '@bundled-es-modules/statuses@1.0.1': + resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} + + '@bundled-es-modules/tough-cookie@0.1.6': + resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} + '@commitlint/cli@19.6.1': resolution: {integrity: sha512-8hcyA6ZoHwWXC76BoC8qVOSr8xHy00LZhZpauiD0iO0VYbVhMnED0da85lTfIULxl7Lj4c6vZgF0Wu/ed1+jlQ==} engines: {node: '>=v18'} @@ -525,10 +596,12 @@ packages: '@evilmartians/lefthook@1.10.1': resolution: {integrity: sha512-G1NPLB4yLYYEyz8oH7yWgHsxUiF546aS1ChSRPNFPSosLxZFM8wqDxek/J9sT447v83gJbKdrnstxeQW/9CIRA==} - cpu: [x64, arm64, ia32] os: [darwin, linux, win32] hasBin: true + '@fatso83/retry-dynamic-import@2.1.4': + resolution: {integrity: sha512-C/fTmz28GWWBJuo/RJm6hR8hPgsOp7wqxYbS572uPjlapUebPfaX9r1c+5rQHHuwYo4goASmvG50q++q26q8NQ==} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -549,6 +622,26 @@ packages: resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} engines: {node: '>=18.18'} + '@inquirer/confirm@5.1.1': + resolution: {integrity: sha512-vVLSbGci+IKQvDOtzpPTCOiEJCNidHcAq9JYVoWTW0svb5FiwSLotkM+JXNXejfjnzVYV9n0DTBythl9+XgTxg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + + '@inquirer/core@10.1.2': + resolution: {integrity: sha512-bHd96F3ezHg1mf/J0Rb4CV8ndCN0v28kUlrHqP7+ECm1C/A+paB7Xh2lbMk6x+kweQC+rZOxM/YeKikzxco8bQ==} + engines: {node: '>=18'} + + '@inquirer/figures@1.0.9': + resolution: {integrity: sha512-BXvGj0ehzrngHTPTDqUoDT3NXL8U0RxUk2zJm2A66RhCEIWdtU1v6GuUqNAgArW4PQ9CinqIWyHdQgdwOj06zQ==} + engines: {node: '>=18'} + + '@inquirer/type@3.0.2': + resolution: {integrity: sha512-ZhQ4TvhwHZF+lGhQ2O/rsjo80XoZR5/5qhOY3t6FJuX5XBg5Be8YzYTvaUGJnc12AUGI2nr4QSUE4PhKSigx7g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + '@jridgewell/gen-mapping@0.3.8': resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} @@ -573,6 +666,10 @@ packages: peerDependencies: eslint: '>= 9.18.0' + '@mswjs/interceptors@0.37.5': + resolution: {integrity: sha512-AAwRb5vXFcY4L+FvZ7LZusDuZ0vEe0Zm8ohn1FM6/X7A3bj4mqmkAcGRWuvC2JwSygNwHAAmMnAI73vPHeqsHA==} + engines: {node: '>=18'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -585,6 +682,15 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@polka/url@1.0.0-next.28': resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} @@ -606,6 +712,15 @@ packages: rollup: optional: true + '@rollup/plugin-dynamic-import-vars@2.1.5': + resolution: {integrity: sha512-Mymi24fd9hlRifdZV/jYIFj1dn99F34imiYu3KzlAcgBcRi3i9SucgW/VRo5SQ9K4NuQ7dCep6pFWgNyhRdFHQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@rollup/plugin-json@6.1.0': resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} engines: {node: '>=14.0.0'} @@ -845,18 +960,33 @@ packages: '@types/conventional-commits-parser@5.0.1': resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==} + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/fs-extra@11.0.4': + resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/jsonfile@6.1.4': + resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + '@types/node@22.10.5': resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + '@types/statuses@2.0.5': + resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@typescript-eslint/eslint-plugin@8.24.0': resolution: {integrity: sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -904,6 +1034,13 @@ packages: resolution: {integrity: sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitejs/plugin-vue@5.2.1': + resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + '@vitest/expect@2.1.9': resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} @@ -959,9 +1096,18 @@ packages: '@vue/compiler-dom@3.5.13': resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} + '@vue/compiler-sfc@3.5.13': + resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} + + '@vue/compiler-ssr@3.5.13': + resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} + '@vue/compiler-vue2@2.7.16': resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + '@vue/language-core@2.2.0': resolution: {integrity: sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==} peerDependencies: @@ -970,9 +1116,34 @@ packages: typescript: optional: true + '@vue/reactivity@3.5.13': + resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} + + '@vue/runtime-core@3.5.13': + resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==} + + '@vue/runtime-dom@3.5.13': + resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==} + + '@vue/server-renderer@3.5.13': + resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==} + peerDependencies: + vue: 3.5.13 + '@vue/shared@3.5.13': resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} + '@vue/tsconfig@0.7.0': + resolution: {integrity: sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==} + peerDependencies: + typescript: 5.x + vue: ^3.4.0 + peerDependenciesMeta: + typescript: + optional: true + vue: + optional: true + JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -982,11 +1153,19 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + acorn@8.14.0: resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1015,6 +1194,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1025,6 +1208,13 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + at-least-node@1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} @@ -1134,6 +1324,10 @@ packages: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -1158,6 +1352,10 @@ packages: colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} @@ -1202,6 +1400,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + cosmiconfig-typescript-loader@6.1.0: resolution: {integrity: sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==} engines: {node: '>=v18'} @@ -1271,6 +1473,13 @@ packages: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + cssstyle@4.1.0: + resolution: {integrity: sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==} + engines: {node: '>=18'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + cz-conventional-changelog@3.3.0: resolution: {integrity: sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw==} engines: {node: '>= 10'} @@ -1279,6 +1488,10 @@ packages: resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==} engines: {node: '>=12'} + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + de-indent@1.0.2: resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} @@ -1291,6 +1504,9 @@ packages: supports-color: optional: true + decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} @@ -1311,6 +1527,10 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + detect-file@1.0.0: resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} engines: {node: '>=0.10.0'} @@ -1575,9 +1795,17 @@ packages: flatted@3.3.2: resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + fs-extra@11.2.0: + resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} + engines: {node: '>=14.14'} + fs-extra@9.1.0: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} engines: {node: '>=10'} @@ -1655,6 +1883,10 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + graphql@16.10.0: + resolution: {integrity: sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -1671,6 +1903,9 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + homedir-polyfill@1.0.3: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} engines: {node: '>=0.10.0'} @@ -1678,10 +1913,26 @@ packages: hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -1748,6 +1999,9 @@ packages: is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -1756,6 +2010,9 @@ packages: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} @@ -1777,6 +2034,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true @@ -1792,6 +2053,15 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsdom@25.0.1: + resolution: {integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1803,6 +2073,10 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-parse-even-better-errors@4.0.0: + resolution: {integrity: sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==} + engines: {node: ^18.17.0 || >=20.5.0} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -1897,9 +2171,6 @@ packages: resolution: {integrity: sha512-Ajzxb8CM6WAnFjgiloPsI3bF+WCxcvhdIG3KNA2KN962+tdBsHcuQ4k4qX/EcS/2CRkcc0iAkR956Nib6aXU/Q==} engines: {node: '>=0.10.0'} - loupe@3.1.2: - resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} - loupe@3.1.3: resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} @@ -1915,6 +2186,10 @@ packages: mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + meow@12.1.1: resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} engines: {node: '>=16.10'} @@ -1930,6 +2205,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -1975,12 +2258,26 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msw@2.7.0: + resolution: {integrity: sha512-BIodwZ19RWfCbYTxWTUfTXc+sg4OwjCAgxU1ZsgmggX/7S3LdUifsbUPJs61j0rWb19CZRGY5if77duhc0uXzw==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + nanoid@3.3.8: resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -1996,9 +2293,21 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + npm-normalize-package-bin@4.0.0: + resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==} + engines: {node: ^18.17.0 || >=20.5.0} + + npm-run-all2@7.0.2: + resolution: {integrity: sha512-7tXR+r9hzRNOPNTvXegM+QzCuMjzUIIq66VDunL6j60O4RrExx32XUhlrS7UK4VcdGw5/Wxzb3kfNcFix9JKDA==} + engines: {node: ^18.17.0 || >=20.5.0, npm: '>= 9'} + hasBin: true + nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nwsapi@2.2.16: + resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -2018,6 +2327,9 @@ packages: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -2046,6 +2358,9 @@ packages: resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} engines: {node: '>=0.10.0'} + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -2068,6 +2383,9 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -2086,9 +2404,24 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + pkg-types@1.3.0: resolution: {integrity: sha512-kS7yWjVFCkIw9hqdJBoMxDdzEngmkr5FXeWZZfQ6GoYacjVnsW6l2CcYW/0ThD0vF4LPJgVYnrg4d0uuhwYQbg==} + playwright-chromium@1.49.1: + resolution: {integrity: sha512-XAQDkZ1Eem1OONhfS8B2LM2mgHG/i5jIxooxjvqjbF/9GnLnRTJHdQamNjo1e4FZvt7J0BFD/15+qAcT0eKlfA==} + engines: {node: '>=18'} + hasBin: true + + playwright-core@1.49.1: + resolution: {integrity: sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==} + engines: {node: '>=18'} + hasBin: true + postcss-calc@10.0.2: resolution: {integrity: sha512-DT/Wwm6fCKgpYVI7ZEWuPJ4az8hiEHtCUeYjZXqU7Ou4QqYh1Df2yCQ7Ca6N7xqKPFkxN3fhf+u9KSoOCJNAjg==} engines: {node: ^18.12 || ^20.9 || >=22.0} @@ -2284,13 +2617,27 @@ packages: resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} engines: {node: ^14.13.1 || >=16.0.0} + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} + engines: {node: '>=0.10.0'} + + read-package-json-fast@4.0.0: + resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==} + engines: {node: ^18.17.0 || >=20.5.0} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -2303,6 +2650,9 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resolve-dir@1.0.1: resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} engines: {node: '>=0.10.0'} @@ -2353,6 +2703,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rrweb-cssom@0.7.1: + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -2369,6 +2722,10 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} @@ -2389,12 +2746,20 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-quote@1.8.2: + resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + sirv@3.0.0: resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==} engines: {node: '>=18'} @@ -2410,9 +2775,16 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + std-env@3.8.0: resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2455,6 +2827,9 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + synckit@0.6.2: resolution: {integrity: sha512-Vhf+bUa//YSTYKseDiiEuQmhGCoIF3CVBhunm3r/DQnYiGT4JssmnKQc44BIyOZRK2pKjXXAgbhfmbeoC9CJpA==} engines: {node: '>=12.20'} @@ -2492,6 +2867,13 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} + tldts-core@6.1.70: + resolution: {integrity: sha512-RNnIXDB1FD4T9cpQRErEqw6ZpjLlGdMOitdV+0xtbsnwr4YFka1zpc7D4KD+aAn8oSG5JyFrdasZTE04qDE9Yg==} + + tldts@6.1.70: + resolution: {integrity: sha512-/W1YVgYVJd9ZDjey5NXadNh0mJXkiUMUue9Zebd0vpdo1sU+H4zFFTaJ1RKD4N6KFoHfcXy6l+Vu7bh+bdWCzA==} + hasBin: true + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -2504,6 +2886,18 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + + tough-cookie@5.0.0: + resolution: {integrity: sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==} + engines: {node: '>=16'} + + tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} + ts-api-utils@2.0.1: resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==} engines: {node: '>=18.12'} @@ -2525,6 +2919,10 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} + type-fest@4.31.0: + resolution: {integrity: sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ==} + engines: {node: '>=16'} + typescript-eslint@8.24.0: resolution: {integrity: sha512-/lmv4366en/qbB32Vz5+kCNZEMf6xYHwh1z48suBwZvAtnXKbP+YhGe8OLE2BqC67LMqKkCNLtjejdwsdW6uOQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2556,6 +2954,10 @@ packages: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -2573,6 +2975,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -2612,6 +3017,46 @@ packages: terser: optional: true + vite@6.0.7: + resolution: {integrity: sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + 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 + 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 + vitest@2.1.9: resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} engines: {node: ^18.0.0 || >=20.0.0} @@ -2646,15 +3091,48 @@ packages: peerDependencies: eslint: '>=6.0.0' + vue-router@4.5.0: + resolution: {integrity: sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==} + peerDependencies: + vue: ^3.2.0 + vue-tsc@2.2.0: resolution: {integrity: sha512-gtmM1sUuJ8aSb0KoAFmK9yMxb8TxjewmxqTJ1aKphD5Cbu0rULFY6+UQT51zW7SpUcenfPUuflKyVwyx9Qdnxg==} hasBin: true peerDependencies: typescript: '>=5.0.0' + vue@3.5.13: + resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.1.0: + resolution: {integrity: sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==} + engines: {node: '>=18'} + which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true @@ -2664,6 +3142,11 @@ packages: engines: {node: '>= 8'} hasBin: true + which@5.0.0: + resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -2688,10 +3171,29 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -2715,6 +3217,10 @@ packages: resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} engines: {node: '>=12.20'} + yoctocolors-cjs@2.1.2: + resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + engines: {node: '>=18'} + snapshots: '@ampproject/remapping@2.3.0': @@ -2822,6 +3328,22 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 + '@bundled-es-modules/cookie@2.0.1': + dependencies: + cookie: 0.7.2 + optional: true + + '@bundled-es-modules/statuses@1.0.1': + dependencies: + statuses: 2.0.1 + optional: true + + '@bundled-es-modules/tough-cookie@0.1.6': + dependencies: + '@types/tough-cookie': 4.0.5 + tough-cookie: 4.1.4 + optional: true + '@commitlint/cli@19.6.1(@types/node@22.10.5)(typescript@5.7.2)': dependencies: '@commitlint/format': 19.5.0 @@ -3142,6 +3664,10 @@ snapshots: '@evilmartians/lefthook@1.10.1': {} + '@fatso83/retry-dynamic-import@2.1.4': + optionalDependencies: + react: 19.0.0 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': @@ -3155,6 +3681,36 @@ snapshots: '@humanwhocodes/retry@0.4.1': {} + '@inquirer/confirm@5.1.1(@types/node@22.10.5)': + dependencies: + '@inquirer/core': 10.1.2(@types/node@22.10.5) + '@inquirer/type': 3.0.2(@types/node@22.10.5) + '@types/node': 22.10.5 + optional: true + + '@inquirer/core@10.1.2(@types/node@22.10.5)': + dependencies: + '@inquirer/figures': 1.0.9 + '@inquirer/type': 3.0.2(@types/node@22.10.5) + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + transitivePeerDependencies: + - '@types/node' + optional: true + + '@inquirer/figures@1.0.9': + optional: true + + '@inquirer/type@3.0.2(@types/node@22.10.5)': + dependencies: + '@types/node': 22.10.5 + optional: true + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 @@ -3193,6 +3749,16 @@ snapshots: - supports-color - typescript + '@mswjs/interceptors@0.37.5': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3205,6 +3771,18 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.18.0 + '@open-draft/deferred-promise@2.2.0': + optional: true + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + optional: true + + '@open-draft/until@2.1.0': + optional: true + '@polka/url@1.0.0-next.28': {} '@rollup/plugin-alias@5.1.1(rollup@4.29.1)': @@ -3223,6 +3801,16 @@ snapshots: optionalDependencies: rollup: 4.29.1 + '@rollup/plugin-dynamic-import-vars@2.1.5(rollup@4.29.1)': + dependencies: + '@rollup/pluginutils': 5.1.4(rollup@4.29.1) + astring: 1.9.0 + estree-walker: 2.0.2 + fast-glob: 3.3.2 + magic-string: 0.30.17 + optionalDependencies: + rollup: 4.29.1 + '@rollup/plugin-json@6.1.0(rollup@4.29.1)': dependencies: '@rollup/pluginutils': 5.1.4(rollup@4.29.1) @@ -3386,16 +3974,34 @@ snapshots: dependencies: '@types/node': 22.10.5 + '@types/cookie@0.6.0': + optional: true + '@types/estree@1.0.6': {} + '@types/fs-extra@11.0.4': + dependencies: + '@types/jsonfile': 6.1.4 + '@types/node': 22.10.5 + '@types/json-schema@7.0.15': {} + '@types/jsonfile@6.1.4': + dependencies: + '@types/node': 22.10.5 + '@types/node@22.10.5': dependencies: undici-types: 6.20.0 '@types/resolve@1.20.2': {} + '@types/statuses@2.0.5': + optional: true + + '@types/tough-cookie@4.0.5': + optional: true + '@typescript-eslint/eslint-plugin@8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -3473,6 +4079,11 @@ snapshots: '@typescript-eslint/types': 8.24.0 eslint-visitor-keys: 4.2.0 + '@vitejs/plugin-vue@5.2.1(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2))(vue@3.5.13(typescript@5.7.2))': + dependencies: + vite: 6.0.7(@types/node@22.10.5)(jiti@2.4.2) + vue: 3.5.13(typescript@5.7.2) + '@vitest/expect@2.1.9': dependencies: '@vitest/spy': 2.1.9 @@ -3480,12 +4091,13 @@ snapshots: chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.9(vite@5.4.14(@types/node@22.10.5))': + '@vitest/mocker@2.1.9(msw@2.7.0(@types/node@22.10.5)(typescript@5.7.2))(vite@5.4.14(@types/node@22.10.5))': dependencies: '@vitest/spy': 2.1.9 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: + msw: 2.7.0(@types/node@22.10.5)(typescript@5.7.2) vite: 5.4.14(@types/node@22.10.5) '@vitest/pretty-format@2.1.8': @@ -3520,12 +4132,12 @@ snapshots: sirv: 3.0.0 tinyglobby: 0.2.10 tinyrainbow: 1.2.0 - vitest: 2.1.9(@types/node@22.10.5)(@vitest/ui@2.1.8) + vitest: 2.1.9(@types/node@22.10.5)(@vitest/ui@2.1.8)(jsdom@25.0.1)(msw@2.7.0(@types/node@22.10.5)(typescript@5.7.2)) '@vitest/utils@2.1.8': dependencies: '@vitest/pretty-format': 2.1.8 - loupe: 3.1.2 + loupe: 3.1.3 tinyrainbow: 1.2.0 '@vitest/utils@2.1.9': @@ -3559,11 +4171,30 @@ snapshots: '@vue/compiler-core': 3.5.13 '@vue/shared': 3.5.13 + '@vue/compiler-sfc@3.5.13': + dependencies: + '@babel/parser': 7.26.3 + '@vue/compiler-core': 3.5.13 + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + estree-walker: 2.0.2 + magic-string: 0.30.17 + postcss: 8.4.49 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.13': + dependencies: + '@vue/compiler-dom': 3.5.13 + '@vue/shared': 3.5.13 + '@vue/compiler-vue2@2.7.16': dependencies: de-indent: 1.0.2 he: 1.2.0 + '@vue/devtools-api@6.6.4': {} + '@vue/language-core@2.2.0(typescript@5.7.2)': dependencies: '@volar/language-core': 2.4.11 @@ -3577,8 +4208,35 @@ snapshots: optionalDependencies: typescript: 5.7.2 + '@vue/reactivity@3.5.13': + dependencies: + '@vue/shared': 3.5.13 + + '@vue/runtime-core@3.5.13': + dependencies: + '@vue/reactivity': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/runtime-dom@3.5.13': + dependencies: + '@vue/reactivity': 3.5.13 + '@vue/runtime-core': 3.5.13 + '@vue/shared': 3.5.13 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.7.2))': + dependencies: + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + vue: 3.5.13(typescript@5.7.2) + '@vue/shared@3.5.13': {} + '@vue/tsconfig@0.7.0(typescript@5.7.2)(vue@3.5.13(typescript@5.7.2))': + optionalDependencies: + typescript: 5.7.2 + vue: 3.5.13(typescript@5.7.2) + JSONStream@1.3.5: dependencies: jsonparse: 1.3.1 @@ -3588,8 +4246,15 @@ snapshots: dependencies: acorn: 8.14.0 + acorn-walk@8.3.4: + dependencies: + acorn: 8.14.0 + acorn@8.14.0: {} + agent-base@7.1.3: + optional: true + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -3624,12 +4289,19 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@6.2.1: {} + argparse@2.0.1: {} array-ify@1.0.0: {} assertion-error@2.0.1: {} + astring@1.9.0: {} + + asynckit@0.4.0: + optional: true + at-least-node@1.0.0: {} autoprefixer@10.4.20(postcss@8.4.49): @@ -3746,6 +4418,9 @@ snapshots: cli-width@3.0.0: {} + cli-width@4.1.0: + optional: true + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -3768,6 +4443,11 @@ snapshots: colord@2.9.3: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + optional: true + commander@7.2.0: {} commitizen@4.3.1(@types/node@22.10.5)(typescript@5.7.2): @@ -3822,6 +4502,9 @@ snapshots: convert-source-map@2.0.0: {} + cookie@0.7.2: + optional: true + cosmiconfig-typescript-loader@6.1.0(@types/node@22.10.5)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2): dependencies: '@types/node': 22.10.5 @@ -3918,6 +4601,13 @@ snapshots: dependencies: css-tree: 2.2.1 + cssstyle@4.1.0: + dependencies: + rrweb-cssom: 0.7.1 + optional: true + + csstype@3.1.3: {} + cz-conventional-changelog@3.3.0(@types/node@22.10.5)(typescript@5.7.2): dependencies: chalk: 2.4.2 @@ -3934,12 +4624,21 @@ snapshots: dargs@8.1.0: {} + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.1.0 + optional: true + de-indent@1.0.2: {} debug@4.4.0: dependencies: ms: 2.1.3 + decimal.js@10.4.3: + optional: true + dedent@0.7.0: {} deep-eql@5.0.2: {} @@ -3954,6 +4653,9 @@ snapshots: defu@6.1.4: {} + delayed-stream@1.0.0: + optional: true + detect-file@1.0.0: {} detect-indent@6.1.0: {} @@ -4298,8 +5000,21 @@ snapshots: flatted@3.3.2: {} + form-data@4.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + optional: true + fraction.js@4.3.7: {} + fs-extra@11.2.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + fs-extra@9.1.0: dependencies: at-least-node: 1.0.0 @@ -4377,6 +5092,9 @@ snapshots: graphemer@1.4.0: {} + graphql@16.10.0: + optional: true + has-flag@3.0.0: {} has-flag@4.0.0: {} @@ -4387,16 +5105,45 @@ snapshots: he@1.2.0: {} + headers-polyfill@4.0.3: + optional: true + homedir-polyfill@1.0.3: dependencies: parse-passwd: 1.0.0 hookable@5.5.3: {} + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + optional: true + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + optional: true + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + optional: true + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + optional: true + ieee754@1.2.1: {} ignore@5.3.2: {} @@ -4475,10 +5222,16 @@ snapshots: is-module@1.0.0: {} + is-node-process@1.2.0: + optional: true + is-number@7.0.0: {} is-obj@2.0.0: {} + is-potential-custom-element-name@1.0.1: + optional: true + is-reference@1.2.1: dependencies: '@types/estree': 1.0.6 @@ -4495,6 +5248,8 @@ snapshots: isexe@2.0.0: {} + isexe@3.1.1: {} + jiti@1.21.7: {} jiti@2.4.2: {} @@ -4505,12 +5260,43 @@ snapshots: dependencies: argparse: 2.0.1 + jsdom@25.0.1: + dependencies: + cssstyle: 4.1.0 + data-urls: 5.0.0 + decimal.js: 10.4.3 + form-data: 4.0.1 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.16 + parse5: 7.2.1 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.0.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.1.0 + ws: 8.18.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + optional: true + jsesc@3.1.0: {} json-buffer@3.0.1: {} json-parse-even-better-errors@2.3.1: {} + json-parse-even-better-errors@4.0.0: {} + json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -4588,8 +5374,6 @@ snapshots: longest@2.0.1: {} - loupe@3.1.2: {} - loupe@3.1.3: {} lru-cache@5.1.1: @@ -4604,6 +5388,8 @@ snapshots: mdn-data@2.0.30: {} + memorystream@0.3.1: {} + meow@12.1.1: {} merge2@1.4.1: {} @@ -4615,6 +5401,14 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: + optional: true + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + optional: true + mimic-fn@2.1.0: {} minimatch@3.1.2: @@ -4629,7 +5423,7 @@ snapshots: minimist@1.2.8: {} - mkdist@2.2.0(typescript@5.7.2)(vue-tsc@2.2.0(typescript@5.7.2)): + mkdist@2.2.0(typescript@5.7.2)(vue-tsc@2.2.0(typescript@5.7.2))(vue@3.5.13(typescript@5.7.2)): dependencies: autoprefixer: 10.4.20(postcss@8.4.49) citty: 0.1.6 @@ -4646,6 +5440,7 @@ snapshots: tinyglobby: 0.2.10 optionalDependencies: typescript: 5.7.2 + vue: 3.5.13(typescript@5.7.2) vue-tsc: 2.2.0(typescript@5.7.2) mlly@1.7.3: @@ -4659,10 +5454,39 @@ snapshots: ms@2.1.3: {} + msw@2.7.0(@types/node@22.10.5)(typescript@5.7.2): + dependencies: + '@bundled-es-modules/cookie': 2.0.1 + '@bundled-es-modules/statuses': 1.0.1 + '@bundled-es-modules/tough-cookie': 0.1.6 + '@inquirer/confirm': 5.1.1(@types/node@22.10.5) + '@mswjs/interceptors': 0.37.5 + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.5 + graphql: 16.10.0 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + strict-event-emitter: 0.5.1 + type-fest: 4.31.0 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.7.2 + transitivePeerDependencies: + - '@types/node' + optional: true + muggle-string@0.4.1: {} mute-stream@0.0.8: {} + mute-stream@2.0.0: + optional: true + nanoid@3.3.8: {} natural-compare@1.4.0: {} @@ -4671,10 +5495,26 @@ snapshots: normalize-range@0.1.2: {} + npm-normalize-package-bin@4.0.0: {} + + npm-run-all2@7.0.2: + dependencies: + ansi-styles: 6.2.1 + cross-spawn: 7.0.6 + memorystream: 0.3.1 + minimatch: 9.0.5 + pidtree: 0.6.0 + read-package-json-fast: 4.0.0 + shell-quote: 1.8.2 + which: 5.0.0 + nth-check@2.1.1: dependencies: boolbase: 1.0.0 + nwsapi@2.2.16: + optional: true + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -4706,6 +5546,9 @@ snapshots: os-tmpdir@1.0.2: {} + outvariant@1.4.3: + optional: true + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -4735,6 +5578,11 @@ snapshots: parse-passwd@1.0.0: {} + parse5@7.2.1: + dependencies: + entities: 4.5.0 + optional: true + path-browserify@1.0.1: {} path-exists@4.0.0: {} @@ -4747,6 +5595,9 @@ snapshots: path-parse@1.0.7: {} + path-to-regexp@6.3.0: + optional: true + pathe@1.1.2: {} pathval@2.0.0: {} @@ -4757,12 +5608,20 @@ snapshots: picomatch@4.0.2: {} + pidtree@0.6.0: {} + pkg-types@1.3.0: dependencies: confbox: 0.1.8 mlly: 1.7.3 pathe: 1.1.2 + playwright-chromium@1.49.1: + dependencies: + playwright-core: 1.49.1 + + playwright-core@1.49.1: {} + postcss-calc@10.0.2(postcss@8.4.49): dependencies: postcss: 8.4.49 @@ -4945,10 +5804,26 @@ snapshots: pretty-bytes@6.1.1: {} + psl@1.15.0: + dependencies: + punycode: 2.3.1 + optional: true + punycode@2.3.1: {} + querystringify@2.2.0: + optional: true + queue-microtask@1.2.3: {} + react@19.0.0: + optional: true + + read-package-json-fast@4.0.0: + dependencies: + json-parse-even-better-errors: 4.0.0 + npm-normalize-package-bin: 4.0.0 + readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -4959,6 +5834,9 @@ snapshots: require-from-string@2.0.2: {} + requires-port@1.0.0: + optional: true + resolve-dir@1.0.1: dependencies: expand-tilde: 2.0.2 @@ -5043,6 +5921,9 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.34.2 fsevents: 2.3.3 + rrweb-cssom@0.7.1: + optional: true + run-async@2.4.1: {} run-parallel@1.2.0: @@ -5057,6 +5938,11 @@ snapshots: safer-buffer@2.1.2: {} + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + optional: true + scule@1.3.0: {} semver@6.3.1: {} @@ -5069,10 +5955,15 @@ snapshots: shebang-regex@3.0.0: {} + shell-quote@1.8.2: {} + siginfo@2.0.0: {} signal-exit@3.0.7: {} + signal-exit@4.1.0: + optional: true + sirv@3.0.0: dependencies: '@polka/url': 1.0.0-next.28 @@ -5085,8 +5976,14 @@ snapshots: stackback@0.0.2: {} + statuses@2.0.1: + optional: true + std-env@3.8.0: {} + strict-event-emitter@0.5.1: + optional: true + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -5131,6 +6028,9 @@ snapshots: csso: 5.0.5 picocolors: 1.1.1 + symbol-tree@3.2.4: + optional: true + synckit@0.6.2: dependencies: tslib: 2.8.1 @@ -5156,6 +6056,14 @@ snapshots: tinyspy@3.0.2: {} + tldts-core@6.1.70: + optional: true + + tldts@6.1.70: + dependencies: + tldts-core: 6.1.70 + optional: true + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -5166,6 +6074,24 @@ snapshots: totalist@3.0.1: {} + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + optional: true + + tough-cookie@5.0.0: + dependencies: + tldts: 6.1.70 + optional: true + + tr46@5.0.0: + dependencies: + punycode: 2.3.1 + optional: true + ts-api-utils@2.0.1(typescript@5.7.2): dependencies: typescript: 5.7.2 @@ -5180,6 +6106,9 @@ snapshots: type-fest@0.21.3: {} + type-fest@4.31.0: + optional: true + typescript-eslint@8.24.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2): dependencies: '@typescript-eslint/eslint-plugin': 8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2) @@ -5194,7 +6123,7 @@ snapshots: ufo@1.5.4: {} - unbuild@3.2.0(typescript@5.7.2)(vue-tsc@2.2.0(typescript@5.7.2)): + unbuild@3.2.0(typescript@5.7.2)(vue-tsc@2.2.0(typescript@5.7.2))(vue@3.5.13(typescript@5.7.2)): dependencies: '@rollup/plugin-alias': 5.1.1(rollup@4.29.1) '@rollup/plugin-commonjs': 28.0.2(rollup@4.29.1) @@ -5209,7 +6138,7 @@ snapshots: hookable: 5.5.3 jiti: 2.4.2 magic-string: 0.30.17 - mkdist: 2.2.0(typescript@5.7.2)(vue-tsc@2.2.0(typescript@5.7.2)) + mkdist: 2.2.0(typescript@5.7.2)(vue-tsc@2.2.0(typescript@5.7.2))(vue@3.5.13(typescript@5.7.2)) mlly: 1.7.3 pathe: 1.1.2 pkg-types: 1.3.0 @@ -5232,6 +6161,9 @@ snapshots: unicorn-magic@0.1.0: {} + universalify@0.2.0: + optional: true + universalify@2.0.1: {} untyped@1.5.2: @@ -5257,6 +6189,12 @@ snapshots: dependencies: punycode: 2.3.1 + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + optional: true + util-deprecate@1.0.2: {} vite-node@2.1.9(@types/node@22.10.5): @@ -5286,10 +6224,20 @@ snapshots: '@types/node': 22.10.5 fsevents: 2.3.3 - vitest@2.1.9(@types/node@22.10.5)(@vitest/ui@2.1.8): + vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2): + dependencies: + esbuild: 0.24.2 + postcss: 8.4.49 + rollup: 4.29.1 + optionalDependencies: + '@types/node': 22.10.5 + fsevents: 2.3.3 + jiti: 2.4.2 + + vitest@2.1.9(@types/node@22.10.5)(@vitest/ui@2.1.8)(jsdom@25.0.1)(msw@2.7.0(@types/node@22.10.5)(typescript@5.7.2)): dependencies: '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.14(@types/node@22.10.5)) + '@vitest/mocker': 2.1.9(msw@2.7.0(@types/node@22.10.5)(typescript@5.7.2))(vite@5.4.14(@types/node@22.10.5)) '@vitest/pretty-format': 2.1.9 '@vitest/runner': 2.1.9 '@vitest/snapshot': 2.1.9 @@ -5311,6 +6259,7 @@ snapshots: optionalDependencies: '@types/node': 22.10.5 '@vitest/ui': 2.1.8(vitest@2.1.9) + jsdom: 25.0.1 transitivePeerDependencies: - less - lightningcss @@ -5337,16 +6286,53 @@ snapshots: transitivePeerDependencies: - supports-color + vue-router@4.5.0(vue@3.5.13(typescript@5.7.2)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.13(typescript@5.7.2) + vue-tsc@2.2.0(typescript@5.7.2): dependencies: '@volar/typescript': 2.4.11 '@vue/language-core': 2.2.0(typescript@5.7.2) typescript: 5.7.2 + vue@3.5.13(typescript@5.7.2): + dependencies: + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-sfc': 3.5.13 + '@vue/runtime-dom': 3.5.13 + '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.7.2)) + '@vue/shared': 3.5.13 + optionalDependencies: + typescript: 5.7.2 + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + optional: true + wcwidth@1.0.1: dependencies: defaults: 1.0.4 + webidl-conversions@7.0.0: + optional: true + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + optional: true + + whatwg-mimetype@4.0.0: + optional: true + + whatwg-url@14.1.0: + dependencies: + tr46: 5.0.0 + webidl-conversions: 7.0.0 + optional: true + which@1.3.1: dependencies: isexe: 2.0.0 @@ -5355,6 +6341,10 @@ snapshots: dependencies: isexe: 2.0.0 + which@5.0.0: + dependencies: + isexe: 3.1.1 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 @@ -5380,8 +6370,17 @@ snapshots: wrappy@1.0.2: {} + ws@8.18.0: + optional: true + xml-name-validator@4.0.0: {} + xml-name-validator@5.0.0: + optional: true + + xmlchars@2.2.0: + optional: true + y18n@5.0.8: {} yallist@3.1.1: {} @@ -5401,3 +6400,6 @@ snapshots: yocto-queue@0.1.0: {} yocto-queue@1.1.1: {} + + yoctocolors-cjs@2.1.2: + optional: true diff --git a/src/plugin-dynamic-import-retry/README.md b/src/plugin-dynamic-import-retry/README.md new file mode 100644 index 0000000..6dc4c52 --- /dev/null +++ b/src/plugin-dynamic-import-retry/README.md @@ -0,0 +1,59 @@ +# plugin-dynamic-import-retry + +This plugin provides a retry mechanism for dynamic imports (both JS and CSS) in Vite. It helps to handle cases where dynamic imports might fail due to network issues or other transient problems by retrying the import a specified number of times. + +Inspired by [fatso83/retry-dynamic-import](https://github.com/fatso83/retry-dynamic-import). + +## Usage + +Import and use the plugin in your Vite configuration file: +```typescript +import { defineConfig } from 'vite' +import DynamicImportRetryPlugin from '@kong/vite-plugins/dynamic-import-retry' + +export default defineConfig({ + plugins: [ + DynamicImportRetryPlugin(), + ], +}) +``` + +## Configuration Options + +| Option | Type | Description | +|----------|-------------------------------------------|---------------------------------------------------------------| +| include | string \| RegExp \| (string \| RegExp)[] | Files to include, default is `\.(js\|ts\|vue\|tsx)$`. | +| exclude | string \| RegExp \| (string \| RegExp)[] | Files to exclude. default is `/node_modules/` | +| retries | number | Number of retry attempts, default is `3`. | + +## Limitations + +**Transitive imports:** This plugin only retries the top-level dynamic import. If the import itself contains other static imports, those will not be retried. +```ts +import('./a.js') // when you import `a.js`, it contains a static import for `b.js` + +// a.js +import './b.js' +// if `b.js` fails, even `a.js` itself is loaded successfully +// it still triggers a retry for `a.js` but it won't succeed because `b.js` is not retried. +``` + +## Work with @rollup/plugin-dynamic-import-vars +If you are using [@rollup/plugin-dynamic-import-vars](https://www.npmjs.com/package/@rollup/plugin-dynamic-import-vars) to import dynamic modules with variables, the order of the plugins matters. + +Please make sure to put `dynamic-import-vars` before `dynamic-import-retry` in the plugins list. + +```typescript +build: { + // minify: false, + outDir: 'dist', + target: 'esnext', + emptyOutDir: true, + rollupOptions: { + plugins: [ + dynamicImportVars(), + DynamicImportRetryPlugin(), + ], + }, +}, +``` diff --git a/src/plugin-dynamic-import-retry/css-preload-retry.ts b/src/plugin-dynamic-import-retry/css-preload-retry.ts new file mode 100644 index 0000000..8d76ed2 --- /dev/null +++ b/src/plugin-dynamic-import-retry/css-preload-retry.ts @@ -0,0 +1,57 @@ +interface VitePreloadErrorEvent extends Event { + payload: Error +} + +/** + * This function will be stringified and injected into html. + * Make sure everything is literally inlined, no imports or external references. + */ +export function createCSSPreloadRetry(options = { retries: 3 }) { + // error message: `Unable to preload CSS for /gateway-manager/assets/GatewayOverviewPage.BwFrm_fH.css` + // https://github.com/vitejs/vite/blob/21ec1ce7f041efa5cd781924f7bc536ab406a197/packages/vite/src/node/plugins/importAnalysisBuild.ts#L141 + const pathRegex = /\s([^\s]+\.css)/ + + // extract the css path from vite error message + function parseMessageBody(message: string) { + const [, cssPath] = message.match(pathRegex) || [] + return cssPath || null + } + + function loadCSS(path: string) { + return new Promise((resolve, reject) => { + const link = document.createElement('link') + link.rel = 'stylesheet' + link.href = path + link.crossOrigin = 'anonymous' + link.onload = resolve + link.onerror = reject + document.head.appendChild(link) + }) + } + + async function handleVitePreloadError(e: VitePreloadErrorEvent) { + if (!e.payload?.message || !e.payload.message.includes('Unable to preload CSS')) return + const cssPath = parseMessageBody(e.payload.message) + if (!cssPath) { + console.error('[preload-css-parse-failure]', e.payload.message) + return + } + e.preventDefault() // mute the original error + for (let i = 0; i < options.retries; i++) { + try { + const cacheBustedPath = `${cssPath}?t=${+new Date()}` + await loadCSS(cacheBustedPath) + return + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (_) { + // avoid to create same timestamp for the next retry + await new Promise((resolve) => setTimeout(resolve, (1 + i) * 200)) + } + } + throw new Error(`[preload-css-retried] ${e.payload.message}`) + } + + // the event is emitted by vite + // https://github.com/vitejs/vite/blob/21ec1ce7f041efa5cd781924f7bc536ab406a197/packages/vite/src/node/plugins/importAnalysisBuild.ts#L150 + window.addEventListener('vite:preloadError', handleVitePreloadError as any) +} diff --git a/src/plugin-dynamic-import-retry/index.ts b/src/plugin-dynamic-import-retry/index.ts new file mode 100644 index 0000000..a194a9b --- /dev/null +++ b/src/plugin-dynamic-import-retry/index.ts @@ -0,0 +1,93 @@ +import type { HtmlTagDescriptor, Plugin } from 'vite' +import { simple } from 'acorn-walk' +import MagicString from 'magic-string' +import { createFilter } from '@rollup/pluginutils' + +import { createCSSPreloadRetry } from './css-preload-retry' + +const dynamicImportPrefixRE = /import\s*\(/ + +export type Options = { + include?: string | RegExp | (string | RegExp)[] + exclude?: string | RegExp | (string | RegExp)[] + retries?: number +} + +export const defaultOptions: Options = { + include: /\.(js|ts|vue|tsx)$/, + exclude: /node_modules/, + retries: 3, +} +export function DynamicImportRetryPlugin(opt?: Options): Plugin { + const options = { ...defaultOptions, ...opt } + const virtualModuleId = 'virtual:dynamic-import-retry' + const resolvedVirtualModuleId = '\0' + virtualModuleId + const filter = createFilter(options.include, options.exclude) + + return { + name: 'dynamic-import-retry', + apply: 'build', + + transformIndexHtml() { + const descriptor: HtmlTagDescriptor[] = [ + { + tag: 'script', + children: `(${createCSSPreloadRetry.toString()}({ retries: ${options.retries} }))`, + injectTo: 'head-prepend', + }, + ] + return descriptor + }, + + transform(code, id) { + if (!filter(id) || !dynamicImportPrefixRE.test(code)) { + return null + } + + const parsed = this.parse(code) + + let ms: MagicString | undefined + + simple(parsed, { + ImportExpression(node) { + ms = ms || new MagicString(code) + const start = node.start + const end = node.end + const path = code.slice(node.source.start, node.source.end) + ms.overwrite(start, end, `importWrapper(() => import(${path}))`) + }, + }) + + if (ms) { + ms.prepend(`import { importWrapper } from '${virtualModuleId}'\n`) + + return { + code: ms.toString(), + map: ms.generateMap({ + file: id, + includeContent: true, + hires: true, + }), + } + } + + return null + }, + + resolveId(id) { + if (id === virtualModuleId) { + return resolvedVirtualModuleId + } + }, + + load(id) { + if (id === resolvedVirtualModuleId) { + return `import { createDynamicImportWithRetry } from '@fatso83/retry-dynamic-import' +export const importWrapper = createDynamicImportWithRetry(${options.retries}, { + logger: console.log, + importFunction: (path) => import(path), +})` + } + }, + } +} diff --git a/vitest.config.e2e.ts b/vitest.config.e2e.ts new file mode 100644 index 0000000..e264dfc --- /dev/null +++ b/vitest.config.e2e.ts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vitest/config' + +const timeout = process.env.CI ? 50000 : 30000 + +process.env.NODE_ENV = process.env.VITE_TEST_BUILD + ? 'production' + : 'development' + +export default defineConfig({ + test: { + include: ['./playground/**/*.spec.[tj]s'], + setupFiles: ['./playground/vitestSetup.ts'], + globalSetup: ['./playground/vitestGlobalSetup.ts'], + testTimeout: timeout, + hookTimeout: timeout, + reporters: 'dot', + onConsoleLog(log) { + if (log.match(/experimental|jit engine|emitted file|tailwind/i)) + return false + }, + }, +})