summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/slow-items-heal.md44
-rw-r--r--packages/astro/package.json3
-rw-r--r--packages/astro/src/@types/astro.ts48
-rw-r--r--packages/astro/src/content/types-generator.ts72
-rw-r--r--packages/astro/src/core/config/schema.ts5
-rw-r--r--packages/astro/test/data-collections-schema.test.js54
-rw-r--r--packages/astro/test/fixtures/data-collections-schema/astro.config.mjs8
-rw-r--r--packages/astro/test/fixtures/data-collections-schema/package.json16
-rw-r--r--packages/astro/test/fixtures/data-collections-schema/src/content/authors-without-config/Ben Holmes.yml2
-rw-r--r--packages/astro/test/fixtures/data-collections-schema/src/content/authors-without-config/Fred K Schott.yml2
-rw-r--r--packages/astro/test/fixtures/data-collections-schema/src/content/authors-without-config/Nate Moore.yml2
-rw-r--r--packages/astro/test/fixtures/data-collections-schema/src/content/config.ts20
-rw-r--r--packages/astro/test/fixtures/data-collections-schema/src/content/docs/example.md3
-rw-r--r--packages/astro/test/fixtures/data-collections-schema/src/content/i18n/en.json6
-rw-r--r--packages/astro/test/fixtures/data-collections-schema/src/content/i18n/es.json6
-rw-r--r--packages/astro/test/fixtures/data-collections-schema/src/content/i18n/fr.yaml3
-rw-r--r--packages/astro/test/fixtures/data-collections-schema/src/pages/authors/[id].json.js18
-rw-r--r--packages/astro/test/fixtures/data-collections-schema/src/pages/authors/all.json.js6
-rw-r--r--packages/astro/test/fixtures/data-collections-schema/src/pages/translations/[lang].json.js18
-rw-r--r--packages/astro/test/fixtures/data-collections-schema/src/pages/translations/all.json.js6
-rw-r--r--packages/astro/test/fixtures/data-collections-schema/src/pages/translations/by-id.json.js6
-rw-r--r--pnpm-lock.yaml17
22 files changed, 359 insertions, 6 deletions
diff --git a/.changeset/slow-items-heal.md b/.changeset/slow-items-heal.md
new file mode 100644
index 000000000..0095974f6
--- /dev/null
+++ b/.changeset/slow-items-heal.md
@@ -0,0 +1,44 @@
+---
+"astro": minor
+---
+
+Adds experimental JSON Schema support for content collections.
+
+This feature will auto-generate a JSON Schema for content collections of `type: 'data'` which can be used as the `$schema` value for TypeScript-style autocompletion/hints in tools like VSCode.
+
+To enable this feature, add the experimental flag:
+
+```diff
+import { defineConfig } from 'astro/config';
+
+export default defineConfig({
+ experimental: {
++ contentCollectionJsonSchema: true
+ }
+});
+```
+
+This experimental implementation requires you to manually reference the schema in each data entry file of the collection:
+
+```diff
+// src/content/test/entry.json
+{
++ "$schema": "../../../.astro/collections/test.schema.json",
+ "test": "test"
+}
+```
+
+Alternatively, you can set this in your [VSCode `json.schemas` settings](https://code.visualstudio.com/docs/languages/json#_json-schemas-and-settings):
+
+```diff
+"json.schemas": [
+ {
+ "fileMatch": [
+ "/src/content/test/**"
+ ],
+ "url": "../../../.astro/collections/test.schema.json"
+ }
+]
+```
+
+Note that this initial implementation uses a library with [known issues for advanced Zod schemas](https://github.com/StefanTerdell/zod-to-json-schema#known-issues), so you may wish to consult these limitations before enabling the experimental flag.
diff --git a/packages/astro/package.json b/packages/astro/package.json
index b1bbf0b2f..ea8393f5d 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -177,7 +177,8 @@
"vitefu": "^0.2.5",
"which-pm": "^2.1.1",
"yargs-parser": "^21.1.1",
- "zod": "^3.22.4"
+ "zod": "^3.22.4",
+ "zod-to-json-schema": "^3.22.4"
},
"optionalDependencies": {
"sharp": "^0.32.6"
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index 932d656e7..74d557924 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -1646,6 +1646,54 @@ export interface AstroUserConfig {
/**
* @docs
+ * @name experimental.contentCollectionJsonSchema
+ * @type {boolean}
+ * @default `false`
+ * @version 4.5.0
+ * @description
+ * This feature will auto-generate a JSON schema for content collections of `type: 'data'` which can be used as the `$schema` value for TypeScript-style autocompletion/hints in tools like VSCode.
+ *
+ * To enable this feature, add the experimental flag:
+ *
+ * ```diff
+ * import { defineConfig } from 'astro/config';
+
+ * export default defineConfig({
+ * experimental: {
+ * + contentCollectionJsonSchema: true
+ * }
+ * });
+ * ```
+ *
+ * This experimental implementation requires you to manually reference the schema in each data entry file of the collection:
+ *
+ * ```diff
+ * // src/content/test/entry.json
+ * {
+ * + "$schema": "../../../.astro/collections/test.schema.json",
+ * "test": "test"
+ * }
+ * ```
+ *
+ * Alternatively, you can set this in your [VSCode `json.schemas` settings](https://code.visualstudio.com/docs/languages/json#_json-schemas-and-settings):
+ *
+ * ```diff
+ * "json.schemas": [
+ * {
+ * "fileMatch": [
+ * "/src/content/test/**"
+ * ],
+ * "url": "../../../.astro/collections/test.schema.json"
+ * }
+ * ]
+ * ```
+ *
+ * Note that this initial implementation uses a library with [known issues for advanced Zod schemas](https://github.com/StefanTerdell/zod-to-json-schema#known-issues), so you may wish to consult these limitations before enabling the experimental flag.
+ */
+ contentCollectionJsonSchema?: boolean;
+
+ /**
+ * @docs
* @name experimental.clientPrerender
* @type {boolean}
* @default `false`
diff --git a/packages/astro/src/content/types-generator.ts b/packages/astro/src/content/types-generator.ts
index a16105ed7..5eabbc3c1 100644
--- a/packages/astro/src/content/types-generator.ts
+++ b/packages/astro/src/content/types-generator.ts
@@ -4,6 +4,8 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
import glob from 'fast-glob';
import { bold, cyan } from 'kleur/colors';
import { type ViteDevServer, normalizePath } from 'vite';
+import { z } from 'zod';
+import { zodToJsonSchema } from 'zod-to-json-schema';
import type { AstroSettings, ContentEntryType } from '../@types/astro.js';
import { AstroError } from '../core/errors/errors.js';
import { AstroErrorData } from '../core/errors/index.js';
@@ -119,7 +121,10 @@ export async function createContentTypesGenerator({
switch (event.name) {
case 'addDir':
- collectionEntryMap[JSON.stringify(collection)] = { type: 'unknown', entries: {} };
+ collectionEntryMap[JSON.stringify(collection)] = {
+ type: 'unknown',
+ entries: {},
+ };
logger.debug('content', `${cyan(collection)} collection added`);
break;
case 'unlinkDir':
@@ -204,7 +209,11 @@ export async function createContentTypesGenerator({
const contentEntryType = contentEntryConfigByExt.get(path.extname(event.entry.pathname));
if (!contentEntryType) return { shouldGenerateTypes: false };
- const { id, slug: generatedSlug } = getContentEntryIdAndSlug({ entry, contentDir, collection });
+ const { id, slug: generatedSlug } = getContentEntryIdAndSlug({
+ entry,
+ contentDir,
+ collection,
+ });
const collectionKey = JSON.stringify(collection);
if (!(collectionKey in collectionEntryMap)) {
@@ -237,7 +246,10 @@ export async function createContentTypesGenerator({
if (!(entryKey in collectionEntryMap[collectionKey].entries)) {
collectionEntryMap[collectionKey] = {
type: 'content',
- entries: { ...collectionInfo.entries, [entryKey]: { slug: addedSlug } },
+ entries: {
+ ...collectionInfo.entries,
+ [entryKey]: { slug: addedSlug },
+ },
};
}
return { shouldGenerateTypes: true };
@@ -310,6 +322,8 @@ export async function createContentTypesGenerator({
contentConfig: observable.status === 'loaded' ? observable.config : undefined,
contentEntryTypes: settings.contentEntryTypes,
viteServer,
+ logger,
+ settings,
});
invalidateVirtualMod(viteServer);
}
@@ -352,6 +366,8 @@ async function writeContentFiles({
contentEntryTypes,
contentConfig,
viteServer,
+ logger,
+ settings,
}: {
fs: typeof fsMod;
contentPaths: ContentPaths;
@@ -360,11 +376,25 @@ async function writeContentFiles({
contentEntryTypes: Pick<ContentEntryType, 'contentModuleTypes'>[];
contentConfig?: ContentConfig;
viteServer: Pick<ViteDevServer, 'hot'>;
+ logger: Logger;
+ settings: AstroSettings;
}) {
let contentTypesStr = '';
let dataTypesStr = '';
+
+ const collectionSchemasDir = new URL('./collections/', contentPaths.cacheDir);
+ if (
+ settings.config.experimental.contentCollectionJsonSchema &&
+ !fs.existsSync(collectionSchemasDir)
+ ) {
+ fs.mkdirSync(collectionSchemasDir, { recursive: true });
+ }
+
for (const [collection, config] of Object.entries(contentConfig?.collections ?? {})) {
- collectionEntryMap[JSON.stringify(collection)] ??= { type: config.type, entries: {} };
+ collectionEntryMap[JSON.stringify(collection)] ??= {
+ type: config.type,
+ entries: {},
+ };
}
for (const collectionKey of Object.keys(collectionEntryMap).sort()) {
const collectionConfig = contentConfig?.collections[JSON.parse(collectionKey)];
@@ -387,7 +417,9 @@ async function writeContentFiles({
collection.type === 'data'
? "Try adding `type: 'data'` to your collection config."
: undefined,
- location: { file: '' /** required for error overlay `hot` messages */ },
+ location: {
+ file: '' /** required for error overlay `hot` messages */,
+ },
}) as any,
});
return;
@@ -419,6 +451,36 @@ async function writeContentFiles({
for (const entryKey of Object.keys(collection.entries).sort()) {
const dataType = collectionConfig?.schema ? `InferEntrySchema<${collectionKey}>` : 'any';
dataTypesStr += `${entryKey}: {\n id: ${entryKey};\n collection: ${collectionKey};\n data: ${dataType}\n};\n`;
+ if (
+ settings.config.experimental.contentCollectionJsonSchema &&
+ collectionConfig?.schema
+ ) {
+ let zodSchemaForJson = collectionConfig.schema;
+ if (zodSchemaForJson instanceof z.ZodObject) {
+ zodSchemaForJson = zodSchemaForJson.extend({
+ $schema: z.string().optional(),
+ });
+ }
+ try {
+ await fs.promises.writeFile(
+ new URL(`./${collectionKey.replace(/"/g, '')}.schema.json`, collectionSchemasDir),
+ JSON.stringify(
+ zodToJsonSchema(zodSchemaForJson, {
+ name: collectionKey.replace(/"/g, ''),
+ markdownDescription: true,
+ errorMessages: true,
+ }),
+ null,
+ 2
+ )
+ );
+ } catch (err) {
+ logger.warn(
+ 'content',
+ `An error was encountered while creating the JSON schema. Proceeding without it. Error: ${err}`
+ );
+ }
+ }
}
dataTypesStr += `};\n`;
break;
diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts
index 4c0276ddf..3af553993 100644
--- a/packages/astro/src/core/config/schema.ts
+++ b/packages/astro/src/core/config/schema.ts
@@ -60,6 +60,7 @@ const ASTRO_CONFIG_DEFAULTS = {
experimental: {
optimizeHoistedScript: false,
contentCollectionCache: false,
+ contentCollectionJsonSchema: false,
clientPrerender: false,
globalRoutePriority: false,
i18nDomains: false,
@@ -458,6 +459,10 @@ export const AstroConfigSchema = z.object({
.boolean()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.experimental.contentCollectionCache),
+ contentCollectionJsonSchema: z
+ .boolean()
+ .optional()
+ .default(ASTRO_CONFIG_DEFAULTS.experimental.contentCollectionJsonSchema),
clientPrerender: z
.boolean()
.optional()
diff --git a/packages/astro/test/data-collections-schema.test.js b/packages/astro/test/data-collections-schema.test.js
new file mode 100644
index 000000000..7a726802e
--- /dev/null
+++ b/packages/astro/test/data-collections-schema.test.js
@@ -0,0 +1,54 @@
+import assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { loadFixture } from './test-utils.js';
+
+describe('Content Collections - data collections', () => {
+ let fixture;
+ before(async () => {
+ fixture = await loadFixture({ root: './fixtures/data-collections-schema/' });
+ await fixture.build();
+ });
+
+ describe('Translations Collection', () => {
+ it('Generates schema file', async () => {
+ const schemaExists = await fixture.pathExists('../.astro/collections/i18n.schema.json');
+ assert.equal(schemaExists, true);
+ });
+
+ it('Generates valid schema file', async () => {
+ const rawJson = await fixture.readFile('../.astro/collections/i18n.schema.json');
+ assert.deepEqual(
+ JSON.stringify({
+ $ref: '#/definitions/i18n',
+ definitions: {
+ i18n: {
+ type: 'object',
+ properties: {
+ homepage: {
+ type: 'object',
+ properties: {
+ greeting: {
+ type: 'string',
+ },
+ preamble: {
+ type: 'string',
+ },
+ },
+ required: ['greeting', 'preamble'],
+ additionalProperties: false,
+ },
+ $schema: {
+ type: 'string',
+ },
+ },
+ required: ['homepage'],
+ additionalProperties: false,
+ },
+ },
+ $schema: 'http://json-schema.org/draft-07/schema#',
+ }),
+ JSON.stringify(JSON.parse(rawJson))
+ );
+ });
+ });
+});
diff --git a/packages/astro/test/fixtures/data-collections-schema/astro.config.mjs b/packages/astro/test/fixtures/data-collections-schema/astro.config.mjs
new file mode 100644
index 000000000..59e5784d1
--- /dev/null
+++ b/packages/astro/test/fixtures/data-collections-schema/astro.config.mjs
@@ -0,0 +1,8 @@
+import { defineConfig } from 'astro/config';
+
+// https://astro.build/config
+export default defineConfig({
+ experimental: {
+ contentCollectionJsonSchema: true
+ }
+});
diff --git a/packages/astro/test/fixtures/data-collections-schema/package.json b/packages/astro/test/fixtures/data-collections-schema/package.json
new file mode 100644
index 000000000..77b213415
--- /dev/null
+++ b/packages/astro/test/fixtures/data-collections-schema/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@test/data-collections-schema",
+ "type": "module",
+ "version": "0.0.1",
+ "private": true,
+ "scripts": {
+ "dev": "astro dev",
+ "start": "astro dev",
+ "build": "astro build",
+ "preview": "astro preview",
+ "astro": "astro"
+ },
+ "dependencies": {
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/astro/test/fixtures/data-collections-schema/src/content/authors-without-config/Ben Holmes.yml b/packages/astro/test/fixtures/data-collections-schema/src/content/authors-without-config/Ben Holmes.yml
new file mode 100644
index 000000000..54e6743d9
--- /dev/null
+++ b/packages/astro/test/fixtures/data-collections-schema/src/content/authors-without-config/Ben Holmes.yml
@@ -0,0 +1,2 @@
+name: Ben J Holmes
+twitter: https://twitter.com/bholmesdev
diff --git a/packages/astro/test/fixtures/data-collections-schema/src/content/authors-without-config/Fred K Schott.yml b/packages/astro/test/fixtures/data-collections-schema/src/content/authors-without-config/Fred K Schott.yml
new file mode 100644
index 000000000..0b51067d9
--- /dev/null
+++ b/packages/astro/test/fixtures/data-collections-schema/src/content/authors-without-config/Fred K Schott.yml
@@ -0,0 +1,2 @@
+name: Fred K Schott
+twitter: https://twitter.com/FredKSchott
diff --git a/packages/astro/test/fixtures/data-collections-schema/src/content/authors-without-config/Nate Moore.yml b/packages/astro/test/fixtures/data-collections-schema/src/content/authors-without-config/Nate Moore.yml
new file mode 100644
index 000000000..953f348a0
--- /dev/null
+++ b/packages/astro/test/fixtures/data-collections-schema/src/content/authors-without-config/Nate Moore.yml
@@ -0,0 +1,2 @@
+name: Nate Something Moore
+twitter: https://twitter.com/n_moore
diff --git a/packages/astro/test/fixtures/data-collections-schema/src/content/config.ts b/packages/astro/test/fixtures/data-collections-schema/src/content/config.ts
new file mode 100644
index 000000000..5f3de9423
--- /dev/null
+++ b/packages/astro/test/fixtures/data-collections-schema/src/content/config.ts
@@ -0,0 +1,20 @@
+import { defineCollection, z } from 'astro:content';
+
+const docs = defineCollection({
+ type: 'content',
+ schema: z.object({
+ title: z.string(),
+ })
+});
+
+const i18n = defineCollection({
+ type: 'data',
+ schema: z.object({
+ homepage: z.object({
+ greeting: z.string(),
+ preamble: z.string(),
+ })
+ }),
+});
+
+export const collections = { docs, i18n };
diff --git a/packages/astro/test/fixtures/data-collections-schema/src/content/docs/example.md b/packages/astro/test/fixtures/data-collections-schema/src/content/docs/example.md
new file mode 100644
index 000000000..356e65f64
--- /dev/null
+++ b/packages/astro/test/fixtures/data-collections-schema/src/content/docs/example.md
@@ -0,0 +1,3 @@
+---
+title: The future of content
+---
diff --git a/packages/astro/test/fixtures/data-collections-schema/src/content/i18n/en.json b/packages/astro/test/fixtures/data-collections-schema/src/content/i18n/en.json
new file mode 100644
index 000000000..51d127f4a
--- /dev/null
+++ b/packages/astro/test/fixtures/data-collections-schema/src/content/i18n/en.json
@@ -0,0 +1,6 @@
+{
+ "homepage": {
+ "greeting": "Hello World!",
+ "preamble": "Welcome to the future of content."
+ }
+}
diff --git a/packages/astro/test/fixtures/data-collections-schema/src/content/i18n/es.json b/packages/astro/test/fixtures/data-collections-schema/src/content/i18n/es.json
new file mode 100644
index 000000000..bf4c7af0f
--- /dev/null
+++ b/packages/astro/test/fixtures/data-collections-schema/src/content/i18n/es.json
@@ -0,0 +1,6 @@
+{
+ "homepage": {
+ "greeting": "¡Hola Mundo!",
+ "preamble": "Bienvenido al futuro del contenido."
+ }
+}
diff --git a/packages/astro/test/fixtures/data-collections-schema/src/content/i18n/fr.yaml b/packages/astro/test/fixtures/data-collections-schema/src/content/i18n/fr.yaml
new file mode 100644
index 000000000..90a86d411
--- /dev/null
+++ b/packages/astro/test/fixtures/data-collections-schema/src/content/i18n/fr.yaml
@@ -0,0 +1,3 @@
+homepage:
+ greeting: "Bonjour le monde!"
+ preamble: "Bienvenue dans le futur du contenu."
diff --git a/packages/astro/test/fixtures/data-collections-schema/src/pages/authors/[id].json.js b/packages/astro/test/fixtures/data-collections-schema/src/pages/authors/[id].json.js
new file mode 100644
index 000000000..8d5365a2e
--- /dev/null
+++ b/packages/astro/test/fixtures/data-collections-schema/src/pages/authors/[id].json.js
@@ -0,0 +1,18 @@
+import { getEntry } from 'astro:content';
+
+const ids = ['Ben Holmes', 'Fred K Schott', 'Nate Moore'];
+
+export function getStaticPaths() {
+ return ids.map((id) => ({ params: { id } }));
+}
+
+/** @param {import('astro').APIContext} params */
+export async function GET({ params }) {
+ const { id } = params;
+ const author = await getEntry('authors-without-config', id);
+ if (!author) {
+ return Response.json({ error: `Author ${id} Not found` });
+ } else {
+ return Response.json(author);
+ }
+}
diff --git a/packages/astro/test/fixtures/data-collections-schema/src/pages/authors/all.json.js b/packages/astro/test/fixtures/data-collections-schema/src/pages/authors/all.json.js
new file mode 100644
index 000000000..79dd8cd9d
--- /dev/null
+++ b/packages/astro/test/fixtures/data-collections-schema/src/pages/authors/all.json.js
@@ -0,0 +1,6 @@
+import { getCollection } from 'astro:content';
+
+export async function GET() {
+ const authors = await getCollection('authors-without-config');
+ return Response.json(authors);
+}
diff --git a/packages/astro/test/fixtures/data-collections-schema/src/pages/translations/[lang].json.js b/packages/astro/test/fixtures/data-collections-schema/src/pages/translations/[lang].json.js
new file mode 100644
index 000000000..c6b0cfff6
--- /dev/null
+++ b/packages/astro/test/fixtures/data-collections-schema/src/pages/translations/[lang].json.js
@@ -0,0 +1,18 @@
+import { getEntry } from 'astro:content';
+
+const langs = ['en', 'es', 'fr'];
+
+export function getStaticPaths() {
+ return langs.map((lang) => ({ params: { lang } }));
+}
+
+/** @param {import('astro').APIContext} params */
+export async function GET({ params }) {
+ const { lang } = params;
+ const translations = await getEntry('i18n', lang);
+ if (!translations) {
+ return Response.json({ error: `Translation ${lang} Not found` });
+ } else {
+ return Response.json(translations);
+ }
+}
diff --git a/packages/astro/test/fixtures/data-collections-schema/src/pages/translations/all.json.js b/packages/astro/test/fixtures/data-collections-schema/src/pages/translations/all.json.js
new file mode 100644
index 000000000..f1ebb15b7
--- /dev/null
+++ b/packages/astro/test/fixtures/data-collections-schema/src/pages/translations/all.json.js
@@ -0,0 +1,6 @@
+import { getCollection } from 'astro:content';
+
+export async function GET() {
+ const translations = await getCollection('i18n');
+ return Response.json(translations);
+}
diff --git a/packages/astro/test/fixtures/data-collections-schema/src/pages/translations/by-id.json.js b/packages/astro/test/fixtures/data-collections-schema/src/pages/translations/by-id.json.js
new file mode 100644
index 000000000..5f71c80e9
--- /dev/null
+++ b/packages/astro/test/fixtures/data-collections-schema/src/pages/translations/by-id.json.js
@@ -0,0 +1,6 @@
+import { getDataEntryById } from 'astro:content';
+
+export async function GET() {
+ const item = await getDataEntryById('i18n', 'en');
+ return Response.json(item);
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 130057300..79bd466bb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -689,6 +689,9 @@ importers:
zod:
specifier: ^3.22.4
version: 3.22.4
+ zod-to-json-schema:
+ specifier: ^3.22.4
+ version: 3.22.4(zod@3.22.4)
optionalDependencies:
sharp:
specifier: ^0.32.6
@@ -2708,6 +2711,12 @@ importers:
specifier: workspace:*
version: link:../../..
+ packages/astro/test/fixtures/data-collections-schema:
+ dependencies:
+ astro:
+ specifier: workspace:*
+ version: link:../../..
+
packages/astro/test/fixtures/debug-component:
dependencies:
astro:
@@ -17053,6 +17062,14 @@ packages:
engines: {node: '>=12.20'}
dev: false
+ /zod-to-json-schema@3.22.4(zod@3.22.4):
+ resolution: {integrity: sha512-2Ed5dJ+n/O3cU383xSY28cuVi0BCQhF8nYqWU5paEpl7fVdqdAmiLdqLyfblbNdfOFwFfi/mqU4O1pwc60iBhQ==}
+ peerDependencies:
+ zod: ^3.22.4
+ dependencies:
+ zod: 3.22.4
+ dev: false
+
/zod@3.22.4:
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
dev: false