mirror of
https://github.com/withastro/astro.git
synced 2025-12-05 18:56:38 -06:00
Compare commits
16 Commits
62edc3156e
...
0f181c3d64
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f181c3d64 | ||
|
|
afd6528184 | ||
|
|
fcdf90d04f | ||
|
|
af68956da1 | ||
|
|
dcb3bdc97d | ||
|
|
c51a23a2c2 | ||
|
|
530e2e06c2 | ||
|
|
bfe74e8c7a | ||
|
|
725859f324 | ||
|
|
8c429bc61e | ||
|
|
713cb7e8a4 | ||
|
|
3a61f4e93d | ||
|
|
215afa07c5 | ||
|
|
2ddd549373 | ||
|
|
4f005ebe1f | ||
|
|
ef994e51a8 |
24
.changeset/dance-ornate-keen.md
Normal file
24
.changeset/dance-ornate-keen.md
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
'astro': major
|
||||
'astro-rss': patch
|
||||
---
|
||||
|
||||
Upgrade to Zod 4
|
||||
|
||||
**Breaking Changes:**
|
||||
|
||||
If you have custom Zod schemas in your `content.config.ts` or other configuration files, you'll need to update them for Zod 4. Refer to the [Zod migration guide](https://zod.dev/v4/changelog) for detailed changes in the Zod API.
|
||||
|
||||
You can import Zod from `astro/zod` to ensure you're using the same version of Zod that Astro uses internally:
|
||||
|
||||
```ts
|
||||
import { z } from 'astro/zod';
|
||||
```
|
||||
|
||||
**Changes:**
|
||||
|
||||
- Updated all internal Zod schemas to use Zod 4 API
|
||||
- Updated error handling and custom error maps for Zod 4 error structure
|
||||
- Removed `zod-to-json-schema` dependency (now built-in to Zod 4)
|
||||
- Updated content collection schemas and type generation
|
||||
- Fixed markdown and image config schema validation
|
||||
@@ -8,7 +8,4 @@ import { defineConfig } from 'astro/config';
|
||||
export default defineConfig({
|
||||
site: 'https://example.com',
|
||||
integrations: [mdx(), sitemap()],
|
||||
experimental: {
|
||||
zod4: true
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineCollection, reference } from 'astro:content';
|
||||
import { defineCollection } from 'astro:content';
|
||||
import { glob } from 'astro/loaders';
|
||||
import { z } from 'astro/zod'
|
||||
|
||||
@@ -14,7 +14,6 @@ const blog = defineCollection({
|
||||
pubDate: z.coerce.date(),
|
||||
updatedDate: z.coerce.date().optional(),
|
||||
heroImage: z.optional(image()),
|
||||
x: reference('blog')
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ const releases = defineCollection({
|
||||
alt: z.string(),
|
||||
}),
|
||||
// Transform string to Date object
|
||||
date: z.date({ coerce: true }),
|
||||
date: z.coerce.date(),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ test.describe('Astro Actions - Blog', () => {
|
||||
const submitButton = form.getByRole('button');
|
||||
await submitButton.click();
|
||||
|
||||
const expectedText = 'Expected string, received null';
|
||||
const expectedText = 'Invalid input: expected string, received null';
|
||||
|
||||
const fields = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ function astroClientClickDirective() {
|
||||
|
||||
function astroClientPasswordDirective() {
|
||||
return {
|
||||
name: 'astro-client-click',
|
||||
name: 'astro-client-password',
|
||||
hooks: {
|
||||
'astro:config:setup': (opts) => {
|
||||
opts.addClientDirective({
|
||||
|
||||
@@ -57,18 +57,18 @@ export type ActionClient<
|
||||
TInputSchema extends z4.$ZodType | undefined,
|
||||
> = TInputSchema extends z4.$ZodType
|
||||
? ((
|
||||
input: TAccept extends 'form' ? FormData : z4.infer<TInputSchema>,
|
||||
input: TAccept extends 'form' ? FormData : z4.input<TInputSchema>,
|
||||
) => Promise<
|
||||
SafeResult<
|
||||
z4.infer<TInputSchema> extends ErrorInferenceObject
|
||||
? z4.infer<TInputSchema>
|
||||
z4.input<TInputSchema> extends ErrorInferenceObject
|
||||
? z4.input<TInputSchema>
|
||||
: ErrorInferenceObject,
|
||||
Awaited<TOutput>
|
||||
>
|
||||
>) & {
|
||||
queryString: string;
|
||||
orThrow: (
|
||||
input: TAccept extends 'form' ? FormData : z4.infer<TInputSchema>,
|
||||
input: TAccept extends 'form' ? FormData : z4.input<TInputSchema>,
|
||||
) => Promise<Awaited<TOutput>>;
|
||||
[inferSymbol]: TInputSchema;
|
||||
}
|
||||
@@ -142,12 +142,13 @@ export function getFormServerHandler<TOutput, TInputSchema extends z4.$ZodType>(
|
||||
|
||||
async function parseFormInput(inputSchema: z4.$ZodType, unparsedInput: FormData) {
|
||||
const baseSchema = unwrapBaseZ4ObjectSchema(inputSchema, unparsedInput);
|
||||
return await z4.safeParseAsync(
|
||||
inputSchema,
|
||||
const input =
|
||||
baseSchema instanceof z4.$ZodObject
|
||||
? formDataToZ4Object(unparsedInput, baseSchema)
|
||||
: unparsedInput,
|
||||
);
|
||||
: unparsedInput;
|
||||
|
||||
const parsed = await z4.safeParseAsync(inputSchema, input);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export function getJsonServerHandler<TOutput, TInputSchema extends z4.$ZodType>(
|
||||
@@ -171,7 +172,6 @@ export function getJsonServerHandler<TOutput, TInputSchema extends z4.$ZodType>(
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/** Transform form data to an object based on a Zod schema. */
|
||||
export function formDataToZ4Object<T extends z4.$ZodObject>(
|
||||
formData: FormData,
|
||||
@@ -213,7 +213,6 @@ export function formDataToZ4Object<T extends z4.$ZodObject>(
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
function handleZ4FormDataGetAll(key: string, formData: FormData, validator: z4.$ZodArray) {
|
||||
const entries = Array.from(formData.getAll(key));
|
||||
const elementValidator = validator._zod.def.element;
|
||||
@@ -225,7 +224,6 @@ function handleZ4FormDataGetAll(key: string, formData: FormData, validator: z4.$
|
||||
return entries;
|
||||
}
|
||||
|
||||
|
||||
function handleZ4FormDataGet(
|
||||
key: string,
|
||||
formData: FormData,
|
||||
@@ -239,10 +237,9 @@ function handleZ4FormDataGet(
|
||||
return validator instanceof z4.$ZodNumber ? Number(value) : value;
|
||||
}
|
||||
|
||||
|
||||
function unwrapBaseZ4ObjectSchema(schema: z4.$ZodType, unparsedInput: FormData) {
|
||||
if (schema instanceof z4.$ZodPipe) {
|
||||
return schema._zod.def.in;
|
||||
return unwrapBaseZ4ObjectSchema(schema._zod.def.in, unparsedInput);
|
||||
}
|
||||
if (schema instanceof z4.$ZodDiscriminatedUnion) {
|
||||
const typeKey = schema._zod.def.discriminator;
|
||||
|
||||
@@ -154,8 +154,6 @@ export function defineLiveCollection<
|
||||
});
|
||||
}
|
||||
|
||||
// Z4 schema is the only supported version now
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -200,8 +198,6 @@ export function defineCollection<
|
||||
}
|
||||
config.type = CONTENT_LAYER_TYPE;
|
||||
|
||||
// Z4 schema is the only supported version now
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,24 @@ const collectionConfigParser = z.union([
|
||||
types?: string;
|
||||
} | void>(),
|
||||
}),
|
||||
schema: z.any().optional(),
|
||||
schema: z.any().transform((v) => {
|
||||
if (typeof v === 'function') {
|
||||
console.warn(
|
||||
`Your loader's schema is defined using a function. This is no longer supported and the schema will be ignored. Please update your loader to use the \`createSchema()\` utility instead, or report this to the loader author. In a future major version, this will cause the loader to break entirely.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
return v;
|
||||
})
|
||||
.superRefine((v, ctx) => {
|
||||
if (v !== undefined && !('_def' in v)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'Invalid Zod schema',
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
}).optional(),
|
||||
createSchema: z
|
||||
.function({
|
||||
input: [],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { MiddlewareHandler } from '../../types/public/common.js';
|
||||
import { defineMiddleware } from '../middleware/index.js';
|
||||
import { defineMiddleware } from '../middleware/defineMiddleware.js';
|
||||
|
||||
/**
|
||||
* Content types that can be passed when sending a request via a form
|
||||
|
||||
5
packages/astro/src/core/middleware/defineMiddleware.ts
Normal file
5
packages/astro/src/core/middleware/defineMiddleware.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { MiddlewareHandler } from '../../types/public/common.js';
|
||||
|
||||
export function defineMiddleware(fn: MiddlewareHandler) {
|
||||
return fn;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
computePreferredLocale,
|
||||
computePreferredLocaleList,
|
||||
} from '../../i18n/utils.js';
|
||||
import type { MiddlewareHandler, Params, RewritePayload } from '../../types/public/common.js';
|
||||
import type { Params, RewritePayload } from '../../types/public/common.js';
|
||||
import type { APIContext, AstroSharedContextCsp } from '../../types/public/context.js';
|
||||
import { ASTRO_GENERATOR } from '../constants.js';
|
||||
import { AstroCookies } from '../cookies/index.js';
|
||||
@@ -13,10 +13,6 @@ import { getClientIpAddress } from '../routing/request.js';
|
||||
import { getOriginPathname } from '../routing/rewrite.js';
|
||||
import { sequence } from './sequence.js';
|
||||
|
||||
function defineMiddleware(fn: MiddlewareHandler) {
|
||||
return fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload for creating a context to be passed to Astro middleware
|
||||
*/
|
||||
@@ -206,4 +202,5 @@ function trySerializeLocals(value: unknown) {
|
||||
}
|
||||
|
||||
// NOTE: this export must export only the functions that will be exposed to user-land as officials APIs
|
||||
export { createContext, defineMiddleware, sequence, trySerializeLocals };
|
||||
export { createContext, sequence, trySerializeLocals };
|
||||
export { defineMiddleware } from './defineMiddleware.js';
|
||||
|
||||
@@ -5,7 +5,7 @@ import { AstroError } from '../errors/index.js';
|
||||
import { getParams, type Pipeline } from '../render/index.js';
|
||||
import { apiContextRoutesSymbol } from '../render-context.js';
|
||||
import { setOriginPathname } from '../routing/rewrite.js';
|
||||
import { defineMiddleware } from './index.js';
|
||||
import { defineMiddleware } from './defineMiddleware.js';
|
||||
|
||||
// From SvelteKit: https://github.com/sveltejs/kit/blob/master/packages/kit/src/exports/hooks/sequence.js
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { z } from 'zod';
|
||||
import type { ActionAccept, ActionClient, ActionReturnType } from '../../actions/runtime/server.js';
|
||||
import type { ActionClient, ActionReturnType } from '../../actions/runtime/server.js';
|
||||
import type { AstroCookies } from '../../core/cookies/cookies.js';
|
||||
import type { CspDirective, CspHash } from '../../core/csp/config.js';
|
||||
import type { AstroSession } from '../../core/session.js';
|
||||
@@ -252,23 +251,14 @@ interface AstroSharedContext<
|
||||
/**
|
||||
* Get action result on the server when using a form POST.
|
||||
*/
|
||||
getActionResult: <
|
||||
TAccept extends ActionAccept,
|
||||
TInputSchema extends z.ZodType,
|
||||
TAction extends ActionClient<unknown, TAccept, TInputSchema>,
|
||||
>(
|
||||
getActionResult: <TAction extends ActionClient<any, any, any>>(
|
||||
action: TAction,
|
||||
) => ActionReturnType<TAction> | undefined;
|
||||
/**
|
||||
* Call action handler from the server.
|
||||
*/
|
||||
callAction: <
|
||||
TAccept extends ActionAccept,
|
||||
TInputSchema extends z.ZodType,
|
||||
TOutput,
|
||||
TAction extends
|
||||
| ActionClient<TOutput, TAccept, TInputSchema>
|
||||
| ActionClient<TOutput, TAccept, TInputSchema>['orThrow'],
|
||||
TAction extends ActionClient<any, any, any> | ActionClient<any, any, any>['orThrow'],
|
||||
>(
|
||||
action: TAction,
|
||||
input: Parameters<TAction>[0],
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export { defineMiddleware, sequence } from '../core/middleware/index.js';
|
||||
export { defineMiddleware } from '../core/middleware/defineMiddleware.js';
|
||||
export { sequence } from '../core/middleware/sequence.js';
|
||||
|
||||
2
packages/astro/templates/content/types.d.ts
vendored
2
packages/astro/templates/content/types.d.ts
vendored
@@ -8,8 +8,6 @@ declare module 'astro:content' {
|
||||
'.md': Promise<RenderResult>;
|
||||
}
|
||||
|
||||
export const defineCollection: '@@DEFINE_COLLECTION@@';
|
||||
|
||||
export interface RenderedContent {
|
||||
html: string;
|
||||
metadata?: {
|
||||
|
||||
@@ -38,9 +38,9 @@ describe('Content Intellisense', () => {
|
||||
|
||||
it('generates a record JSON schema for the file loader', async () => {
|
||||
const schema = JSON.parse(await fixture.readFile('../.astro/collections/data-cl.schema.json'));
|
||||
assert.equal(schema.definitions['data-cl'].type, 'object');
|
||||
assert.equal(schema.definitions['data-cl'].additionalProperties.type, 'object');
|
||||
assert.deepEqual(schema.definitions['data-cl'].additionalProperties.properties, {
|
||||
assert.equal(schema.type, 'object');
|
||||
assert.equal(schema.additionalProperties.type, 'object');
|
||||
assert.deepEqual(schema.additionalProperties.properties, {
|
||||
name: { type: 'string' },
|
||||
color: { type: 'string' },
|
||||
});
|
||||
@@ -71,7 +71,7 @@ describe('Content Intellisense', () => {
|
||||
it('has entries for content collections', async () => {
|
||||
const collectionEntries = Object.entries(manifest.entries).filter((entry) =>
|
||||
entry[0].includes(
|
||||
'/astro/packages/astro/test/fixtures/content-intellisense/src/content/blog-cc/',
|
||||
'/packages/astro/test/fixtures/content-intellisense/src/content/blog-cc/',
|
||||
),
|
||||
);
|
||||
assert.equal(collectionEntries.length, 3, "Expected 3 entries for 'blog-cc' collection");
|
||||
@@ -83,8 +83,9 @@ describe('Content Intellisense', () => {
|
||||
});
|
||||
|
||||
it('has entries for content layer', async () => {
|
||||
|
||||
const collectionEntries = Object.entries(manifest.entries).filter((entry) =>
|
||||
entry[0].includes('/astro/packages/astro/test/fixtures/content-intellisense/src/blog-cl/'),
|
||||
entry[0].includes('/packages/astro/test/fixtures/content-intellisense/src/blog-cl/'),
|
||||
);
|
||||
|
||||
assert.equal(collectionEntries.length, 3, "Expected 3 entries for 'blog-cl' collection");
|
||||
|
||||
@@ -2,7 +2,4 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
export default defineConfig({
|
||||
experimental: {
|
||||
zod4: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -42,13 +42,13 @@
|
||||
"dependencies": {
|
||||
"@astrojs/internal-helpers": "workspace:*",
|
||||
"@astrojs/underscore-redirects": "workspace:*",
|
||||
"@cloudflare/vite-plugin": "^1.15.2",
|
||||
"@cloudflare/vite-plugin": "^1.17.0",
|
||||
"@cloudflare/workers-types": "^4.20251125.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"piccolore": "^0.1.3",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"vite": "^7.1.12",
|
||||
"wrangler": "4.50.0"
|
||||
"wrangler": "4.53.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"astro": "^6.0.0-alpha.0"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMiddleware } from 'astro/middleware'
|
||||
import { defineMiddleware } from 'astro:middleware'
|
||||
import { API_SECRET } from 'astro:env/server'
|
||||
|
||||
const secret = API_SECRET
|
||||
@@ -6,4 +6,4 @@ const secret = API_SECRET
|
||||
export const onRequest = defineMiddleware((_ctx, next) => {
|
||||
console.log({ secret })
|
||||
return next()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -44,11 +44,11 @@
|
||||
"astro": "workspace:*",
|
||||
"astro-scripts": "workspace:*",
|
||||
"cheerio": "1.1.2",
|
||||
"svelte": "^5.43.6"
|
||||
"svelte": "5.43.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"astro": "^6.0.0-alpha.0",
|
||||
"svelte": "^5.43.6",
|
||||
"svelte": "5.43.8",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
"dependencies": {
|
||||
"@astrojs/svelte": "workspace:*",
|
||||
"astro": "workspace:*",
|
||||
"svelte": "^5.43.14"
|
||||
"svelte": "5.43.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ describe(
|
||||
|
||||
assert.deepStrictEqual(hover?.contents, {
|
||||
kind: 'markdown',
|
||||
value: "The blog post's title.",
|
||||
value: "The blog post's title\\.",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
492
pnpm-lock.yaml
generated
492
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user