Compare commits

...

16 Commits

Author SHA1 Message Date
Matthew Phillips
0f181c3d64 remove debugging stuff 2025-12-05 17:00:30 -05:00
Matthew Phillips
afd6528184 add some debugging here 2025-12-05 16:37:27 -05:00
Matthew Phillips
fcdf90d04f remove experiemental flag 2025-12-05 16:16:46 -05:00
Matthew Phillips
af68956da1 changeset 2025-12-05 16:14:56 -05:00
Matthew Phillips
dcb3bdc97d pr comment stuff 2025-12-05 16:03:27 -05:00
Matthew Phillips
c51a23a2c2 fix examples types 2025-12-05 15:57:05 -05:00
Matthew Phillips
530e2e06c2 remove unused reference 2025-12-05 15:45:02 -05:00
Matthew Phillips
bfe74e8c7a fix defineMiddleware issue 2025-12-05 15:31:53 -05:00
Matthew Phillips
725859f324 fix cloudflare tests 2025-12-05 14:59:45 -05:00
Matthew Phillips
8c429bc61e fix more e2e 2025-12-05 12:39:23 -05:00
Matthew Phillips
713cb7e8a4 fix svelte tests 2025-12-05 12:24:00 -05:00
Matthew Phillips
3a61f4e93d Update Svelte to exactly 5.43.8 in package and fixtures 2025-12-05 12:23:06 -05:00
Florian Lefebvre
215afa07c5 fix: types 2025-12-05 17:04:35 +01:00
Matthew Phillips
2ddd549373 fix schema test 2025-12-05 09:06:08 -05:00
Matthew Phillips
4f005ebe1f fix intellisense tsts 2025-12-05 08:57:35 -05:00
Matthew Phillips
ef994e51a8 fix actions test 2025-12-05 08:48:49 -05:00
24 changed files with 435 additions and 208 deletions

View 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

View File

@@ -8,7 +8,4 @@ import { defineConfig } from 'astro/config';
export default defineConfig({
site: 'https://example.com',
integrations: [mdx(), sitemap()],
experimental: {
zod4: true
}
});

View File

@@ -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')
}),
});

View File

@@ -16,7 +16,7 @@ const releases = defineCollection({
alt: z.string(),
}),
// Transform string to Date object
date: z.date({ coerce: true }),
date: z.coerce.date(),
}),
});

View File

@@ -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'];

View File

@@ -22,7 +22,7 @@ function astroClientClickDirective() {
function astroClientPasswordDirective() {
return {
name: 'astro-client-click',
name: 'astro-client-password',
hooks: {
'astro:config:setup': (opts) => {
opts.addClientDirective({

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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: [],

View File

@@ -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

View File

@@ -0,0 +1,5 @@
import type { MiddlewareHandler } from '../../types/public/common.js';
export function defineMiddleware(fn: MiddlewareHandler) {
return fn;
}

View File

@@ -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';

View File

@@ -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
/**

View File

@@ -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],

View File

@@ -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';

View File

@@ -8,8 +8,6 @@ declare module 'astro:content' {
'.md': Promise<RenderResult>;
}
export const defineCollection: '@@DEFINE_COLLECTION@@';
export interface RenderedContent {
html: string;
metadata?: {

View File

@@ -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");

View File

@@ -2,7 +2,4 @@
import { defineConfig } from 'astro/config';
export default defineConfig({
experimental: {
zod4: true,
},
});

View File

@@ -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"

View File

@@ -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()
})
})

View File

@@ -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": {

View File

@@ -11,6 +11,6 @@
"dependencies": {
"@astrojs/svelte": "workspace:*",
"astro": "workspace:*",
"svelte": "^5.43.14"
"svelte": "5.43.8"
}
}

View File

@@ -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

File diff suppressed because it is too large Load Diff