zod-v3-to-v4 1.10.0 → 1.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/collect-imports.js +45 -1
- package/dist/convert-name-to-top-level-api.js +56 -1
- package/dist/convert-zod-errors.js +36 -0
- package/dist/index.js +62 -1
- package/dist/migrate.js +6 -3
- package/package.json +10 -9
package/README.md
CHANGED
|
@@ -132,6 +132,9 @@ pnpm playground:interactive
|
|
|
132
132
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yann-combarnous"><img src="https://avatars.githubusercontent.com/u/39089766?v=4?s=100" width="100px;" alt="yann-combarnous"/><br /><sub><b>yann-combarnous</b></sub></a><br /><a href="https://github.com/nicoespeon/zod-v3-to-v4/issues?q=author%3Ayann-combarnous" title="Bug reports">🐛</a></td>
|
|
133
133
|
<td align="center" valign="top" width="14.28%"><a href="http://adam.doussan.info"><img src="https://avatars.githubusercontent.com/u/26745150?v=4?s=100" width="100px;" alt="acdoussan"/><br /><sub><b>acdoussan</b></sub></a><br /><a href="https://github.com/nicoespeon/zod-v3-to-v4/issues?q=author%3Aacdoussan" title="Bug reports">🐛</a></td>
|
|
134
134
|
<td align="center" valign="top" width="14.28%"><a href="http://matsjfunke.com"><img src="https://avatars.githubusercontent.com/u/125814808?v=4?s=100" width="100px;" alt="Mats Julius Funke"/><br /><sub><b>Mats Julius Funke</b></sub></a><br /><a href="#ideas-matsjfunke" title="Ideas, Planning, & Feedback">🤔</a></td>
|
|
135
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/popfendi"><img src="https://avatars.githubusercontent.com/u/84185235?v=4?s=100" width="100px;" alt="popfendi"/><br /><sub><b>popfendi</b></sub></a><br /><a href="https://github.com/nicoespeon/zod-v3-to-v4/issues?q=author%3Apopfendi" title="Bug reports">🐛</a></td>
|
|
136
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/luis-azevedo-visma"><img src="https://avatars.githubusercontent.com/u/149402066?v=4?s=100" width="100px;" alt="Luís Azevedo"/><br /><sub><b>Luís Azevedo</b></sub></a><br /><a href="#ideas-luis-azevedo-visma" title="Ideas, Planning, & Feedback">🤔</a></td>
|
|
137
|
+
<td align="center" valign="top" width="14.28%"><a href="https://florian-lefebvre.dev"><img src="https://avatars.githubusercontent.com/u/69633530?v=4?s=100" width="100px;" alt="Florian Lefebvre"/><br /><sub><b>Florian Lefebvre</b></sub></a><br /><a href="https://github.com/nicoespeon/zod-v3-to-v4/issues?q=author%3Aflorian-lefebvre" title="Bug reports">🐛</a></td>
|
|
135
138
|
</tr>
|
|
136
139
|
</tbody>
|
|
137
140
|
<tfoot>
|
package/dist/collect-imports.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SyntaxKind } from "ts-morph";
|
|
1
|
+
import { SyntaxKind, } from "ts-morph";
|
|
2
2
|
export const AstroZodModuleSpecifier = "astro/zod";
|
|
3
3
|
// https://v6.docs.astro.build/en/guides/upgrade-to/v6/#deprecated-astroschema-and-z-from-astrocontent
|
|
4
4
|
export const AstroDeprecatedZodModuleSpecifiers = [
|
|
@@ -36,3 +36,47 @@ export function collectZodReferences(importDeclarations) {
|
|
|
36
36
|
.filter((node) => node !== namedNode);
|
|
37
37
|
}));
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Collect references to variables that are derived from Zod schemas.
|
|
41
|
+
*
|
|
42
|
+
* For example, given:
|
|
43
|
+
*
|
|
44
|
+
* const baseSchema = z.string().refine(...);
|
|
45
|
+
* const constrainedSchema = baseSchema.refine(...);
|
|
46
|
+
*
|
|
47
|
+
* This function will return references to `baseSchema` (like the one used
|
|
48
|
+
* in `constrainedSchema`), so we can also migrate those.
|
|
49
|
+
*/
|
|
50
|
+
export function collectDerivedZodSchemaReferences(zodReferences) {
|
|
51
|
+
const derivedReferences = [];
|
|
52
|
+
const visited = new Set();
|
|
53
|
+
function collectFromReferences(refs) {
|
|
54
|
+
for (const ref of refs) {
|
|
55
|
+
// Find the variable declaration that contains this Zod reference
|
|
56
|
+
const variableDeclaration = ref.getFirstAncestorByKind(SyntaxKind.VariableDeclaration);
|
|
57
|
+
if (!variableDeclaration) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const variableName = variableDeclaration.getName();
|
|
61
|
+
if (visited.has(variableName)) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
visited.add(variableName);
|
|
65
|
+
// Get the identifier node for the variable name
|
|
66
|
+
const nameNode = variableDeclaration.getNameNode();
|
|
67
|
+
if (!nameNode.isKind(SyntaxKind.Identifier)) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
// Find all references to this variable (excluding the declaration itself)
|
|
71
|
+
const variableReferences = nameNode
|
|
72
|
+
.findReferencesAsNodes()
|
|
73
|
+
.filter((node) => node !== nameNode)
|
|
74
|
+
.filter((node) => node.isKind(SyntaxKind.Identifier));
|
|
75
|
+
derivedReferences.push(...variableReferences);
|
|
76
|
+
// Recursively collect references from these derived schemas
|
|
77
|
+
collectFromReferences(variableReferences);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
collectFromReferences(zodReferences);
|
|
81
|
+
return derivedReferences;
|
|
82
|
+
}
|
|
@@ -85,6 +85,8 @@ export function convertZCoercePatternsToTopLevelApi(node, zodName) {
|
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
87
|
export function convertZObjectPatternsToTopLevelApi(node, zodName) {
|
|
88
|
+
// First, convert z.object() methods like .strict() or .passthrough()
|
|
89
|
+
convertObjectMethodOnSchemaReference(node, zodName);
|
|
88
90
|
convertNameToTopLevelApi(node, {
|
|
89
91
|
zodName,
|
|
90
92
|
oldName: "object",
|
|
@@ -98,6 +100,56 @@ export function convertZObjectPatternsToTopLevelApi(node, zodName) {
|
|
|
98
100
|
});
|
|
99
101
|
convertZObjectMergeToExtend(node);
|
|
100
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* @example
|
|
105
|
+
* UserSchema.pick({ id: true }).strict()
|
|
106
|
+
* // becomes
|
|
107
|
+
* z.strictObject(UserSchema.pick({ id: true }).shape)
|
|
108
|
+
*/
|
|
109
|
+
function convertObjectMethodOnSchemaReference(node, zodName) {
|
|
110
|
+
const methodMappings = {
|
|
111
|
+
strict: "strictObject",
|
|
112
|
+
passthrough: "looseObject",
|
|
113
|
+
strip: "object",
|
|
114
|
+
nonstrict: "object",
|
|
115
|
+
};
|
|
116
|
+
const methodNames = Object.keys(methodMappings);
|
|
117
|
+
node
|
|
118
|
+
.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression)
|
|
119
|
+
// Start from the deepest to handle nested cases
|
|
120
|
+
.reverse()
|
|
121
|
+
.filter((e) => methodNames.includes(e.getName()))
|
|
122
|
+
.forEach((propertyAccess) => {
|
|
123
|
+
const methodName = propertyAccess.getName();
|
|
124
|
+
const parent = propertyAccess.getFirstAncestorByKind(SyntaxKind.CallExpression);
|
|
125
|
+
if (!parent || parent.getExpression() !== propertyAccess) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const baseExpression = propertyAccess.getExpression();
|
|
129
|
+
const baseText = baseExpression.getText();
|
|
130
|
+
const hasZObjectInChain = baseExpression
|
|
131
|
+
.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression)
|
|
132
|
+
.some((e) => e.getName() === "object" && e.getExpression().getText() === zodName);
|
|
133
|
+
if (hasZObjectInChain) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (baseExpression.isKind(SyntaxKind.CallExpression) &&
|
|
137
|
+
baseExpression
|
|
138
|
+
.getExpression()
|
|
139
|
+
.isKind(SyntaxKind.PropertyAccessExpression)) {
|
|
140
|
+
const propAccess = baseExpression.getExpression();
|
|
141
|
+
if (propAccess.isKind(SyntaxKind.PropertyAccessExpression) &&
|
|
142
|
+
propAccess.getName() === "object" &&
|
|
143
|
+
propAccess.getExpression().getText() === zodName) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// This is a schema reference with .strict(), etc.
|
|
148
|
+
// Transform it to `z.strictObject(schemaRef.shape)`
|
|
149
|
+
const newName = methodMappings[methodName];
|
|
150
|
+
parent.replaceWithText(`${zodName}.${newName}(${baseText}.shape)`);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
101
153
|
function convertZObjectMergeToExtend(node) {
|
|
102
154
|
node
|
|
103
155
|
.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression)
|
|
@@ -123,7 +175,10 @@ export function convertZArrayPatternsToTopLevelApi(node, zodName) {
|
|
|
123
175
|
getCallExpressionsToConvert(node, {
|
|
124
176
|
oldName,
|
|
125
177
|
names,
|
|
126
|
-
})
|
|
178
|
+
})
|
|
179
|
+
// Start from the deepest, otherwise we can't get info from removed nodes
|
|
180
|
+
.reverse()
|
|
181
|
+
.forEach((callExpression) => {
|
|
127
182
|
// Remove deprecated names from the chain
|
|
128
183
|
callExpression
|
|
129
184
|
.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression)
|
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
import { SyntaxKind, } from "ts-morph";
|
|
2
2
|
import { getDirectDescendantsOfKind, isZodReference, } from "./zod-node.js";
|
|
3
|
+
export function convertSetErrorMapToConfig(sourceFile, zodName) {
|
|
4
|
+
sourceFile
|
|
5
|
+
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
6
|
+
.filter((expression) => {
|
|
7
|
+
const callee = expression.getExpression();
|
|
8
|
+
if (!callee.isKind(SyntaxKind.PropertyAccessExpression)) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
const objectName = callee.getExpression().getText();
|
|
12
|
+
const methodName = callee.getName();
|
|
13
|
+
return objectName === zodName && methodName === "setErrorMap";
|
|
14
|
+
})
|
|
15
|
+
.forEach((expression) => {
|
|
16
|
+
const argument = expression.getArguments()[0];
|
|
17
|
+
if (!argument) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
expression.replaceWithText(`${zodName}.config({ customError: ${argument.getText()} })`);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export function convertZodErrorMapType(sourceFile, zodName) {
|
|
24
|
+
sourceFile
|
|
25
|
+
.getDescendantsOfKind(SyntaxKind.TypeReference)
|
|
26
|
+
.filter((typeRef) => {
|
|
27
|
+
const typeName = typeRef.getTypeName();
|
|
28
|
+
if (!typeName.isKind(SyntaxKind.QualifiedName)) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const left = typeName.getLeft().getText();
|
|
32
|
+
const right = typeName.getRight().getText();
|
|
33
|
+
return left === zodName && right === "ZodErrorMap";
|
|
34
|
+
})
|
|
35
|
+
.forEach((typeRef) => {
|
|
36
|
+
typeRef.replaceWithText(`${zodName}.core.$ZodErrorMap`);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
3
39
|
export function convertErrorMapToErrorFunction(node) {
|
|
4
40
|
// Find all errorMap properties
|
|
5
41
|
getDirectDescendantsOfKind(node, SyntaxKind.Identifier)
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
import { cancel, confirm, intro, isCancel, log, outro, progress, text, } from "@clack/prompts";
|
|
3
3
|
import { exec } from "node:child_process";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
|
+
import * as path from "node:path";
|
|
5
6
|
import { promisify } from "node:util";
|
|
6
7
|
import { Project } from "ts-morph";
|
|
8
|
+
import { z } from "zod";
|
|
7
9
|
import { migrateZodV3ToV4 } from "./migrate.js";
|
|
8
10
|
const execAsync = promisify(exec);
|
|
9
11
|
intro(`🏗️ Let's migrate Zod from v3 to v4`);
|
|
@@ -56,7 +58,26 @@ if (isCancel(tsConfigFilePath)) {
|
|
|
56
58
|
await runMigration(tsConfigFilePath);
|
|
57
59
|
async function runMigration(tsConfigFilePath) {
|
|
58
60
|
const project = new Project({ tsConfigFilePath });
|
|
59
|
-
|
|
61
|
+
let filesToProcess = project.getSourceFiles();
|
|
62
|
+
const references = getProjectReferences(tsConfigFilePath);
|
|
63
|
+
// If we can only find refs, we are probably in a monorepo setup.
|
|
64
|
+
// Ask the user if they want to migrate all the references.
|
|
65
|
+
const isMonorepoRoot = filesToProcess.length === 0 && references.length > 0;
|
|
66
|
+
if (isMonorepoRoot) {
|
|
67
|
+
const shouldFollowRefs = await confirm({
|
|
68
|
+
message: `Found ${references.length} project reference(s). Migrate all referenced packages?`,
|
|
69
|
+
});
|
|
70
|
+
if (isCancel(shouldFollowRefs)) {
|
|
71
|
+
cancel("Migration cancelled.");
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
if (shouldFollowRefs) {
|
|
75
|
+
for (const refPath of references) {
|
|
76
|
+
project.addSourceFilesFromTsConfig(refPath);
|
|
77
|
+
}
|
|
78
|
+
filesToProcess = project.getSourceFiles();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
60
81
|
let processedFilesCount = 0;
|
|
61
82
|
const progressBar = progress({ max: filesToProcess.length });
|
|
62
83
|
progressBar.start("Processing files...");
|
|
@@ -85,6 +106,46 @@ async function runMigration(tsConfigFilePath) {
|
|
|
85
106
|
|
|
86
107
|
ℹ️ If the migration missed something or did something wrong, please report it at https://github.com/nicoespeon/zod-v3-to-v4/issues`);
|
|
87
108
|
}
|
|
109
|
+
function getProjectReferences(tsConfigFilePath) {
|
|
110
|
+
// Use Zod to parse the tsconfig.json file for interesting fields.
|
|
111
|
+
const tsConfigWithRefs = z
|
|
112
|
+
.object({
|
|
113
|
+
references: z
|
|
114
|
+
.array(z.object({
|
|
115
|
+
path: z.string().nullable(),
|
|
116
|
+
}))
|
|
117
|
+
.default([]),
|
|
118
|
+
})
|
|
119
|
+
.passthrough();
|
|
120
|
+
try {
|
|
121
|
+
const content = fs.readFileSync(tsConfigFilePath, "utf-8");
|
|
122
|
+
const config = tsConfigWithRefs.parse(JSON.parse(content));
|
|
123
|
+
const tsConfigDir = path.dirname(tsConfigFilePath);
|
|
124
|
+
return config.references
|
|
125
|
+
.map((ref) => {
|
|
126
|
+
if (!ref.path)
|
|
127
|
+
return null;
|
|
128
|
+
const refDir = path.resolve(tsConfigDir, ref.path);
|
|
129
|
+
// Scenario: `ref.path` points directly to the tsconfig file
|
|
130
|
+
if (ref.path.endsWith(".json") && fs.existsSync(refDir)) {
|
|
131
|
+
return refDir;
|
|
132
|
+
}
|
|
133
|
+
// Otherwise, look for `tsconfig.json` in the referenced directory.
|
|
134
|
+
// We don't support other names yet. Workaround is to reference
|
|
135
|
+
// the tsconfig path or directly run the codemod with the path
|
|
136
|
+
// to the tsconfig you want to migrate.
|
|
137
|
+
const refTsConfig = path.join(refDir, "tsconfig.json");
|
|
138
|
+
if (fs.existsSync(refTsConfig)) {
|
|
139
|
+
return refTsConfig;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
})
|
|
143
|
+
.filter((p) => p !== null);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
88
149
|
function validateTsConfigPath(path) {
|
|
89
150
|
if (!path.endsWith(".json")) {
|
|
90
151
|
return {
|
package/dist/migrate.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { SyntaxKind } from "ts-morph";
|
|
2
2
|
import { findRootExpression } from "./ast.js";
|
|
3
|
-
import { AstroZodModuleSpecifiers, collectZodImportDeclarations, collectZodReferences, getZodName, } from "./collect-imports.js";
|
|
3
|
+
import { AstroZodModuleSpecifiers, collectDerivedZodSchemaReferences, collectZodImportDeclarations, collectZodReferences, getZodName, } from "./collect-imports.js";
|
|
4
4
|
import { convertAstroDeprecatedZodImports } from "./convert-astro-imports.js";
|
|
5
5
|
import { convertZArrayPatternsToTopLevelApi, convertZCoercePatternsToTopLevelApi, convertZFunctionPatternsToTopLevelApi, convertZNumberPatternsToZInt, convertZObjectPatternsToTopLevelApi, convertZRecordPatternsToTopLevelApi, convertZStringPatternsToTopLevelApi, } from "./convert-name-to-top-level-api.js";
|
|
6
|
-
import { convertDeprecatedErrorKeysToErrorFunction, convertErrorMapToErrorFunction, convertMessageKeyToError, convertZodErrorAddIssueToDirectPushes, convertZodErrorToTreeifyError, } from "./convert-zod-errors.js";
|
|
6
|
+
import { convertDeprecatedErrorKeysToErrorFunction, convertErrorMapToErrorFunction, convertMessageKeyToError, convertSetErrorMapToConfig, convertZodErrorAddIssueToDirectPushes, convertZodErrorMapType, convertZodErrorToTreeifyError, } from "./convert-zod-errors.js";
|
|
7
7
|
import { replaceDeletedTypes } from "./replace-deleted-types.js";
|
|
8
8
|
import { isZodNode } from "./zod-node.js";
|
|
9
9
|
export function migrateZodV3ToV4(sourceFile, options = {}) {
|
|
@@ -26,8 +26,9 @@ export function migrateZodV3ToV4(sourceFile, options = {}) {
|
|
|
26
26
|
});
|
|
27
27
|
// Collect references before modifying imports
|
|
28
28
|
const zodReferences = collectZodReferences(importDeclarations);
|
|
29
|
+
const derivedReferences = collectDerivedZodSchemaReferences(zodReferences);
|
|
29
30
|
replaceDeletedTypes(importDeclarations, zodReferences);
|
|
30
|
-
zodReferences.forEach((node) => {
|
|
31
|
+
[...zodReferences, ...derivedReferences].forEach((node) => {
|
|
31
32
|
if (node.wasForgotten()) {
|
|
32
33
|
return;
|
|
33
34
|
}
|
|
@@ -55,6 +56,8 @@ export function migrateZodV3ToV4(sourceFile, options = {}) {
|
|
|
55
56
|
});
|
|
56
57
|
convertZodErrorToTreeifyError(sourceFile, zodName);
|
|
57
58
|
convertZodErrorAddIssueToDirectPushes(sourceFile, zodName);
|
|
59
|
+
convertSetErrorMapToConfig(sourceFile, zodName);
|
|
60
|
+
convertZodErrorMapType(sourceFile, zodName);
|
|
58
61
|
renameZSchemaEnumToLowercase(sourceFile, zodName);
|
|
59
62
|
convertAstroDeprecatedZodImports(importDeclarations);
|
|
60
63
|
return sourceFile.getFullText();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zod-v3-to-v4",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.1",
|
|
4
4
|
"description": "Migrate Zod from v3 to v4",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"zod",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"format-check": "prettier . --list-different",
|
|
34
34
|
"playground": "pnpm playground:interactive playground/tsconfig.json",
|
|
35
35
|
"playground:interactive": "node --experimental-strip-types --disable-warning=ExperimentalWarning src/index.ts",
|
|
36
|
+
"playground:monorepo": "pnpm playground:interactive playground-monorepo/tsconfig.json",
|
|
36
37
|
"prepare": "husky",
|
|
37
38
|
"prepublishOnly": "pnpm typecheck && pnpm test && pnpm build",
|
|
38
39
|
"test": "vitest run",
|
|
@@ -43,22 +44,22 @@
|
|
|
43
44
|
"*": "prettier --ignore-unknown --write"
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
46
|
-
"@clack/prompts": "1.0.0-alpha.
|
|
47
|
-
"ts-morph": "26.0.0"
|
|
47
|
+
"@clack/prompts": "1.0.0-alpha.9",
|
|
48
|
+
"ts-morph": "26.0.0",
|
|
49
|
+
"zod": "3.25.12"
|
|
48
50
|
},
|
|
49
51
|
"devDependencies": {
|
|
50
|
-
"@types/node": "
|
|
52
|
+
"@types/node": "25.0.3",
|
|
51
53
|
"husky": "9.1.7",
|
|
52
|
-
"lint-staged": "16.
|
|
54
|
+
"lint-staged": "16.2.7",
|
|
53
55
|
"prettier": "3.5.3",
|
|
54
56
|
"prettier-plugin-curly": "0.3.1",
|
|
55
57
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
56
58
|
"prettier-plugin-packagejson": "2.5.10",
|
|
57
|
-
"prettier-plugin-sh": "0.
|
|
59
|
+
"prettier-plugin-sh": "0.17.4",
|
|
58
60
|
"rimraf": "6.0.1",
|
|
59
|
-
"typescript": "5.
|
|
60
|
-
"vitest": "3.1.4"
|
|
61
|
-
"zod": "3.25.12"
|
|
61
|
+
"typescript": "5.9.3",
|
|
62
|
+
"vitest": "3.1.4"
|
|
62
63
|
},
|
|
63
64
|
"packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0"
|
|
64
65
|
}
|