diff --git a/packages/frontend/routes/README.md b/packages/frontend/routes/README.md
new file mode 100644
index 0000000000..96e1b8bfa6
--- /dev/null
+++ b/packages/frontend/routes/README.md
@@ -0,0 +1,36 @@
+# Routes
+
+## Usage
+
+### Path Factories
+
+```ts
+import { FACTORIES } from '@affine/routes';
+
+const path = FACTORIES.workspace.doc({ workspaceId: '123', docId: '456' });
+// ^^^^ with typecheck
+```
+
+### Register router
+
+```tsx
+import { ROUTES } from '@affine/routes';
+
+function Routes() {
+ return ;
+}
+```
+
+### Path Parameter
+
+```ts
+import { RouteParamsTypes } from '@affine/routes';
+
+function Doc() {
+ const { workspaceId, docId } = useParams();
+}
+
+function Attachment() {
+ const { workspaceId, docId, attachmentId } = useParams();
+}
+```
diff --git a/packages/frontend/routes/build.ts b/packages/frontend/routes/build.ts
new file mode 100644
index 0000000000..13ddc911c3
--- /dev/null
+++ b/packages/frontend/routes/build.ts
@@ -0,0 +1,260 @@
+import fs from 'node:fs';
+import { join } from 'node:path/posix';
+
+import { Path, prettier } from '@affine-tools/utils';
+import { parse } from 'path-to-regexp';
+
+const curdir = Path.dir(import.meta.url);
+
+const inputFile = curdir.join('routes.json').value;
+const routerOutputFile = curdir.join('src', 'routes.ts').value;
+
+interface RawRoutesSchema {
+ route: string;
+ children: {
+ [key: string]: string | RawRoutesSchema;
+ };
+}
+
+interface RouteSchema {
+ name: string;
+ fromParent: string;
+ fromRoot: string;
+ children: Array;
+}
+
+function loadRoutesSchema(): RouteSchema {
+ const rawSchema = JSON.parse(
+ fs.readFileSync(inputFile, 'utf-8')
+ ) as RawRoutesSchema;
+
+ const build = (
+ name: string,
+ schema: RawRoutesSchema | string,
+ fromRoot: string = ''
+ ): RouteSchema => {
+ if (typeof schema === 'string') {
+ return {
+ name,
+ fromParent: schema,
+ fromRoot: join(fromRoot, schema),
+ children: [],
+ };
+ }
+
+ const absolute = join(fromRoot, schema.route);
+ return {
+ name,
+ fromParent: schema.route,
+ fromRoot: absolute,
+ children: Object.entries(schema.children).map(([key, value]) => {
+ return build(key, value, absolute);
+ }),
+ };
+ };
+
+ return build('home', rawSchema);
+}
+
+interface BuiltRouteSchema {
+ name: string;
+ type?: string;
+ factory: string;
+ fromRoot: string;
+ fromParent: string;
+ children: BuiltRouteSchema[];
+ parent: BuiltRouteSchema | null;
+}
+
+function buildRoutes(
+ schema: RouteSchema,
+ parent: BuiltRouteSchema | null = null
+): BuiltRouteSchema {
+ const { tokens } = parse(schema.fromRoot);
+
+ const types: string[] = [];
+ const factories: string[] = [];
+
+ for (const token of tokens) {
+ switch (token.type) {
+ case 'param':
+ case 'wildcard':
+ types.push(token.name);
+ factories.push(`\${params.${token.name}}`);
+ break;
+ case 'text': {
+ factories.push(token.value);
+ break;
+ }
+ }
+ }
+
+ // [a, b, c] -> '{ a: string; b: string; c: string }'
+ const type = types.length
+ ? `{ ${types.map(type => `${type}: string`).join('; ')} }`
+ : undefined;
+
+ const builtResult: BuiltRouteSchema = {
+ name: schema.name,
+ type,
+ factory: factories.join(''),
+ fromRoot: schema.fromRoot,
+ fromParent: schema.fromParent,
+ parent: parent,
+ children: [],
+ };
+
+ builtResult.children = schema.children.map(child =>
+ buildRoutes(child, builtResult)
+ );
+
+ return builtResult;
+}
+
+function printSchemaTypes(schema: BuiltRouteSchema, level: number = 1): string {
+ const types: string[] = [];
+
+ if (schema.type) {
+ if (schema.children.length) {
+ types.push(`index: ${schema.type};`);
+ } else {
+ return schema.type;
+ }
+ }
+
+ for (const child of schema.children) {
+ const childType = printSchemaTypes(child, level + 1);
+ if (childType) {
+ types.push(`${child.name}: ${childType};`);
+ }
+ }
+
+ if (types.length > 0) {
+ const output = `{ ${types.join('\n')} }`;
+ return level === 1 ? `export interface RouteParamsTypes ${output}` : output;
+ }
+
+ return '';
+}
+
+function printAbsolutePaths(schema: BuiltRouteSchema, level = 1): string {
+ const absolutes: string[] = [];
+
+ if (schema.children.length) {
+ absolutes.push(`index: '${schema.fromRoot}'`);
+ } else {
+ return `'${schema.fromRoot}'`;
+ }
+
+ for (const child of schema.children) {
+ const childRoute = printAbsolutePaths(child, level + 1);
+ absolutes.push(`${child.name}: ${childRoute}`);
+ }
+
+ const output = `{ ${absolutes.join('\n,')} }`;
+
+ return level === 1 ? `export const ROUTES = ${output}` : output;
+}
+
+function printRelativePaths(schema: BuiltRouteSchema, level = 1): string {
+ const relatives: string[] = [];
+
+ if (schema.children.length) {
+ relatives.push(`index: '${schema.fromParent}'`);
+ } else {
+ return `'${schema.fromParent}'`;
+ }
+
+ for (const child of schema.children) {
+ const childRoute = printRelativePaths(child, level + 1);
+ relatives.push(`${child.name}: ${childRoute}`);
+ }
+
+ const output = `{ ${relatives.join('\n,')} }`;
+
+ return level === 1 ? `export const RELATIVE_ROUTES = ${output}` : output;
+}
+
+function printFactories(schema: BuiltRouteSchema): string {
+ const factories: string[] = [];
+
+ const factory = schema.type
+ ? `(params: ${schema.type}) => \`${schema.factory}\``
+ : `() => '${schema.factory}'`;
+
+ let parent: BuiltRouteSchema | null = schema.parent;
+ let nameFromRoot: string[] = [schema.name];
+ while (parent) {
+ // ignore home
+ if (parent.parent) {
+ nameFromRoot = [parent.name, ...nameFromRoot];
+ }
+ parent = parent.parent;
+ }
+
+ if (schema.children.length) {
+ // with children, we do
+ const visitor = nameFromRoot.join('_');
+ // 1. const workspace_doc = () => 'workspace/doc'
+ factories.push(`const ${visitor} = ${factory}`);
+ for (const child of schema.children) {
+ // 2. generate children for workspace_doc
+ factories.push(printFactories(child));
+ }
+ // 3. workspace.doc = workspace_doc
+ const parentNameFromRoot = nameFromRoot.slice(0, -1).join('_');
+ if (parentNameFromRoot) {
+ factories.push(`${parentNameFromRoot}.${schema.name} = ${visitor}`);
+ }
+ } else {
+ // without children, we directly
+ const parentNameFromRoot = nameFromRoot.slice(0, -1).join('_');
+ if (parentNameFromRoot) {
+ // parent.child = () => 'child'
+ factories.push(`${parentNameFromRoot}.${schema.name} = ${factory}`);
+ } else {
+ // const route = () => 'route'
+ factories.push(`const ${schema.name} = ${factory}`);
+ }
+ }
+
+ const output = factories.join('\n');
+
+ if (!schema.parent) {
+ const firstLevelNames = schema.children.map(child => child.name);
+ firstLevelNames.push(schema.name);
+ return `${output}\nexport const FACTORIES = { ${firstLevelNames.join(
+ ', '
+ )} };`;
+ }
+
+ return output;
+}
+
+async function printRoutes(schema: BuiltRouteSchema) {
+ const parts = {
+ ['Path Parameter Types']: printSchemaTypes,
+ ['Absolute Paths']: printAbsolutePaths,
+ ['Relative Paths']: printRelativePaths,
+ ['Path Factories']: printFactories,
+ };
+ const content = await prettier(
+ Object.entries(parts)
+ .map(([key, print]) => {
+ return `// #region ${key}\n${print(schema)}\n// #endregion`;
+ })
+ .join('\n\n'),
+ 'typescript'
+ );
+
+ fs.writeFileSync(routerOutputFile, content, 'utf-8');
+}
+
+async function build() {
+ const schema = loadRoutesSchema();
+ const builtSchema = buildRoutes(schema);
+
+ await printRoutes(builtSchema);
+}
+
+await build();
diff --git a/packages/frontend/routes/package.json b/packages/frontend/routes/package.json
new file mode 100644
index 0000000000..e70b33168c
--- /dev/null
+++ b/packages/frontend/routes/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "@affine/routes",
+ "version": "0.0.1",
+ "private": true,
+ "type": "module",
+ "exports": {
+ ".": "./src/routes.ts"
+ },
+ "scripts": {
+ "build": "r build.ts"
+ },
+ "devDependencies": {
+ "@affine-tools/cli": "workspace:*",
+ "@affine-tools/utils": "workspace:*",
+ "path-to-regexp": "^8.2.0",
+ "query-string": "^9.1.1",
+ "vitest": "^3.0.6"
+ },
+ "optionalDependencies": {
+ "react-router-dom": "^6.28.0"
+ }
+}
diff --git a/packages/frontend/routes/routes.json b/packages/frontend/routes/routes.json
new file mode 100644
index 0000000000..14a8e00001
--- /dev/null
+++ b/packages/frontend/routes/routes.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "./schema.json",
+ "route": "/",
+ "children": {
+ "admin": {
+ "route": "admin",
+ "children": {
+ "accounts": "accounts",
+ "ai": "ai",
+ "settings": {
+ "route": "settings",
+ "children": {
+ "module": ":module"
+ }
+ },
+ "about": "about",
+ "notFound": "404"
+ }
+ }
+ }
+}
diff --git a/packages/frontend/routes/schema.json b/packages/frontend/routes/schema.json
new file mode 100644
index 0000000000..18039ed9db
--- /dev/null
+++ b/packages/frontend/routes/schema.json
@@ -0,0 +1,32 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "description": "AFFiNE Routes Schema",
+ "definitions": {
+ "route": {
+ "type": "string",
+ "description": "route path"
+ },
+ "children": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object",
+ "required": ["route", "children"],
+ "properties": {
+ "route": { "$ref": "#/definitions/route" },
+ "children": { "$ref": "#/definitions/children" }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "properties": {
+ "route": { "$ref": "#/definitions/route" },
+ "children": { "$ref": "#/definitions/children" }
+ }
+}
diff --git a/packages/frontend/routes/src/__tests__/routes.spec.ts b/packages/frontend/routes/src/__tests__/routes.spec.ts
new file mode 100644
index 0000000000..77ba3e133c
--- /dev/null
+++ b/packages/frontend/routes/src/__tests__/routes.spec.ts
@@ -0,0 +1,13 @@
+import { describe, expect, it } from 'vitest';
+
+import { FACTORIES } from '../routes';
+
+describe('PATH_FACTORIES', () => {
+ it('should generate correct paths', () => {
+ expect(
+ FACTORIES.admin.settings.module({
+ module: 'auth',
+ })
+ ).toBe('/admin/settings/auth');
+ });
+});
diff --git a/packages/frontend/routes/src/routes.ts b/packages/frontend/routes/src/routes.ts
new file mode 100644
index 0000000000..a55b862eb5
--- /dev/null
+++ b/packages/frontend/routes/src/routes.ts
@@ -0,0 +1,44 @@
+// #region Path Parameter Types
+export interface RouteParamsTypes {
+ admin: { settings: { module: { module: string } } };
+}
+// #endregion
+
+// #region Absolute Paths
+export const ROUTES = {
+ index: '/',
+ admin: {
+ index: '/admin',
+ accounts: '/admin/accounts',
+ ai: '/admin/ai',
+ settings: { index: '/admin/settings', module: '/admin/settings/:module' },
+ about: '/admin/about',
+ },
+};
+// #endregion
+
+// #region Relative Paths
+export const RELATIVE_ROUTES = {
+ index: '/',
+ admin: {
+ index: 'admin',
+ accounts: 'accounts',
+ ai: 'ai',
+ settings: { index: 'settings', module: ':module' },
+ about: 'about',
+ },
+};
+// #endregion
+
+// #region Path Factories
+const home = () => '/';
+const admin = () => '/admin';
+admin.accounts = () => '/admin/accounts';
+admin.ai = () => '/admin/ai';
+const admin_settings = () => '/admin/settings';
+admin_settings.module = (params: { module: string }) =>
+ `/admin/settings/${params.module}`;
+admin.settings = admin_settings;
+admin.about = () => '/admin/about';
+export const FACTORIES = { admin, home };
+// #endregion
diff --git a/packages/frontend/routes/tsconfig.json b/packages/frontend/routes/tsconfig.json
new file mode 100644
index 0000000000..6433071963
--- /dev/null
+++ b/packages/frontend/routes/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "extends": "../../../tsconfig.web.json",
+ "compilerOptions": {
+ "rootDir": "./src",
+ "outDir": "./dist",
+ "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
+ },
+ "include": ["./src"],
+ "references": [
+ { "path": "../../../tools/cli" },
+ { "path": "../../../tools/utils" }
+ ]
+}
diff --git a/tools/utils/package.json b/tools/utils/package.json
index a6eaf562e7..dfb217df63 100644
--- a/tools/utils/package.json
+++ b/tools/utils/package.json
@@ -4,6 +4,7 @@
"type": "module",
"private": true,
"exports": {
+ ".": "./src/index.ts",
"./path": "./src/path.ts",
"./workspace": "./src/workspace.ts",
"./process": "./src/process.ts",
@@ -16,6 +17,7 @@
"@types/node": "^22.0.0",
"chalk": "^5.3.0",
"lodash-es": "^4.17.21",
+ "prettier": "^3.3.3",
"typescript": "^5.5.4"
}
}
diff --git a/tools/utils/src/format.ts b/tools/utils/src/format.ts
new file mode 100644
index 0000000000..975d3ec9cc
--- /dev/null
+++ b/tools/utils/src/format.ts
@@ -0,0 +1,17 @@
+import { readFileSync } from 'node:fs';
+
+import { once } from 'lodash-es';
+import { type BuiltInParserName, format } from 'prettier';
+
+import { ProjectRoot } from './path';
+
+const readConfig = once(() => {
+ const path = ProjectRoot.join('.prettierrc').value;
+ const config = JSON.parse(readFileSync(path, 'utf-8'));
+ return config;
+});
+
+export function prettier(content: string, parser: BuiltInParserName) {
+ const config = readConfig();
+ return format(content, { parser, ...config });
+}
diff --git a/tools/utils/src/index.ts b/tools/utils/src/index.ts
new file mode 100644
index 0000000000..1df3f5afd3
--- /dev/null
+++ b/tools/utils/src/index.ts
@@ -0,0 +1,2 @@
+export * from './format';
+export * from './path';
diff --git a/tools/utils/src/workspace.gen.ts b/tools/utils/src/workspace.gen.ts
index e957afe87e..fc2924e8f4 100644
--- a/tools/utils/src/workspace.gen.ts
+++ b/tools/utils/src/workspace.gen.ts
@@ -1165,6 +1165,11 @@ export const PackageList = [
name: '@affine/native',
workspaceDependencies: [],
},
+ {
+ location: 'packages/frontend/routes',
+ name: '@affine/routes',
+ workspaceDependencies: ['tools/cli', 'tools/utils'],
+ },
{
location: 'packages/frontend/templates',
name: '@affine/templates',
@@ -1349,6 +1354,7 @@ export type PackageName =
| '@affine/i18n'
| '@affine/media-capture-playground'
| '@affine/native'
+ | '@affine/routes'
| '@affine/templates'
| '@affine/track'
| '@affine-test/affine-cloud'
diff --git a/tsconfig.json b/tsconfig.json
index 2102f04a6b..30726ef91d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -133,6 +133,7 @@
{ "path": "./packages/frontend/i18n" },
{ "path": "./packages/frontend/media-capture-playground" },
{ "path": "./packages/frontend/native" },
+ { "path": "./packages/frontend/routes" },
{ "path": "./packages/frontend/track" },
{ "path": "./tests/affine-cloud" },
{ "path": "./tests/affine-cloud-copilot" },
diff --git a/yarn.lock b/yarn.lock
index d016f774d2..d0032b3ad7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -167,6 +167,7 @@ __metadata:
"@types/node": "npm:^22.0.0"
chalk: "npm:^5.3.0"
lodash-es: "npm:^4.17.21"
+ prettier: "npm:^3.3.3"
typescript: "npm:^5.5.4"
languageName: unknown
linkType: soft
@@ -851,6 +852,22 @@ __metadata:
languageName: unknown
linkType: soft
+"@affine/routes@workspace:packages/frontend/routes":
+ version: 0.0.0-use.local
+ resolution: "@affine/routes@workspace:packages/frontend/routes"
+ dependencies:
+ "@affine-tools/cli": "workspace:*"
+ "@affine-tools/utils": "workspace:*"
+ path-to-regexp: "npm:^8.2.0"
+ query-string: "npm:^9.1.1"
+ react-router-dom: "npm:^6.28.0"
+ vitest: "npm:^3.0.6"
+ dependenciesMeta:
+ react-router-dom:
+ optional: true
+ languageName: unknown
+ linkType: soft
+
"@affine/server-native@workspace:*, @affine/server-native@workspace:packages/backend/native":
version: 0.0.0-use.local
resolution: "@affine/server-native@workspace:packages/backend/native"
@@ -15958,6 +15975,18 @@ __metadata:
languageName: node
linkType: hard
+"@vitest/expect@npm:3.1.2":
+ version: 3.1.2
+ resolution: "@vitest/expect@npm:3.1.2"
+ dependencies:
+ "@vitest/spy": "npm:3.1.2"
+ "@vitest/utils": "npm:3.1.2"
+ chai: "npm:^5.2.0"
+ tinyrainbow: "npm:^2.0.0"
+ checksum: 10/3c414e376154c8095f40efe409bb5f2c9380ba05a15b20552ee2e29f73197ab73068177e3da298ac135ef72673d1ea92090c466c78443ee69a7438bc8ab65f4f
+ languageName: node
+ linkType: hard
+
"@vitest/mocker@npm:3.1.1":
version: 3.1.1
resolution: "@vitest/mocker@npm:3.1.1"
@@ -15977,6 +16006,25 @@ __metadata:
languageName: node
linkType: hard
+"@vitest/mocker@npm:3.1.2":
+ version: 3.1.2
+ resolution: "@vitest/mocker@npm:3.1.2"
+ dependencies:
+ "@vitest/spy": "npm:3.1.2"
+ estree-walker: "npm:^3.0.3"
+ magic-string: "npm:^0.30.17"
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^5.0.0 || ^6.0.0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+ checksum: 10/e6d730400daa7a97fb277159733df1366a932b5b06ac83d72e094e5383191c2597b4a5ae3538b28de6112b9e5d314cb50b44e031e79522f43f3dfc8ab022a584
+ languageName: node
+ linkType: hard
+
"@vitest/pretty-format@npm:2.0.5":
version: 2.0.5
resolution: "@vitest/pretty-format@npm:2.0.5"
@@ -15995,7 +16043,7 @@ __metadata:
languageName: node
linkType: hard
-"@vitest/pretty-format@npm:3.1.1, @vitest/pretty-format@npm:^3.1.1":
+"@vitest/pretty-format@npm:3.1.1":
version: 3.1.1
resolution: "@vitest/pretty-format@npm:3.1.1"
dependencies:
@@ -16004,6 +16052,15 @@ __metadata:
languageName: node
linkType: hard
+"@vitest/pretty-format@npm:3.1.2, @vitest/pretty-format@npm:^3.1.1, @vitest/pretty-format@npm:^3.1.2":
+ version: 3.1.2
+ resolution: "@vitest/pretty-format@npm:3.1.2"
+ dependencies:
+ tinyrainbow: "npm:^2.0.0"
+ checksum: 10/454d0a8c250dbe52f7ec9dab4968e7c769fa10c8318eb5c54cb4b6d5b524772c04856e1990279f2c6e76705ffa107fddcbc1973560ed3b88167c231ccfeada16
+ languageName: node
+ linkType: hard
+
"@vitest/runner@npm:3.1.1":
version: 3.1.1
resolution: "@vitest/runner@npm:3.1.1"
@@ -16014,6 +16071,16 @@ __metadata:
languageName: node
linkType: hard
+"@vitest/runner@npm:3.1.2":
+ version: 3.1.2
+ resolution: "@vitest/runner@npm:3.1.2"
+ dependencies:
+ "@vitest/utils": "npm:3.1.2"
+ pathe: "npm:^2.0.3"
+ checksum: 10/b09c1ff3a556f318585307e6bb8954d219d0d35d1e17708fdd5d5ae1a230e6f29eba4f37a86faa71192f72406bb96b576c1b620d49d686def87bc5dcb8bf5737
+ languageName: node
+ linkType: hard
+
"@vitest/snapshot@npm:3.1.1":
version: 3.1.1
resolution: "@vitest/snapshot@npm:3.1.1"
@@ -16025,6 +16092,17 @@ __metadata:
languageName: node
linkType: hard
+"@vitest/snapshot@npm:3.1.2":
+ version: 3.1.2
+ resolution: "@vitest/snapshot@npm:3.1.2"
+ dependencies:
+ "@vitest/pretty-format": "npm:3.1.2"
+ magic-string: "npm:^0.30.17"
+ pathe: "npm:^2.0.3"
+ checksum: 10/dda969b697bdcd8616f17e98c74ad5e95a5f3c2284140aa72390ce668db34e70936ee0b8ebe89adb2e0dea332500689d54c8ff03f8adf1e00be70639ec9032bf
+ languageName: node
+ linkType: hard
+
"@vitest/spy@npm:2.0.5":
version: 2.0.5
resolution: "@vitest/spy@npm:2.0.5"
@@ -16043,6 +16121,15 @@ __metadata:
languageName: node
linkType: hard
+"@vitest/spy@npm:3.1.2":
+ version: 3.1.2
+ resolution: "@vitest/spy@npm:3.1.2"
+ dependencies:
+ tinyspy: "npm:^3.0.2"
+ checksum: 10/c2c638368fa4130f903901fdf4e86da6f90d5d6a8cf7ce880cdd24768a1f8e6b726ea3428501c97e00c34ac2e8e39ac09b3a03606dffd8081559e0a35c892ddc
+ languageName: node
+ linkType: hard
+
"@vitest/ui@npm:3.1.1":
version: 3.1.1
resolution: "@vitest/ui@npm:3.1.1"
@@ -16083,6 +16170,17 @@ __metadata:
languageName: node
linkType: hard
+"@vitest/utils@npm:3.1.2":
+ version: 3.1.2
+ resolution: "@vitest/utils@npm:3.1.2"
+ dependencies:
+ "@vitest/pretty-format": "npm:3.1.2"
+ loupe: "npm:^3.1.3"
+ tinyrainbow: "npm:^2.0.0"
+ checksum: 10/221faaaf6c69ef24eacdcf68581c833cb99bf3e5125945b5dec928af7ef1af4359aa520b90c42413a128b308037bf3217d8c41a41f44ca4aee3ac44e3f0d56b5
+ languageName: node
+ linkType: hard
+
"@vitest/utils@npm:^2.1.1":
version: 2.1.9
resolution: "@vitest/utils@npm:2.1.9"
@@ -21142,7 +21240,7 @@ __metadata:
languageName: node
linkType: hard
-"expect-type@npm:^1.2.0":
+"expect-type@npm:^1.2.0, expect-type@npm:^1.2.1":
version: 1.2.1
resolution: "expect-type@npm:1.2.1"
checksum: 10/d121d90f4f3f705ca0b656e36f28c0ba91483d0cddf2876e64e23c3dea2f2d5853e9c0c9a4e90eb4b3e4663bf09c2c02e9729c339dcd308c70b2107188e6b286
@@ -21448,15 +21546,15 @@ __metadata:
languageName: node
linkType: hard
-"fdir@npm:^6.4.3":
- version: 6.4.3
- resolution: "fdir@npm:6.4.3"
+"fdir@npm:^6.4.4":
+ version: 6.4.4
+ resolution: "fdir@npm:6.4.4"
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
- checksum: 10/8e6d20f4590dc168de1374a9cadaa37e20ca6e0b822aa247c230e7ea1d9e9674a68cd816146435e4ecc98f9285091462ab7e5e56eebc9510931a1794e4db68b2
+ checksum: 10/d0000d6b790059b35f4ed19acc8847a66452e0bc68b28766c929ffd523e5ec2083811fc8a545e4a1d4945ce70e887b3a610c145c681073b506143ae3076342ed
languageName: node
linkType: hard
@@ -26881,8 +26979,8 @@ __metadata:
linkType: hard
"msw@npm:^2.6.8":
- version: 2.7.4
- resolution: "msw@npm:2.7.4"
+ version: 2.7.3
+ resolution: "msw@npm:2.7.3"
dependencies:
"@bundled-es-modules/cookie": "npm:^2.0.1"
"@bundled-es-modules/statuses": "npm:^1.0.1"
@@ -26909,7 +27007,7 @@ __metadata:
optional: true
bin:
msw: cli/index.js
- checksum: 10/3ce3cb190b1a036536a047c018fc4f52d23371765c97595127411695c11e73bc7c737fea8fb4a6b46b3cd36223031ffc23a8bbaff10d1daebdda3bb4a817dd34
+ checksum: 10/f193329a68fc22e477a6f8504aa44a92bd12847f2eeac1dfbd8ec1cc43ff293112ec067de1c7fe312ba02beecb313fb00aeeebf5817432b57af2d796b2dff2fa
languageName: node
linkType: hard
@@ -28261,7 +28359,7 @@ __metadata:
languageName: node
linkType: hard
-"path-to-regexp@npm:8.2.0, path-to-regexp@npm:^8.0.0":
+"path-to-regexp@npm:8.2.0, path-to-regexp@npm:^8.0.0, path-to-regexp@npm:^8.2.0":
version: 8.2.0
resolution: "path-to-regexp@npm:8.2.0"
checksum: 10/23378276a172b8ba5f5fb824475d1818ca5ccee7bbdb4674701616470f23a14e536c1db11da9c9e6d82b82c556a817bbf4eee6e41b9ed20090ef9427cbb38e13
@@ -29079,7 +29177,7 @@ __metadata:
languageName: node
linkType: hard
-"prettier@npm:3.5.3, prettier@npm:^3.2.5, prettier@npm:^3.4.2":
+"prettier@npm:3.5.3, prettier@npm:^3.2.5, prettier@npm:^3.3.3, prettier@npm:^3.4.2":
version: 3.5.3
resolution: "prettier@npm:3.5.3"
bin:
@@ -31683,7 +31781,7 @@ __metadata:
languageName: node
linkType: hard
-"std-env@npm:^3.7.0, std-env@npm:^3.8.1":
+"std-env@npm:^3.7.0, std-env@npm:^3.8.1, std-env@npm:^3.9.0":
version: 3.9.0
resolution: "std-env@npm:3.9.0"
checksum: 10/3044b2c54a74be4f460db56725571241ab3ac89a91f39c7709519bc90fa37148784bc4cd7d3a301aa735f43bd174496f263563f76703ce3e81370466ab7c235b
@@ -32480,13 +32578,13 @@ __metadata:
languageName: node
linkType: hard
-"tinyglobby@npm:^0.2.12":
- version: 0.2.12
- resolution: "tinyglobby@npm:0.2.12"
+"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.13":
+ version: 0.2.13
+ resolution: "tinyglobby@npm:0.2.13"
dependencies:
- fdir: "npm:^6.4.3"
+ fdir: "npm:^6.4.4"
picomatch: "npm:^4.0.2"
- checksum: 10/4ad28701fa9118b32ef0e27f409e0a6c5741e8b02286d50425c1f6f71e6d6c6ded9dd5bbbbb714784b08623c4ec4d150151f1d3d996cfabe0495f908ab4f7002
+ checksum: 10/b04557ee58ad2be5f2d2cbb4b441476436c92bb45ba2e1fc464d686b793392b305ed0bcb8b877429e9b5036bdd46770c161a08384c0720b6682b7cd6ac80e403
languageName: node
linkType: hard
@@ -33651,7 +33749,7 @@ __metadata:
languageName: node
linkType: hard
-"vite-node@npm:3.1.1, vite-node@npm:^3.0.4":
+"vite-node@npm:3.1.1":
version: 3.1.1
resolution: "vite-node@npm:3.1.1"
dependencies:
@@ -33666,6 +33764,21 @@ __metadata:
languageName: node
linkType: hard
+"vite-node@npm:3.1.2, vite-node@npm:^3.0.4":
+ version: 3.1.2
+ resolution: "vite-node@npm:3.1.2"
+ dependencies:
+ cac: "npm:^6.7.14"
+ debug: "npm:^4.4.0"
+ es-module-lexer: "npm:^1.6.0"
+ pathe: "npm:^2.0.3"
+ vite: "npm:^5.0.0 || ^6.0.0"
+ bin:
+ vite-node: vite-node.mjs
+ checksum: 10/8af0465810c6f27200dc899792002320995f3d85c432aaa411bf7ff15580c0b93c4a5153d8a93c7af89b496a6e1a7979a7777984e37ebd7311851ea7572eaac7
+ languageName: node
+ linkType: hard
+
"vite-plugin-istanbul@npm:^7.0.0":
version: 7.0.0
resolution: "vite-plugin-istanbul@npm:7.0.0"
@@ -33811,6 +33924,60 @@ __metadata:
languageName: node
linkType: hard
+"vitest@npm:^3.0.6":
+ version: 3.1.2
+ resolution: "vitest@npm:3.1.2"
+ dependencies:
+ "@vitest/expect": "npm:3.1.2"
+ "@vitest/mocker": "npm:3.1.2"
+ "@vitest/pretty-format": "npm:^3.1.2"
+ "@vitest/runner": "npm:3.1.2"
+ "@vitest/snapshot": "npm:3.1.2"
+ "@vitest/spy": "npm:3.1.2"
+ "@vitest/utils": "npm:3.1.2"
+ chai: "npm:^5.2.0"
+ debug: "npm:^4.4.0"
+ expect-type: "npm:^1.2.1"
+ magic-string: "npm:^0.30.17"
+ pathe: "npm:^2.0.3"
+ std-env: "npm:^3.9.0"
+ tinybench: "npm:^2.9.0"
+ tinyexec: "npm:^0.3.2"
+ tinyglobby: "npm:^0.2.13"
+ tinypool: "npm:^1.0.2"
+ tinyrainbow: "npm:^2.0.0"
+ vite: "npm:^5.0.0 || ^6.0.0"
+ vite-node: "npm:3.1.2"
+ why-is-node-running: "npm:^2.3.0"
+ peerDependencies:
+ "@edge-runtime/vm": "*"
+ "@types/debug": ^4.1.12
+ "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0
+ "@vitest/browser": 3.1.2
+ "@vitest/ui": 3.1.2
+ happy-dom: "*"
+ jsdom: "*"
+ peerDependenciesMeta:
+ "@edge-runtime/vm":
+ optional: true
+ "@types/debug":
+ optional: true
+ "@types/node":
+ optional: true
+ "@vitest/browser":
+ optional: true
+ "@vitest/ui":
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+ bin:
+ vitest: vitest.mjs
+ checksum: 10/aa5638bf37b2811b01ad8ff0563cdec09202f7a28d9dbcb8eabb2e51cadefc57309cba4f5ff2bac4a72edda44055a844236fc4a09eb72127ef1bd34eb25d0808
+ languageName: node
+ linkType: hard
+
"void-elements@npm:3.1.0":
version: 3.1.0
resolution: "void-elements@npm:3.1.0"