diff --git a/.changeset/blue-colts-film.md b/.changeset/blue-colts-film.md new file mode 100644 index 000000000000..9df2e9e56031 --- /dev/null +++ b/.changeset/blue-colts-film.md @@ -0,0 +1,23 @@ +--- +'@astrojs/db': minor +--- + +Removes the `AstroDbIntegration` type + +Astro integration hooks can now be extended and as such `@astrojs/db` no longer needs to declare it's own integration type. Using `AstroIntegration` will have the same type. + +If you were using the `AstroDbIntegration` type, apply this change to your integration code: + +```diff +- import { defineDbIntegration, type AstroDbIntegration } from '@astrojs/db/utils'; ++ import { defineDbIntegration } from '@astrojs/db/utils'; +import type { AstroIntegration } from 'astro'; + +- export default (): AstroDbIntegration => { ++ export default (): AstroIntegration => { + return defineDbIntegration({ + name: 'your-integration', + hooks: {}, + }); +} +``` diff --git a/.changeset/chilled-impalas-dance.md b/.changeset/chilled-impalas-dance.md new file mode 100644 index 000000000000..51d6708011fd --- /dev/null +++ b/.changeset/chilled-impalas-dance.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes an issue where the development server was emitting a 404 status code when the user uses a rewrite that emits a 200 status code. diff --git a/.changeset/cold-crabs-arrive.md b/.changeset/cold-crabs-arrive.md new file mode 100644 index 000000000000..6bde11b6a818 --- /dev/null +++ b/.changeset/cold-crabs-arrive.md @@ -0,0 +1,32 @@ +--- +'@astrojs/markdown-remark': minor +'astro': minor +--- + +Adds support for [Shiki's `defaultColor` option](https://shiki.style/guide/dual-themes#without-default-color). + +This option allows you to override the values of a theme's inline style, adding only CSS variables to give you more flexibility in applying multiple color themes. + +Configure `defaultColor: false` in your Shiki config to apply throughout your site, or pass to Astro's built-in `` component to style an individual code block. + +```js title="astro.config.mjs" +import { defineConfig } from 'astro/config'; +export default defineConfig({ + markdown: { + shikiConfig: { + themes: { + light: 'github-light', + dark: 'github-dark', + }, + defaultColor: false, + }, + }, +}); +``` + +```astro +--- +import { Code } from 'astro:components'; +--- + +``` diff --git a/.changeset/curvy-otters-jog.md b/.changeset/curvy-otters-jog.md new file mode 100644 index 000000000000..8bfa0a17ced7 --- /dev/null +++ b/.changeset/curvy-otters-jog.md @@ -0,0 +1,58 @@ +--- +'astro': minor +--- + +Refactors the type for integration hooks so that integration authors writing custom integration hooks can now allow runtime interactions between their integration and other integrations. + +This internal change should not break existing code for integration authors. + +To declare your own hooks for your integration, extend the `Astro.IntegrationHooks` interface: + +```ts +// your-integration/types.ts +declare global { + namespace Astro { + interface IntegrationHooks { + 'myLib:eventHappened': (your: string, parameters: number) => Promise; + } + } +} +``` + +Call your hooks on all other integrations installed in a project at the appropriate time. For example, you can call your hook on initialization before either the Vite or Astro config have resolved: + +```ts +// your-integration/index.ts +import './types.ts'; + +export default (): AstroIntegration => { + return { + name: 'your-integration', + hooks: { + 'astro:config:setup': async ({ config }) => { + for (const integration of config.integrations) { + await integration.hooks['myLib:eventHappened'].?('your values', 123); + } + }, + } + } +} +``` + +Other integrations can also now declare your hooks: + +```ts +// other-integration/index.ts +import 'your-integration/types.ts'; + +export default (): AstroIntegration => { + return { + name: 'other-integration', + hooks: { + 'myLib:eventHappened': async (your, values) => { + // ... + }, + } + } +} +``` diff --git a/.changeset/large-geese-play.md b/.changeset/large-geese-play.md new file mode 100644 index 000000000000..2cfd9788df9a --- /dev/null +++ b/.changeset/large-geese-play.md @@ -0,0 +1,20 @@ +--- +"astro": minor +--- + +Adds a new `inferRemoteSize()` function that can be used to infer the dimensions of a remote image. + +Previously, the ability to infer these values was only available by adding the [`inferSize`] attribute to the `` and `` components or `getImage()`. Now, you can also access this data outside of these components. + +This is useful for when you need to know the dimensions of an image for styling purposes or to calculate different densities for responsive images. + +```astro +--- +import { inferRemoteSize, Image } from 'astro:assets'; + +const imageUrl = 'https://...'; +const { width, height } = await inferRemoteSize(imageUrl); +--- + + +``` diff --git a/.changeset/loud-socks-doubt.md b/.changeset/loud-socks-doubt.md deleted file mode 100644 index 4af3b4607703..000000000000 --- a/.changeset/loud-socks-doubt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Fixes a case where Astro's config `experimental.env.schema` keys did not allow numbers. Numbers are still not allowed as the first character to be able to generate valid JavaScript identifiers diff --git a/.changeset/modern-buses-check.md b/.changeset/modern-buses-check.md new file mode 100644 index 000000000000..3cf7482c1bf1 --- /dev/null +++ b/.changeset/modern-buses-check.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Refactors how `sync` works and when it's called. Fixes an issue with `astro:env` types in dev not being generated diff --git a/.changeset/nasty-poems-juggle.md b/.changeset/nasty-poems-juggle.md deleted file mode 100644 index 74e1b176d036..000000000000 --- a/.changeset/nasty-poems-juggle.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'astro': patch ---- - -Expands the `isInputError()` utility from `astro:actions` to accept errors of any type. This should now allow type narrowing from a try / catch block. - -```ts -// example.ts -import { actions, isInputError } from 'astro:actions'; - -try { - await actions.like(new FormData()); -} catch (error) { - if (isInputError(error)) { - console.log(error.fields); - } -} -``` diff --git a/.changeset/plenty-socks-talk.md b/.changeset/plenty-socks-talk.md deleted file mode 100644 index 2749228dd952..000000000000 --- a/.changeset/plenty-socks-talk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Exposes utility types from `astro:actions` for the `defineAction` handler (`ActionHandler`) and the `ActionError` code (`ActionErrorCode`). diff --git a/.changeset/seven-donuts-happen.md b/.changeset/seven-donuts-happen.md new file mode 100644 index 000000000000..cf6b85b5b958 --- /dev/null +++ b/.changeset/seven-donuts-happen.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Supports importing Astro components with Vite queries, like `?url`, `?raw`, and `?direct` diff --git a/.changeset/slow-roses-call.md b/.changeset/slow-roses-call.md deleted file mode 100644 index 9217f96fe965..000000000000 --- a/.changeset/slow-roses-call.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -'astro': patch ---- - -Adds a new property `experimental.env.validateSecrets` to allow validating private variables on the server. - -By default, this is set to `false` and only public variables are checked on start. If enabled, secrets will also be checked on start (dev/build modes). This is useful for example in some CIs to make sure all your secrets are correctly set before deploying. - -```js -// astro.config.mjs -import { defineConfig, envField } from "astro/config" - -export default defineConfig({ - experimental: { - env: { - schema: { - // ... - }, - validateSecrets: true - } - } -}) -``` diff --git a/.changeset/small-vans-own.md b/.changeset/small-vans-own.md deleted file mode 100644 index 06352e256af0..000000000000 --- a/.changeset/small-vans-own.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -'astro': patch ---- - -Expose new `ActionReturnType` utility from `astro:actions`. This infers the return type of an action by passing `typeof actions.name` as a type argument. This example defines a `like` action that returns `likes` as an object: - -```ts -// actions/index.ts -import { defineAction } from 'astro:actions'; - -export const server = { - like: defineAction({ - handler: () => { - /* ... */ - return { likes: 42 } - } - }) -} -``` - -In your client code, you can infer this handler return value with `ActionReturnType`: - -```ts -// client.ts -import { actions, ActionReturnType } from 'astro:actions'; - -type LikesResult = ActionReturnType; -// -> { likes: number } -``` diff --git a/.changeset/swift-cows-walk.md b/.changeset/swift-cows-walk.md deleted file mode 100644 index 212d0417e699..000000000000 --- a/.changeset/swift-cows-walk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"astro": patch ---- - -Fixes `astro:actions` autocompletion for the `defineAction` `accept` property diff --git a/.changeset/twenty-maps-glow.md b/.changeset/twenty-maps-glow.md new file mode 100644 index 000000000000..9588a45bc2e4 --- /dev/null +++ b/.changeset/twenty-maps-glow.md @@ -0,0 +1,5 @@ +--- +'astro': minor +--- + +Adds two new values to the [pagination `page` prop](https://docs.astro.build/en/reference/api-reference/#the-pagination-page-prop): `page.first` and `page.last` for accessing the URLs of the first and last pages. diff --git a/examples/basics/package.json b/examples/basics/package.json index ed7f452c2051..d922caebfbb8 100644 --- a/examples/basics/package.json +++ b/examples/basics/package.json @@ -11,6 +11,6 @@ "astro": "astro" }, "dependencies": { - "astro": "^4.11.5" + "astro": "^4.11.6" } } diff --git a/examples/blog/package.json b/examples/blog/package.json index 29d33e4f9c4a..6954ca7e03eb 100644 --- a/examples/blog/package.json +++ b/examples/blog/package.json @@ -14,6 +14,6 @@ "@astrojs/mdx": "^3.1.2", "@astrojs/rss": "^4.0.7", "@astrojs/sitemap": "^3.1.6", - "astro": "^4.11.5" + "astro": "^4.11.6" } } diff --git a/examples/component/package.json b/examples/component/package.json index dadb92b7b359..1d40ec2f1247 100644 --- a/examples/component/package.json +++ b/examples/component/package.json @@ -15,7 +15,7 @@ ], "scripts": {}, "devDependencies": { - "astro": "^4.11.5" + "astro": "^4.11.6" }, "peerDependencies": { "astro": "^4.0.0" diff --git a/examples/container-with-vitest/package.json b/examples/container-with-vitest/package.json index 21ae4bea31dc..de2bec8281a8 100644 --- a/examples/container-with-vitest/package.json +++ b/examples/container-with-vitest/package.json @@ -16,7 +16,7 @@ "astro": "^4.11.5", "react": "^18.3.1", "react-dom": "^18.3.1", - "vitest": "^1.6.0" + "vitest": "^2.0.3" }, "devDependencies": { "@types/react": "^18.3.3", diff --git a/examples/framework-alpine/package.json b/examples/framework-alpine/package.json index 60d3bd55984d..22d788d8617a 100644 --- a/examples/framework-alpine/package.json +++ b/examples/framework-alpine/package.json @@ -14,6 +14,6 @@ "@astrojs/alpinejs": "^0.4.0", "@types/alpinejs": "^3.13.10", "alpinejs": "^3.14.1", - "astro": "^4.11.5" + "astro": "^4.11.6" } } diff --git a/examples/framework-lit/package.json b/examples/framework-lit/package.json index 701d46f235df..9d4baa253e74 100644 --- a/examples/framework-lit/package.json +++ b/examples/framework-lit/package.json @@ -13,7 +13,7 @@ "dependencies": { "@astrojs/lit": "^4.3.0", "@webcomponents/template-shadowroot": "^0.2.1", - "astro": "^4.11.5", + "astro": "^4.11.6", "lit": "^3.1.4" } } diff --git a/examples/framework-multiple/package.json b/examples/framework-multiple/package.json index ef70525d9c6a..941b71491297 100644 --- a/examples/framework-multiple/package.json +++ b/examples/framework-multiple/package.json @@ -11,14 +11,14 @@ "astro": "astro" }, "dependencies": { - "@astrojs/preact": "^3.5.0", + "@astrojs/preact": "^3.5.1", "@astrojs/react": "^3.6.0", "@astrojs/solid-js": "^4.4.0", "@astrojs/svelte": "^5.6.0", "@astrojs/vue": "^4.5.0", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "astro": "^4.11.5", + "astro": "^4.11.6", "preact": "^10.22.1", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/examples/framework-preact/package.json b/examples/framework-preact/package.json index b1b672385bd5..77a95ac08f07 100644 --- a/examples/framework-preact/package.json +++ b/examples/framework-preact/package.json @@ -11,9 +11,9 @@ "astro": "astro" }, "dependencies": { - "@astrojs/preact": "^3.5.0", - "@preact/signals": "^1.2.3", - "astro": "^4.11.5", + "@astrojs/preact": "^3.5.1", + "@preact/signals": "^1.3.0", + "astro": "^4.11.6", "preact": "^10.22.1" } } diff --git a/examples/framework-react/package.json b/examples/framework-react/package.json index 20484e8dd198..b9d21852400a 100644 --- a/examples/framework-react/package.json +++ b/examples/framework-react/package.json @@ -14,7 +14,7 @@ "@astrojs/react": "^3.6.0", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "astro": "^4.11.5", + "astro": "^4.11.6", "react": "^18.3.1", "react-dom": "^18.3.1" } diff --git a/examples/framework-solid/package.json b/examples/framework-solid/package.json index 945aa9987359..42a886518188 100644 --- a/examples/framework-solid/package.json +++ b/examples/framework-solid/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@astrojs/solid-js": "^4.4.0", - "astro": "^4.11.5", + "astro": "^4.11.6", "solid-js": "^1.8.18" } } diff --git a/examples/framework-svelte/package.json b/examples/framework-svelte/package.json index 60658a81a378..adb56ccb1e99 100644 --- a/examples/framework-svelte/package.json +++ b/examples/framework-svelte/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@astrojs/svelte": "^5.6.0", - "astro": "^4.11.5", + "astro": "^4.11.6", "svelte": "^4.2.18" } } diff --git a/examples/framework-vue/package.json b/examples/framework-vue/package.json index 2225addab524..8632013551d7 100644 --- a/examples/framework-vue/package.json +++ b/examples/framework-vue/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@astrojs/vue": "^4.5.0", - "astro": "^4.11.5", + "astro": "^4.11.6", "vue": "^3.4.31" } } diff --git a/examples/hackernews/package.json b/examples/hackernews/package.json index fa6298bc5356..f81f208c38fc 100644 --- a/examples/hackernews/package.json +++ b/examples/hackernews/package.json @@ -12,6 +12,6 @@ }, "dependencies": { "@astrojs/node": "^8.3.2", - "astro": "^4.11.5" + "astro": "^4.11.6" } } diff --git a/examples/integration/package.json b/examples/integration/package.json index cf3d457f731f..3c80d79c2c3e 100644 --- a/examples/integration/package.json +++ b/examples/integration/package.json @@ -15,7 +15,7 @@ ], "scripts": {}, "devDependencies": { - "astro": "^4.11.5" + "astro": "^4.11.6" }, "peerDependencies": { "astro": "^4.0.0" diff --git a/examples/middleware/package.json b/examples/middleware/package.json index 4daf9ed748f5..51b813736a5a 100644 --- a/examples/middleware/package.json +++ b/examples/middleware/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@astrojs/node": "^8.3.2", - "astro": "^4.11.5", + "astro": "^4.11.6", "html-minifier": "^4.0.0" }, "devDependencies": { diff --git a/examples/minimal/package.json b/examples/minimal/package.json index c4290981616d..120306263178 100644 --- a/examples/minimal/package.json +++ b/examples/minimal/package.json @@ -11,6 +11,6 @@ "astro": "astro" }, "dependencies": { - "astro": "^4.11.5" + "astro": "^4.11.6" } } diff --git a/examples/non-html-pages/package.json b/examples/non-html-pages/package.json index 988bec080883..7485ab41914f 100644 --- a/examples/non-html-pages/package.json +++ b/examples/non-html-pages/package.json @@ -11,6 +11,6 @@ "astro": "astro" }, "dependencies": { - "astro": "^4.11.5" + "astro": "^4.11.6" } } diff --git a/examples/portfolio/package.json b/examples/portfolio/package.json index 93454dcae621..9f446ac7473e 100644 --- a/examples/portfolio/package.json +++ b/examples/portfolio/package.json @@ -11,6 +11,6 @@ "astro": "astro" }, "dependencies": { - "astro": "^4.11.5" + "astro": "^4.11.6" } } diff --git a/examples/ssr/package.json b/examples/ssr/package.json index c59ebd382edd..18bda715305e 100644 --- a/examples/ssr/package.json +++ b/examples/ssr/package.json @@ -14,7 +14,7 @@ "dependencies": { "@astrojs/node": "^8.3.2", "@astrojs/svelte": "^5.6.0", - "astro": "^4.11.5", + "astro": "^4.11.6", "svelte": "^4.2.18" } } diff --git a/examples/starlog/package.json b/examples/starlog/package.json index ec3a948803ce..dd06787c1a78 100644 --- a/examples/starlog/package.json +++ b/examples/starlog/package.json @@ -10,8 +10,8 @@ "astro": "astro" }, "dependencies": { - "astro": "^4.11.5", - "sass": "^1.77.6", + "astro": "^4.11.6", + "sass": "^1.77.8", "sharp": "^0.33.3" } } diff --git a/examples/toolbar-app/package.json b/examples/toolbar-app/package.json index bb93e381a785..c34863aae384 100644 --- a/examples/toolbar-app/package.json +++ b/examples/toolbar-app/package.json @@ -15,6 +15,6 @@ "./app": "./dist/app.js" }, "devDependencies": { - "astro": "^4.11.5" + "astro": "^4.11.6" } } diff --git a/examples/view-transitions/package.json b/examples/view-transitions/package.json index c0847ef49ae3..1b1cb9936f16 100644 --- a/examples/view-transitions/package.json +++ b/examples/view-transitions/package.json @@ -12,6 +12,6 @@ "devDependencies": { "@astrojs/tailwind": "^5.1.0", "@astrojs/node": "^8.3.2", - "astro": "^4.11.5" + "astro": "^4.11.6" } } diff --git a/examples/with-markdoc/package.json b/examples/with-markdoc/package.json index cf908daad7dd..f74158cb5c0c 100644 --- a/examples/with-markdoc/package.json +++ b/examples/with-markdoc/package.json @@ -11,7 +11,7 @@ "astro": "astro" }, "dependencies": { - "@astrojs/markdoc": "^0.11.1", - "astro": "^4.11.5" + "@astrojs/markdoc": "^0.11.2", + "astro": "^4.11.6" } } diff --git a/examples/with-markdown-plugins/package.json b/examples/with-markdown-plugins/package.json index dc53eb70d0ef..cacf0fdb559b 100644 --- a/examples/with-markdown-plugins/package.json +++ b/examples/with-markdown-plugins/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@astrojs/markdown-remark": "^5.1.1", - "astro": "^4.11.5", + "astro": "^4.11.6", "hast-util-select": "^6.0.2", "rehype-autolink-headings": "^7.1.0", "rehype-slug": "^6.0.0", diff --git a/examples/with-markdown-shiki/package.json b/examples/with-markdown-shiki/package.json index 2c681672dec8..89da7396b270 100644 --- a/examples/with-markdown-shiki/package.json +++ b/examples/with-markdown-shiki/package.json @@ -11,6 +11,6 @@ "astro": "astro" }, "dependencies": { - "astro": "^4.11.5" + "astro": "^4.11.6" } } diff --git a/examples/with-mdx/package.json b/examples/with-mdx/package.json index d3a50340be37..b517d7d1d51d 100644 --- a/examples/with-mdx/package.json +++ b/examples/with-mdx/package.json @@ -12,8 +12,8 @@ }, "dependencies": { "@astrojs/mdx": "^3.1.2", - "@astrojs/preact": "^3.5.0", - "astro": "^4.11.5", + "@astrojs/preact": "^3.5.1", + "astro": "^4.11.6", "preact": "^10.22.1" } } diff --git a/examples/with-nanostores/package.json b/examples/with-nanostores/package.json index c5d22c730680..a1903af8b5a2 100644 --- a/examples/with-nanostores/package.json +++ b/examples/with-nanostores/package.json @@ -11,9 +11,9 @@ "astro": "astro" }, "dependencies": { - "@astrojs/preact": "^3.5.0", + "@astrojs/preact": "^3.5.1", "@nanostores/preact": "^0.5.1", - "astro": "^4.11.5", + "astro": "^4.11.6", "nanostores": "^0.10.3", "preact": "^10.22.1" } diff --git a/examples/with-tailwindcss/package.json b/examples/with-tailwindcss/package.json index 8d35a24a1737..9a8a0cea622a 100644 --- a/examples/with-tailwindcss/package.json +++ b/examples/with-tailwindcss/package.json @@ -14,10 +14,10 @@ "@astrojs/mdx": "^3.1.2", "@astrojs/tailwind": "^5.1.0", "@types/canvas-confetti": "^1.6.4", - "astro": "^4.11.5", + "astro": "^4.11.6", "autoprefixer": "^10.4.19", "canvas-confetti": "^1.9.3", "postcss": "^8.4.39", - "tailwindcss": "^3.4.4" + "tailwindcss": "^3.4.5" } } diff --git a/examples/with-vitest/package.json b/examples/with-vitest/package.json index 91bb845784b8..60f1a0a9ee83 100644 --- a/examples/with-vitest/package.json +++ b/examples/with-vitest/package.json @@ -12,7 +12,7 @@ "test": "vitest" }, "dependencies": { - "astro": "^4.11.5", - "vitest": "^1.6.0" + "astro": "^4.11.6", + "vitest": "^2.0.3" } } diff --git a/package.json b/package.json index e99e740da097..d1948fb2bb06 100644 --- a/package.json +++ b/package.json @@ -52,25 +52,25 @@ "astro-benchmark": "workspace:*" }, "devDependencies": { - "@astrojs/check": "^0.7.0", + "@astrojs/check": "^0.8.1", "@biomejs/biome": "1.8.1", "@changesets/changelog-github": "^0.5.0", - "@changesets/cli": "^2.27.6", + "@changesets/cli": "^2.27.7", "@eslint/eslintrc": "^3.1.0", "@types/node": "^18.17.8", "esbuild": "^0.21.5", - "eslint": "^9.6.0", + "eslint": "^9.7.0", "eslint-plugin-no-only-tests": "^3.1.0", "eslint-plugin-regexp": "^2.6.0", "globby": "^14.0.2", "only-allow": "^1.2.1", "organize-imports-cli": "^0.10.0", - "prettier": "^3.3.2", + "prettier": "^3.3.3", "prettier-plugin-astro": "^0.14.0", "tiny-glob": "^0.2.9", "turbo": "^1.13.4", - "typescript": "~5.5.2", - "typescript-eslint": "^7.14.1" + "typescript": "~5.5.3", + "typescript-eslint": "^7.16.1" }, "pnpm": { "packageExtensions": { diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md index 935f0bcf39af..b7c6c6df77b9 100644 --- a/packages/astro/CHANGELOG.md +++ b/packages/astro/CHANGELOG.md @@ -1,5 +1,88 @@ # astro +## 4.11.6 + +### Patch Changes + +- [#11459](https://github.com/withastro/astro/pull/11459) [`bc2e74d`](https://github.com/withastro/astro/commit/bc2e74de384776caa252fd47dbeda895c0488c11) Thanks [@mingjunlu](https://github.com/mingjunlu)! - Fixes false positive audit warnings on elements with the role "tabpanel". + +- [#11472](https://github.com/withastro/astro/pull/11472) [`cb4e6d0`](https://github.com/withastro/astro/commit/cb4e6d09deb7507058115a3fd2a567019a501e4d) Thanks [@delucis](https://github.com/delucis)! - Avoids targeting all files in the `src/` directory for eager optimization by Vite. After this change, only JSX, Vue, Svelte, and Astro components get scanned for early optimization. + +- [#11387](https://github.com/withastro/astro/pull/11387) [`b498461`](https://github.com/withastro/astro/commit/b498461e277bffb0abe21b59a94b1e56a8c69d47) Thanks [@bluwy](https://github.com/bluwy)! - Fixes prerendering not removing unused dynamic imported chunks + +- [#11437](https://github.com/withastro/astro/pull/11437) [`6ccb30e`](https://github.com/withastro/astro/commit/6ccb30e610eed34c2cc2c275485a8ac45c9b6b9e) Thanks [@NuroDev](https://github.com/NuroDev)! - Fixes a case where Astro's config `experimental.env.schema` keys did not allow numbers. Numbers are still not allowed as the first character to be able to generate valid JavaScript identifiers + +- [#11439](https://github.com/withastro/astro/pull/11439) [`08baf56`](https://github.com/withastro/astro/commit/08baf56f328ce4b6814a7f90089c0b3398d8bbfe) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Expands the `isInputError()` utility from `astro:actions` to accept errors of any type. This should now allow type narrowing from a try / catch block. + + ```ts + // example.ts + import { actions, isInputError } from 'astro:actions'; + + try { + await actions.like(new FormData()); + } catch (error) { + if (isInputError(error)) { + console.log(error.fields); + } + } + ``` + +- [#11452](https://github.com/withastro/astro/pull/11452) [`0e66849`](https://github.com/withastro/astro/commit/0e6684983b9b24660a8fef83fe401ec1d567378a) Thanks [@FugiTech](https://github.com/FugiTech)! - Fixes an issue where using .nullish() in a formdata Astro action would always parse as a string + +- [#11438](https://github.com/withastro/astro/pull/11438) [`619f07d`](https://github.com/withastro/astro/commit/619f07db701ebab2d2f2598dd2dcf93ba1e5719c) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Exposes utility types from `astro:actions` for the `defineAction` handler (`ActionHandler`) and the `ActionError` code (`ActionErrorCode`). + +- [#11456](https://github.com/withastro/astro/pull/11456) [`17e048d`](https://github.com/withastro/astro/commit/17e048de0e79d76b933d128676be2388954b419e) Thanks [@RickyC0626](https://github.com/RickyC0626)! - Fixes `astro dev --open` unexpected behavior that spawns a new tab every time a config file is saved + +- [#11337](https://github.com/withastro/astro/pull/11337) [`0a4b31f`](https://github.com/withastro/astro/commit/0a4b31ffeb41ad1dfb3141384e22787763fcae3d) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Adds a new property `experimental.env.validateSecrets` to allow validating private variables on the server. + + By default, this is set to `false` and only public variables are checked on start. If enabled, secrets will also be checked on start (dev/build modes). This is useful for example in some CIs to make sure all your secrets are correctly set before deploying. + + ```js + // astro.config.mjs + import { defineConfig, envField } from 'astro/config'; + + export default defineConfig({ + experimental: { + env: { + schema: { + // ... + }, + validateSecrets: true, + }, + }, + }); + ``` + +- [#11443](https://github.com/withastro/astro/pull/11443) [`ea4bc04`](https://github.com/withastro/astro/commit/ea4bc04e9489c456e2b4b5dbd67d5e4cf3f89f97) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Expose new `ActionReturnType` utility from `astro:actions`. This infers the return type of an action by passing `typeof actions.name` as a type argument. This example defines a `like` action that returns `likes` as an object: + + ```ts + // actions/index.ts + import { defineAction } from 'astro:actions'; + + export const server = { + like: defineAction({ + handler: () => { + /* ... */ + return { likes: 42 }; + }, + }), + }; + ``` + + In your client code, you can infer this handler return value with `ActionReturnType`: + + ```ts + // client.ts + import { actions, ActionReturnType } from 'astro:actions'; + + type LikesResult = ActionReturnType; + // -> { likes: number } + ``` + +- [#11436](https://github.com/withastro/astro/pull/11436) [`7dca68f`](https://github.com/withastro/astro/commit/7dca68ff2e0f089a3fd090650ee05b1942792fed) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fixes `astro:actions` autocompletion for the `defineAction` `accept` property + +- [#11455](https://github.com/withastro/astro/pull/11455) [`645e128`](https://github.com/withastro/astro/commit/645e128537f1f20da6703afc115d06371d7da5dd) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Improves `astro:env` invalid variables errors + ## 4.11.5 ### Patch Changes diff --git a/packages/astro/client.d.ts b/packages/astro/client.d.ts index 0870d3dcc566..d74e5fa488cb 100644 --- a/packages/astro/client.d.ts +++ b/packages/astro/client.d.ts @@ -54,6 +54,7 @@ declare module 'astro:assets' { ) => Promise; imageConfig: import('./dist/@types/astro.js').AstroConfig['image']; getConfiguredImageService: typeof import('./dist/assets/index.js').getConfiguredImageService; + inferRemoteSize: typeof import('./dist/assets/utils/index.js').inferRemoteSize; Image: typeof import('./components/Image.astro').default; Picture: typeof import('./components/Picture.astro').default; }; diff --git a/packages/astro/e2e/fixtures/actions-blog/package.json b/packages/astro/e2e/fixtures/actions-blog/package.json index 8b2c3824d2fe..d536e64fb93e 100644 --- a/packages/astro/e2e/fixtures/actions-blog/package.json +++ b/packages/astro/e2e/fixtures/actions-blog/package.json @@ -10,7 +10,7 @@ "astro": "astro" }, "dependencies": { - "@astrojs/check": "^0.7.0", + "@astrojs/check": "^0.8.1", "@astrojs/db": "workspace:*", "@astrojs/node": "workspace:*", "@astrojs/react": "workspace:*", @@ -19,6 +19,6 @@ "astro": "workspace:*", "react": "^18.3.1", "react-dom": "^18.3.1", - "typescript": "^5.5.2" + "typescript": "^5.5.3" } } diff --git a/packages/astro/e2e/fixtures/actions-react-19/package.json b/packages/astro/e2e/fixtures/actions-react-19/package.json index d7294977320d..5927a6782675 100644 --- a/packages/astro/e2e/fixtures/actions-react-19/package.json +++ b/packages/astro/e2e/fixtures/actions-react-19/package.json @@ -10,7 +10,7 @@ "astro": "astro" }, "dependencies": { - "@astrojs/check": "^0.7.0", + "@astrojs/check": "^0.8.1", "@astrojs/db": "workspace:*", "@astrojs/node": "workspace:*", "@astrojs/react": "workspace:*", @@ -19,7 +19,7 @@ "astro": "workspace:*", "react": "19.0.0-rc-fb9a90fa48-20240614", "react-dom": "19.0.0-rc-fb9a90fa48-20240614", - "typescript": "^5.5.2" + "typescript": "^5.5.3" }, "overrides": { "@types/react": "npm:types-react", diff --git a/packages/astro/e2e/fixtures/error-sass/package.json b/packages/astro/e2e/fixtures/error-sass/package.json index 57d060439ed2..9658b550a1f6 100644 --- a/packages/astro/e2e/fixtures/error-sass/package.json +++ b/packages/astro/e2e/fixtures/error-sass/package.json @@ -4,6 +4,6 @@ "private": true, "dependencies": { "astro": "workspace:*", - "sass": "^1.77.6" + "sass": "^1.77.8" } } diff --git a/packages/astro/e2e/fixtures/errors/package.json b/packages/astro/e2e/fixtures/errors/package.json index cdafd19203e4..43cc930720f2 100644 --- a/packages/astro/e2e/fixtures/errors/package.json +++ b/packages/astro/e2e/fixtures/errors/package.json @@ -12,7 +12,7 @@ "preact": "^10.22.1", "react": "^18.3.1", "react-dom": "^18.3.1", - "sass": "^1.77.6", + "sass": "^1.77.8", "solid-js": "^1.8.18", "svelte": "^4.2.18", "vue": "^3.4.31" diff --git a/packages/astro/e2e/fixtures/hmr/package.json b/packages/astro/e2e/fixtures/hmr/package.json index 8f65a830d5e8..87d59ab7f6e2 100644 --- a/packages/astro/e2e/fixtures/hmr/package.json +++ b/packages/astro/e2e/fixtures/hmr/package.json @@ -4,6 +4,6 @@ "private": true, "devDependencies": { "astro": "workspace:*", - "sass": "^1.77.6" + "sass": "^1.77.8" } } diff --git a/packages/astro/e2e/fixtures/tailwindcss/package.json b/packages/astro/e2e/fixtures/tailwindcss/package.json index 94acb613eedb..09ccca0040d5 100644 --- a/packages/astro/e2e/fixtures/tailwindcss/package.json +++ b/packages/astro/e2e/fixtures/tailwindcss/package.json @@ -7,6 +7,6 @@ "astro": "workspace:*", "autoprefixer": "^10.4.19", "postcss": "^8.4.39", - "tailwindcss": "^3.4.4" + "tailwindcss": "^3.4.5" } } diff --git a/packages/astro/package.json b/packages/astro/package.json index 78d7d017244e..68051ccc7f22 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,6 +1,6 @@ { "name": "astro", - "version": "4.11.5", + "version": "4.11.6", "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.", "type": "module", "author": "withastro", @@ -63,6 +63,7 @@ "./actions/runtime/*": "./dist/actions/runtime/*", "./assets": "./dist/assets/index.js", "./assets/utils": "./dist/assets/utils/index.js", + "./assets/utils/inferRemoteSize.js": "./dist/assets/utils/remoteProbe.js", "./assets/endpoint/*": "./dist/assets/endpoint/*.js", "./assets/services/sharp": "./dist/assets/services/sharp.js", "./assets/services/squoosh": "./dist/assets/services/squoosh.js", @@ -125,23 +126,23 @@ "test:node": "astro-scripts test \"test/**/*.test.js\"" }, "dependencies": { - "@astrojs/compiler": "^2.8.1", + "@astrojs/compiler": "^2.8.2", "@astrojs/internal-helpers": "workspace:*", "@astrojs/markdown-remark": "workspace:*", "@astrojs/telemetry": "workspace:*", - "@babel/core": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/parser": "^7.24.7", + "@babel/core": "^7.24.9", + "@babel/generator": "^7.24.10", + "@babel/parser": "^7.24.8", "@babel/plugin-transform-react-jsx": "^7.24.7", "@babel/traverse": "^7.24.7", "@babel/types": "^7.24.7", "@rollup/pluginutils": "^5.1.0", "@types/babel__core": "^7.20.5", "@types/cookie": "^0.6.0", - "acorn": "^8.12.0", + "acorn": "^8.12.1", "aria-query": "^5.3.0", - "axobject-query": "^4.0.0", - "boxen": "^7.1.1", + "axobject-query": "^4.1.0", + "boxen": "^8.0.0", "chokidar": "^3.6.0", "ci-info": "^4.0.0", "clsx": "^2.1.1", @@ -170,22 +171,22 @@ "micromatch": "^4.0.7", "mrmime": "^2.0.0", "ora": "^8.0.1", - "p-limit": "^5.0.0", + "p-limit": "^6.1.0", "p-queue": "^8.0.1", "path-to-regexp": "^6.2.2", - "preferred-pm": "^3.1.3", + "preferred-pm": "^4.0.0", "prompts": "^2.4.2", "rehype": "^13.0.1", "semver": "^7.6.2", - "shiki": "^1.10.0", + "shiki": "^1.10.3", "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "tsconfck": "^3.1.1", "unist-util-visit": "^5.0.0", - "vfile": "^6.0.1", - "vite": "^5.3.2", + "vfile": "^6.0.2", + "vite": "^5.3.4", "vitefu": "^0.2.5", - "which-pm": "^2.2.0", + "which-pm": "^3.0.0", "yargs-parser": "^21.1.1", "zod": "^3.23.8", "zod-to-json-schema": "^3.23.1", @@ -195,8 +196,8 @@ "sharp": "^0.33.3" }, "devDependencies": { - "@astrojs/check": "^0.7.0", - "@playwright/test": "^1.45.0", + "@astrojs/check": "^0.8.1", + "@playwright/test": "^1.45.2", "@types/aria-query": "^5.0.4", "@types/babel__generator": "^7.6.8", "@types/babel__traverse": "^7.20.6", @@ -231,8 +232,8 @@ "rehype-slug": "^6.0.0", "rehype-toc": "^3.0.2", "remark-code-titles": "^0.1.2", - "rollup": "^4.18.0", - "sass": "^1.77.6", + "rollup": "^4.18.1", + "sass": "^1.77.8", "srcset-parse": "^1.1.0", "undici": "^6.19.2", "unified": "^11.0.5" @@ -245,4 +246,4 @@ "publishConfig": { "provenance": true } -} \ No newline at end of file +} diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index e624b69c8d37..e4487c6852d1 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -2647,6 +2647,10 @@ export interface Page { prev: string | undefined; /** url of the next page (if there is one) */ next: string | undefined; + /** url of the first page (if the current page is not the first page) */ + first: string | undefined; + /** url of the next page (if the current page in not the last page) */ + last: string | undefined; }; } @@ -3052,83 +3056,92 @@ export type HookParameters< Fn = AstroIntegration['hooks'][Hook], > = Fn extends (...args: any) => any ? Parameters[0] : never; +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Astro { + export interface IntegrationHooks { + 'astro:config:setup': (options: { + config: AstroConfig; + command: 'dev' | 'build' | 'preview'; + isRestart: boolean; + updateConfig: (newConfig: DeepPartial) => AstroConfig; + addRenderer: (renderer: AstroRenderer) => void; + addWatchFile: (path: URL | string) => void; + injectScript: (stage: InjectedScriptStage, content: string) => void; + injectRoute: (injectRoute: InjectedRoute) => void; + addClientDirective: (directive: ClientDirectiveConfig) => void; + /** + * @deprecated Use `addDevToolbarApp` instead. + * TODO: Fully remove in Astro 5.0 + */ + addDevOverlayPlugin: (entrypoint: string) => void; + // TODO: Deprecate the `string` overload once a few apps have been migrated to the new API. + addDevToolbarApp: (entrypoint: DevToolbarAppEntry | string) => void; + addMiddleware: (mid: AstroIntegrationMiddleware) => void; + logger: AstroIntegrationLogger; + // TODO: Add support for `injectElement()` for full HTML element injection, not just scripts. + // This may require some refactoring of `scripts`, `styles`, and `links` into something + // more generalized. Consider the SSR use-case as well. + // injectElement: (stage: vite.HtmlTagDescriptor, element: string) => void; + }) => void | Promise; + 'astro:config:done': (options: { + config: AstroConfig; + setAdapter: (adapter: AstroAdapter) => void; + logger: AstroIntegrationLogger; + }) => void | Promise; + 'astro:server:setup': (options: { + server: vite.ViteDevServer; + logger: AstroIntegrationLogger; + toolbar: ReturnType; + }) => void | Promise; + 'astro:server:start': (options: { + address: AddressInfo; + logger: AstroIntegrationLogger; + }) => void | Promise; + 'astro:server:done': (options: { logger: AstroIntegrationLogger }) => void | Promise; + 'astro:build:ssr': (options: { + manifest: SerializedSSRManifest; + /** + * This maps a {@link RouteData} to an {@link URL}, this URL represents + * the physical file you should import. + */ + entryPoints: Map; + /** + * File path of the emitted middleware + */ + middlewareEntryPoint: URL | undefined; + logger: AstroIntegrationLogger; + }) => void | Promise; + 'astro:build:start': (options: { logger: AstroIntegrationLogger }) => void | Promise; + 'astro:build:setup': (options: { + vite: vite.InlineConfig; + pages: Map; + target: 'client' | 'server'; + updateConfig: (newConfig: vite.InlineConfig) => void; + logger: AstroIntegrationLogger; + }) => void | Promise; + 'astro:build:generated': (options: { + dir: URL; + logger: AstroIntegrationLogger; + }) => void | Promise; + 'astro:build:done': (options: { + pages: { pathname: string }[]; + dir: URL; + routes: RouteData[]; + logger: AstroIntegrationLogger; + cacheManifest: boolean; + }) => void | Promise; + } + } +} + export interface AstroIntegration { /** The name of the integration. */ name: string; /** The different hooks available to extend. */ hooks: { - 'astro:config:setup'?: (options: { - config: AstroConfig; - command: 'dev' | 'build' | 'preview'; - isRestart: boolean; - updateConfig: (newConfig: DeepPartial) => AstroConfig; - addRenderer: (renderer: AstroRenderer) => void; - addWatchFile: (path: URL | string) => void; - injectScript: (stage: InjectedScriptStage, content: string) => void; - injectRoute: (injectRoute: InjectedRoute) => void; - addClientDirective: (directive: ClientDirectiveConfig) => void; - /** - * @deprecated Use `addDevToolbarApp` instead. - * TODO: Fully remove in Astro 5.0 - */ - addDevOverlayPlugin: (entrypoint: string) => void; - // TODO: Deprecate the `string` overload once a few apps have been migrated to the new API. - addDevToolbarApp: (entrypoint: DevToolbarAppEntry | string) => void; - addMiddleware: (mid: AstroIntegrationMiddleware) => void; - logger: AstroIntegrationLogger; - // TODO: Add support for `injectElement()` for full HTML element injection, not just scripts. - // This may require some refactoring of `scripts`, `styles`, and `links` into something - // more generalized. Consider the SSR use-case as well. - // injectElement: (stage: vite.HtmlTagDescriptor, element: string) => void; - }) => void | Promise; - 'astro:config:done'?: (options: { - config: AstroConfig; - setAdapter: (adapter: AstroAdapter) => void; - logger: AstroIntegrationLogger; - }) => void | Promise; - 'astro:server:setup'?: (options: { - server: vite.ViteDevServer; - logger: AstroIntegrationLogger; - toolbar: ReturnType; - }) => void | Promise; - 'astro:server:start'?: (options: { - address: AddressInfo; - logger: AstroIntegrationLogger; - }) => void | Promise; - 'astro:server:done'?: (options: { logger: AstroIntegrationLogger }) => void | Promise; - 'astro:build:ssr'?: (options: { - manifest: SerializedSSRManifest; - /** - * This maps a {@link RouteData} to an {@link URL}, this URL represents - * the physical file you should import. - */ - entryPoints: Map; - /** - * File path of the emitted middleware - */ - middlewareEntryPoint: URL | undefined; - logger: AstroIntegrationLogger; - }) => void | Promise; - 'astro:build:start'?: (options: { logger: AstroIntegrationLogger }) => void | Promise; - 'astro:build:setup'?: (options: { - vite: vite.InlineConfig; - pages: Map; - target: 'client' | 'server'; - updateConfig: (newConfig: vite.InlineConfig) => void; - logger: AstroIntegrationLogger; - }) => void | Promise; - 'astro:build:generated'?: (options: { - dir: URL; - logger: AstroIntegrationLogger; - }) => void | Promise; - 'astro:build:done'?: (options: { - pages: { pathname: string }[]; - dir: URL; - routes: RouteData[]; - logger: AstroIntegrationLogger; - cacheManifest: boolean; - }) => void | Promise; - }; + [K in keyof Astro.IntegrationHooks]?: Astro.IntegrationHooks[K]; + } & Partial>; } export type RewritePayload = string | URL | Request; diff --git a/packages/astro/src/actions/index.ts b/packages/astro/src/actions/index.ts index e20f8647dd97..f4ab24e2d428 100644 --- a/packages/astro/src/actions/index.ts +++ b/packages/astro/src/actions/index.ts @@ -1,4 +1,4 @@ -import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import fsMod from 'node:fs'; import type { Plugin as VitePlugin } from 'vite'; import type { AstroIntegration } from '../@types/astro.js'; import { ActionsWithoutServerOutputError } from '../core/errors/errors-data.js'; @@ -6,7 +6,7 @@ import { AstroError } from '../core/errors/errors.js'; import { isServerLikeOutput, viteID } from '../core/util.js'; import { ACTIONS_TYPES_FILE, RESOLVED_VIRTUAL_MODULE_ID, VIRTUAL_MODULE_ID } from './consts.js'; -export default function astroActions(): AstroIntegration { +export default function astroActions({ fs = fsMod }: { fs?: typeof fsMod }): AstroIntegration { return { name: VIRTUAL_MODULE_ID, hooks: { @@ -25,7 +25,7 @@ export default function astroActions(): AstroIntegration { define: { 'import.meta.env.ACTIONS_PATH': stringifiedActionsImport, }, - plugins: [vitePluginActions], + plugins: [vitePluginActions(fs)], }, }); @@ -43,13 +43,14 @@ export default function astroActions(): AstroIntegration { await typegen({ stringifiedActionsImport, root: params.config.root, + fs, }); }, }, }; } -const vitePluginActions: VitePlugin = { +const vitePluginActions = (fs: typeof fsMod): VitePlugin => ({ name: VIRTUAL_MODULE_ID, enforce: 'pre', resolveId(id) { @@ -60,7 +61,10 @@ const vitePluginActions: VitePlugin = { async load(id, opts) { if (id !== RESOLVED_VIRTUAL_MODULE_ID) return; - let code = await readFile(new URL('../../templates/actions.mjs', import.meta.url), 'utf-8'); + let code = await fs.promises.readFile( + new URL('../../templates/actions.mjs', import.meta.url), + 'utf-8' + ); if (opts?.ssr) { code += `\nexport * from 'astro/actions/runtime/virtual/server.js';`; } else { @@ -68,14 +72,16 @@ const vitePluginActions: VitePlugin = { } return code; }, -}; +}); async function typegen({ stringifiedActionsImport, root, + fs, }: { stringifiedActionsImport: string; root: URL; + fs: typeof fsMod; }) { const content = `declare module "astro:actions" { type Actions = typeof import(${stringifiedActionsImport})["server"]; @@ -85,6 +91,6 @@ async function typegen({ const dotAstroDir = new URL('.astro/', root); - await mkdir(dotAstroDir, { recursive: true }); - await writeFile(new URL(ACTIONS_TYPES_FILE, dotAstroDir), content); + await fs.promises.mkdir(dotAstroDir, { recursive: true }); + await fs.promises.writeFile(new URL(ACTIONS_TYPES_FILE, dotAstroDir), content); } diff --git a/packages/astro/src/actions/runtime/virtual/server.ts b/packages/astro/src/actions/runtime/virtual/server.ts index 326bbf4f9be6..3efa7ca149a0 100644 --- a/packages/astro/src/actions/runtime/virtual/server.ts +++ b/packages/astro/src/actions/runtime/virtual/server.ts @@ -124,8 +124,8 @@ export function formDataToObject( const obj: Record = {}; for (const [key, baseValidator] of Object.entries(schema.shape)) { let validator = baseValidator; - if (baseValidator instanceof z.ZodOptional || baseValidator instanceof z.ZodNullable) { - validator = baseValidator._def.innerType; + while (validator instanceof z.ZodOptional || validator instanceof z.ZodNullable) { + validator = validator._def.innerType; } if (validator instanceof z.ZodBoolean) { obj[key] = formData.has(key); diff --git a/packages/astro/src/assets/internal.ts b/packages/astro/src/assets/internal.ts index 8770f27b5998..7265e85dfb10 100644 --- a/packages/astro/src/assets/internal.ts +++ b/packages/astro/src/assets/internal.ts @@ -10,7 +10,7 @@ import { isImageMetadata, } from './types.js'; import { isESMImportedImage, isRemoteImage, resolveSrc } from './utils/imageKind.js'; -import { probe } from './utils/remoteProbe.js'; +import { inferRemoteSize } from './utils/remoteProbe.js'; export async function getConfiguredImageService(): Promise { if (!globalThis?.astroAsset?.imageService) { @@ -66,17 +66,10 @@ export async function getImage( // Infer size for remote images if inferSize is true if (options.inferSize && isRemoteImage(resolvedOptions.src)) { - try { - const result = await probe(resolvedOptions.src); // Directly probe the image URL - resolvedOptions.width ??= result.width; - resolvedOptions.height ??= result.height; - delete resolvedOptions.inferSize; // Delete so it doesn't end up in the attributes - } catch { - throw new AstroError({ - ...AstroErrorData.FailedToFetchRemoteImageDimensions, - message: AstroErrorData.FailedToFetchRemoteImageDimensions.message(resolvedOptions.src), - }); - } + const result = await inferRemoteSize(resolvedOptions.src); // Directly probe the image URL + resolvedOptions.width ??= result.width; + resolvedOptions.height ??= result.height; + delete resolvedOptions.inferSize; // Delete so it doesn't end up in the attributes } const originalFilePath = isESMImportedImage(resolvedOptions.src) diff --git a/packages/astro/src/assets/utils/index.ts b/packages/astro/src/assets/utils/index.ts index 4fb4e42db373..69e7c88dc401 100644 --- a/packages/astro/src/assets/utils/index.ts +++ b/packages/astro/src/assets/utils/index.ts @@ -1,4 +1,4 @@ -export { emitESMImage } from './emitAsset.js'; +export { emitESMImage } from './node/emitAsset.js'; export { isESMImportedImage, isRemoteImage } from './imageKind.js'; export { imageMetadata } from './metadata.js'; export { getOrigQueryParams } from './queryParams.js'; @@ -12,3 +12,4 @@ export { type RemotePattern, } from './remotePattern.js'; export { hashTransform, propsToFilename } from './transformToPath.js'; +export { inferRemoteSize } from './remoteProbe.js'; diff --git a/packages/astro/src/assets/utils/emitAsset.ts b/packages/astro/src/assets/utils/node/emitAsset.ts similarity index 93% rename from packages/astro/src/assets/utils/emitAsset.ts rename to packages/astro/src/assets/utils/node/emitAsset.ts index 1b6bb207bbb1..3a590e3a6814 100644 --- a/packages/astro/src/assets/utils/emitAsset.ts +++ b/packages/astro/src/assets/utils/node/emitAsset.ts @@ -2,9 +2,9 @@ import fs from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import type * as vite from 'vite'; -import { prependForwardSlash, slash } from '../../core/path.js'; -import type { ImageMetadata } from '../types.js'; -import { imageMetadata } from './metadata.js'; +import { prependForwardSlash, slash } from '../../../core/path.js'; +import type { ImageMetadata } from '../../types.js'; +import { imageMetadata } from '../metadata.js'; type FileEmitter = vite.Rollup.EmitFile; diff --git a/packages/astro/src/assets/utils/remoteProbe.ts b/packages/astro/src/assets/utils/remoteProbe.ts index 1cda4ca45818..c71413069e68 100644 --- a/packages/astro/src/assets/utils/remoteProbe.ts +++ b/packages/astro/src/assets/utils/remoteProbe.ts @@ -1,11 +1,15 @@ -import { lookup } from './vendor/image-size/lookup.js'; -import type { ISize } from './vendor/image-size/types/interface.ts'; +import { AstroError, AstroErrorData } from '../../core/errors/index.js'; +import type { ImageMetadata } from '../types.js'; +import { imageMetadata } from './metadata.js'; -export async function probe(url: string): Promise { +export async function inferRemoteSize(url: string): Promise> { // Start fetching the image const response = await fetch(url); if (!response.body || !response.ok) { - throw new Error('Failed to fetch image'); + throw new AstroError({ + ...AstroErrorData.FailedToFetchRemoteImageDimensions, + message: AstroErrorData.FailedToFetchRemoteImageDimensions.message(url), + }); } const reader = response.body.getReader(); @@ -31,17 +35,22 @@ export async function probe(url: string): Promise { try { // Attempt to determine the size with each new chunk - const dimensions = lookup(accumulatedChunks); + const dimensions = await imageMetadata(accumulatedChunks, url); + if (dimensions) { await reader.cancel(); // stop stream as we have size now + return dimensions; } } catch (error) { - // This catch block is specifically for `sizeOf` failures, + // This catch block is specifically for `imageMetadata` errors // which might occur if the accumulated data isn't yet sufficient. } } } - throw new Error('Failed to parse the size'); + throw new AstroError({ + ...AstroErrorData.NoImageMetadata, + message: AstroErrorData.NoImageMetadata.message(url), + }); } diff --git a/packages/astro/src/assets/vite-plugin-assets.ts b/packages/astro/src/assets/vite-plugin-assets.ts index eda4b6cfbc2a..949a6e6722d5 100644 --- a/packages/astro/src/assets/vite-plugin-assets.ts +++ b/packages/astro/src/assets/vite-plugin-assets.ts @@ -14,9 +14,9 @@ import { } from '../core/path.js'; import { isServerLikeOutput } from '../core/util.js'; import { VALID_INPUT_FORMATS, VIRTUAL_MODULE_ID, VIRTUAL_SERVICE_ID } from './consts.js'; -import { emitESMImage } from './utils/emitAsset.js'; import { getAssetsPrefix } from './utils/getAssetsPrefix.js'; import { isESMImportedImage } from './utils/imageKind.js'; +import { emitESMImage } from './utils/node/emitAsset.js'; import { getProxyCode } from './utils/proxy.js'; import { hashTransform, propsToFilename } from './utils/transformToPath.js'; @@ -133,6 +133,7 @@ export default function assets({ import { getImage as getImageInternal } from "astro/assets"; export { default as Image } from "astro/components/Image.astro"; export { default as Picture } from "astro/components/Picture.astro"; + export { inferRemoteSize } from "astro/assets/utils/inferRemoteSize.js"; export const imageConfig = ${JSON.stringify(settings.config.image)}; // This is used by the @astrojs/node integration to locate images. diff --git a/packages/astro/src/cli/check/index.ts b/packages/astro/src/cli/check/index.ts index 721a0bf6911b..ff7835fdca08 100644 --- a/packages/astro/src/cli/check/index.ts +++ b/packages/astro/src/cli/check/index.ts @@ -28,10 +28,10 @@ export async function check(flags: Arguments) { // NOTE: In the future, `@astrojs/check` can expose a `before lint` hook so that this works during `astro check --watch` too. // For now, we run this once as usually `astro check --watch` is ran alongside `astro dev` which also calls `astro sync`. const { default: sync } = await import('../../core/sync/index.js'); - const inlineConfig = flagsToAstroInlineConfig(flags); - const exitCode = await sync(inlineConfig); - if (exitCode !== 0) { - process.exit(exitCode); + try { + await sync({ inlineConfig: flagsToAstroInlineConfig(flags) }); + } catch (_) { + return process.exit(1); } const { check: checker, parseArgsAsCheckConfig } = checkPackage; diff --git a/packages/astro/src/cli/sync/index.ts b/packages/astro/src/cli/sync/index.ts index 8650bf904655..6849fee70844 100644 --- a/packages/astro/src/cli/sync/index.ts +++ b/packages/astro/src/cli/sync/index.ts @@ -20,8 +20,10 @@ export async function sync({ flags }: SyncOptions) { return 0; } - const inlineConfig = flagsToAstroInlineConfig(flags); - - const exitCode = await _sync(inlineConfig); - return exitCode; + try { + await _sync({ inlineConfig: flagsToAstroInlineConfig(flags), telemetry: true }); + return 0; + } catch (_) { + return 1; + } } diff --git a/packages/astro/src/content/runtime-assets.ts b/packages/astro/src/content/runtime-assets.ts index 26d98fd6e8db..42969042c678 100644 --- a/packages/astro/src/content/runtime-assets.ts +++ b/packages/astro/src/content/runtime-assets.ts @@ -1,7 +1,7 @@ import type { PluginContext } from 'rollup'; import { z } from 'zod'; import type { ImageMetadata, OmitBrand } from '../assets/types.js'; -import { emitESMImage } from '../assets/utils/emitAsset.js'; +import { emitESMImage } from '../assets/utils/node/emitAsset.js'; export function createImage( pluginContext: PluginContext, diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts index 0ef48a926542..aa3c7370d37c 100644 --- a/packages/astro/src/core/app/index.ts +++ b/packages/astro/src/core/app/index.ts @@ -314,10 +314,12 @@ export class App { } const pathname = this.#getPathnameFromRequest(request); const defaultStatus = this.#getDefaultStatusCode(routeData, pathname); - const mod = await this.#pipeline.getModuleForRoute(routeData); let response; try { + // Load route module. We also catch its error here if it fails on initialization + const mod = await this.#pipeline.getModuleForRoute(routeData); + const renderContext = RenderContext.create({ pipeline: this.#pipeline, locals, diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index 854a3fbd0914..9376cf439445 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -144,11 +144,12 @@ class AstroBuilder { ); await runHookConfigDone({ settings: this.settings, logger: logger }); - const { syncContentCollections } = await import('../sync/index.js'); - const syncRet = await syncContentCollections(this.settings, { logger: logger, fs }); - if (syncRet !== 0) { - return process.exit(syncRet); - } + const { syncInternal } = await import('../sync/index.js'); + await syncInternal({ + settings: this.settings, + logger, + fs, + }); const dataStore = await DataStore.fromModule(); globalDataStore.set(dataStore); diff --git a/packages/astro/src/core/build/plugins/plugin-pages.ts b/packages/astro/src/core/build/plugins/plugin-pages.ts index 9b7faf07c6bb..4df70eac4b56 100644 --- a/packages/astro/src/core/build/plugins/plugin-pages.ts +++ b/packages/astro/src/core/build/plugins/plugin-pages.ts @@ -44,8 +44,8 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V for (const pageData of pageDatas) { const resolvedPage = await this.resolve(pageData.moduleSpecifier); if (resolvedPage) { - imports.push(`const page = () => import(${JSON.stringify(pageData.moduleSpecifier)});`); - exports.push(`export { page }`); + imports.push(`import * as _page from ${JSON.stringify(pageData.moduleSpecifier)};`); + exports.push(`export const page = () => _page`); imports.push(`import { renderers } from "${RENDERERS_MODULE_ID}";`); exports.push(`export { renderers };`); diff --git a/packages/astro/src/core/build/plugins/plugin-prerender.ts b/packages/astro/src/core/build/plugins/plugin-prerender.ts index b7feb70e36a0..a8ace9a2549e 100644 --- a/packages/astro/src/core/build/plugins/plugin-prerender.ts +++ b/packages/astro/src/core/build/plugins/plugin-prerender.ts @@ -40,7 +40,7 @@ function getNonPrerenderOnlyChunks(bundle: Rollup.OutputBundle, internals: Build const prerenderOnlyEntryChunks = new Set(); const nonPrerenderOnlyEntryChunks = new Set(); for (const chunk of chunks) { - if (chunk.type === 'chunk' && (chunk.isEntry || chunk.isDynamicEntry)) { + if (chunk.type === 'chunk' && chunk.isEntry) { // See if this entry chunk is prerendered, if so, skip it if (chunk.facadeModuleId?.startsWith(ASTRO_PAGE_RESOLVED_MODULE_ID)) { const pageDatas = getPagesFromVirtualModulePageName( diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index 5b75dbc9354f..da651effdcdd 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -215,14 +215,15 @@ async function ssrBuild( if (isContentCache) { prefix += `${buildID}/`; suffix = '.mjs'; - } - if (isContentCache && name.includes('/content/')) { - const parts = name.split('/'); - if (parts.at(1) === 'content') { - return encodeName(parts.slice(1).join('/')); + if (name.includes('/content/')) { + const parts = name.split('/'); + if (parts.at(1) === 'content') { + return encodeName(parts.slice(1).join('/')); + } } } + // Sometimes chunks have the `@_@astro` suffix due to SSR logic. Remove it! // TODO: refactor our build logic to avoid this if (name.includes(ASTRO_PAGE_EXTENSION_POST_PATTERN)) { diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts index bb3130137eeb..c27dc682449b 100644 --- a/packages/astro/src/core/config/schema.ts +++ b/packages/astro/src/core/config/schema.ts @@ -321,6 +321,9 @@ export const AstroConfigSchema = z.object({ .or(z.custom()) ) .default(ASTRO_CONFIG_DEFAULTS.markdown.shikiConfig.themes!), + defaultColor: z + .union([z.literal('light'), z.literal('dark'), z.string(), z.literal(false)]) + .optional(), wrap: z.boolean().or(z.null()).default(ASTRO_CONFIG_DEFAULTS.markdown.shikiConfig.wrap!), transformers: z .custom() diff --git a/packages/astro/src/core/constants.ts b/packages/astro/src/core/constants.ts index 0930ea6f0f7d..8e9f5ac74e0a 100644 --- a/packages/astro/src/core/constants.ts +++ b/packages/astro/src/core/constants.ts @@ -22,6 +22,14 @@ export const ASTRO_VERSION = process.env.PACKAGE_VERSION ?? 'development'; */ export const REROUTE_DIRECTIVE_HEADER = 'X-Astro-Reroute'; +/** + * Header and value that are attached to a Response object when a **user rewrite** occurs. + * + * This metadata is used to determine the origin of a Response. If a rewrite has occurred, it should be prioritised over other logic. + */ +export const REWRITE_DIRECTIVE_HEADER_KEY = 'X-Astro-Rewrite'; +export const REWRITE_DIRECTIVE_HEADER_VALUE = 'yes'; + /** * The name for the header used to help i18n middleware, which only needs to act on "page" and "fallback" route types. */ diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index e07150c39db8..4462bb088539 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -25,7 +25,6 @@ import envVitePlugin from '../vite-plugin-env/index.js'; import vitePluginFileURL from '../vite-plugin-fileurl/index.js'; import astroHeadPlugin from '../vite-plugin-head/index.js'; import htmlVitePlugin from '../vite-plugin-html/index.js'; -import { astroInjectEnvTsPlugin } from '../vite-plugin-inject-env-ts/index.js'; import astroIntegrationsContainerPlugin from '../vite-plugin-integrations-container/index.js'; import astroLoadFallbackPlugin from '../vite-plugin-load-fallback/index.js'; import markdownVitePlugin from '../vite-plugin-markdown/index.js'; @@ -120,13 +119,8 @@ export async function createVite( customLogger: createViteLogger(logger, settings.config.vite.logLevel), appType: 'custom', optimizeDeps: { - // Scan all files within `srcDir` except for known server-code (e.g endpoints) - entries: [ - `${srcDirPattern}!(pages)/**/*`, // All files except for pages - `${srcDirPattern}pages/**/!(*.js|*.mjs|*.ts|*.mts)`, // All pages except for endpoints - `${srcDirPattern}pages/**/_*.{js,mjs,ts,mts}`, // Remaining JS/TS files prefixed with `_` (not endpoints) - `${srcDirPattern}pages/**/_*/**/*.{js,mjs,ts,mts}`, // Remaining JS/TS files within directories prefixed with `_` (not endpoints) - ], + // Scan for component code within `srcDir` + entries: [`${srcDirPattern}**/*.{jsx,tsx,vue,svelte,html,astro}`], exclude: ['astro', 'node-fetch'], }, plugins: [ @@ -147,7 +141,6 @@ export async function createVite( astroScriptsPageSSRPlugin({ settings }), astroHeadPlugin(), astroScannerPlugin({ settings, logger }), - astroInjectEnvTsPlugin({ settings, logger, fs }), astroContentVirtualModPlugin({ fs, settings }), astroContentImportPlugin({ fs, settings, logger }), astroContentAssetPropagationPlugin({ mode, settings }), diff --git a/packages/astro/src/core/dev/container.ts b/packages/astro/src/core/dev/container.ts index 0102a87cd0be..aaf6506e9d81 100644 --- a/packages/astro/src/core/dev/container.ts +++ b/packages/astro/src/core/dev/container.ts @@ -14,6 +14,7 @@ import { import { createVite } from '../create-vite.js'; import type { Logger } from '../logger/core.js'; import { apply as applyPolyfill } from '../polyfill.js'; +import { syncInternal } from '../sync/index.js'; export interface Container { fs: typeof nodeFs; @@ -56,9 +57,22 @@ export async function createContainer({ base, server: { host, headers, open: serverOpen }, } = settings.config; + + // serverOpen = true, isRestart = false + // when astro dev --open command is run the first time + // expected behavior: spawn a new tab + // ------------------------------------------------------ + // serverOpen = true, isRestart = true + // when config file is saved + // expected behavior: do not spawn a new tab + // ------------------------------------------------------ + // Non-config files don't reach this point + const isServerOpenURL = typeof serverOpen == 'string' && !isRestart; + const isServerOpenBoolean = serverOpen && !isRestart; + // Open server to the correct path. We pass the `base` here as we didn't pass the // base to the initial Vite config - const open = typeof serverOpen == 'string' ? serverOpen : serverOpen ? base : false; + const open = isServerOpenURL ? serverOpen : isServerOpenBoolean ? base : false; // The client entrypoint for renderers. Since these are imported dynamically // we need to tell Vite to preoptimize them. @@ -77,6 +91,14 @@ export async function createContainer({ { settings, logger, mode: 'dev', command: 'dev', fs, sync: false } ); await runHookConfigDone({ settings, logger }); + await syncInternal({ + settings, + logger, + skip: { + content: true, + }, + }); + const viteServer = await vite.createServer(viteConfig); const container: Container = { diff --git a/packages/astro/src/core/errors/dev/vite.ts b/packages/astro/src/core/errors/dev/vite.ts index 96e53d41c2e7..4d70a52686dd 100644 --- a/packages/astro/src/core/errors/dev/vite.ts +++ b/packages/astro/src/core/errors/dev/vite.ts @@ -146,8 +146,7 @@ export async function getViteErrorPayload(err: ErrorWithMetadata): Promise - `The following environment variables do not match the data type and/or properties defined in \`experimental.env.schema\`:\n\n${variables}\n`, + message: (errors: Array) => + `The following environment variables defined in \`experimental.env.schema\` are invalid:\n\n${errors.map((err) => `- ${err}`).join('\n')}\n`, } satisfies ErrorData; /** diff --git a/packages/astro/src/core/errors/overlay.ts b/packages/astro/src/core/errors/overlay.ts index 4a7818d66844..c4caa3e8de9a 100644 --- a/packages/astro/src/core/errors/overlay.ts +++ b/packages/astro/src/core/errors/overlay.ts @@ -695,6 +695,10 @@ class ErrorOverlay extends HTMLElement { const el = this.root.querySelector(selector); + if (!el) { + return; + } + if (html) { // Automatically detect links text = text @@ -706,14 +710,10 @@ class ErrorOverlay extends HTMLElement { return `${v}`; }) .join(' '); - } - if (el) { - if (!html) { - el.textContent = text.trim(); - } else { - el.innerHTML = text.trim(); - } + el.innerHTML = text.trim(); + } else { + el.textContent = text.trim(); } } diff --git a/packages/astro/src/core/index.ts b/packages/astro/src/core/index.ts index 31d868311455..e0f9f6c82412 100644 --- a/packages/astro/src/core/index.ts +++ b/packages/astro/src/core/index.ts @@ -22,5 +22,5 @@ export const build = (inlineConfig: AstroInlineConfig) => _build(inlineConfig); * * @experimental The JavaScript API is experimental */ -// Wrap `_sync` to prevent exposing the second internal options parameter -export const sync = (inlineConfig: AstroInlineConfig) => _sync(inlineConfig); +// Wrap `_sync` to prevent exposing internal options +export const sync = (inlineConfig: AstroInlineConfig) => _sync({ inlineConfig }); diff --git a/packages/astro/src/core/middleware/callMiddleware.ts b/packages/astro/src/core/middleware/callMiddleware.ts index d52ba01265f2..5f4fb3d0f5e5 100644 --- a/packages/astro/src/core/middleware/callMiddleware.ts +++ b/packages/astro/src/core/middleware/callMiddleware.ts @@ -56,15 +56,16 @@ export async function callMiddleware( let responseFunctionPromise: Promise | Response | undefined = undefined; const next: MiddlewareNext = async (payload) => { nextCalled = true; - if (!enableRerouting && payload) { - logger.warn( - 'router', - 'The rewrite API is experimental. To use this feature, add the `rewriting` flag to the `experimental` object in your Astro config.' - ); - } + if (enableRerouting) { responseFunctionPromise = responseFunction(apiContext, payload); } else { + if (payload) { + logger.warn( + 'router', + 'The rewrite API is experimental. To use this feature, add the `rewriting` flag to the `experimental` object in your Astro config.' + ); + } responseFunctionPromise = responseFunction(apiContext); } // We need to pass the APIContext pass to `callMiddleware` because it can be mutated across middleware functions diff --git a/packages/astro/src/core/redirects/render.ts b/packages/astro/src/core/redirects/render.ts index 24361fde471f..379f26e3ba48 100644 --- a/packages/astro/src/core/redirects/render.ts +++ b/packages/astro/src/core/redirects/render.ts @@ -25,8 +25,7 @@ function redirectRouteGenerate(renderContext: RenderContext): string { let target = redirect; for (const param of Object.keys(params)) { const paramValue = params[param]!; - target = target.replace(`[${param}]`, paramValue); - target = target.replace(`[...${param}]`, paramValue); + target = target.replace(`[${param}]`, paramValue).replace(`[...${param}]`, paramValue); } return target; } else if (typeof redirect === 'undefined') { diff --git a/packages/astro/src/core/render-context.ts b/packages/astro/src/core/render-context.ts index 910ee679e274..561d5b7f3e87 100644 --- a/packages/astro/src/core/render-context.ts +++ b/packages/astro/src/core/render-context.ts @@ -21,6 +21,8 @@ import { renderPage } from '../runtime/server/index.js'; import { ASTRO_VERSION, REROUTE_DIRECTIVE_HEADER, + REWRITE_DIRECTIVE_HEADER_KEY, + REWRITE_DIRECTIVE_HEADER_VALUE, ROUTE_TYPE_HEADER, clientAddressSymbol, clientLocalsSymbol, @@ -184,13 +186,12 @@ export class RenderContext { // Signal to the i18n middleware to maybe act on this response response.headers.set(ROUTE_TYPE_HEADER, 'page'); // Signal to the error-page-rerouting infra to let this response pass through to avoid loops - if ( - this.routeData.route === '/404' || - this.routeData.route === '/500' || - this.isRewriting - ) { + if (this.routeData.route === '/404' || this.routeData.route === '/500') { response.headers.set(REROUTE_DIRECTIVE_HEADER, 'no'); } + if (this.isRewriting) { + response.headers.set(REWRITE_DIRECTIVE_HEADER_KEY, REWRITE_DIRECTIVE_HEADER_VALUE); + } break; } case 'fallback': { @@ -381,6 +382,7 @@ export class RenderContext { } #astroPagePartial?: Omit; + /** * The Astro global is sourced in 3 different phases: * - **Static**: `.generator` and `.glob` is printed by the compiler, instantiated once per process per astro file @@ -512,6 +514,7 @@ export class RenderContext { * So, it is computed and saved here on creation of the first APIContext and reused for later ones. */ #currentLocale: APIContext['currentLocale']; + computeCurrentLocale() { const { url, @@ -537,6 +540,7 @@ export class RenderContext { } #preferredLocale: APIContext['preferredLocale']; + computePreferredLocale() { const { pipeline: { i18n }, @@ -547,6 +551,7 @@ export class RenderContext { } #preferredLocaleList: APIContext['preferredLocaleList']; + computePreferredLocaleList() { const { pipeline: { i18n }, diff --git a/packages/astro/src/core/render/paginate.ts b/packages/astro/src/core/render/paginate.ts index 00aba3702bac..591d739c5b05 100644 --- a/packages/astro/src/core/render/paginate.ts +++ b/packages/astro/src/core/render/paginate.ts @@ -56,6 +56,19 @@ export function generatePaginateFunction( !includesFirstPageNumber && pageNum - 1 === 1 ? undefined : String(pageNum - 1), }) ); + const first = + pageNum === 1 + ? undefined + : correctIndexRoute( + routeMatch.generate({ + ...params, + page: includesFirstPageNumber ? '1' : undefined, + }) + ); + const last = + pageNum === lastPage + ? undefined + : correctIndexRoute(routeMatch.generate({ ...params, page: String(lastPage) })); return { params, props: { @@ -68,7 +81,7 @@ export function generatePaginateFunction( total: data.length, currentPage: pageNum, lastPage: lastPage, - url: { current, next, prev }, + url: { current, next, prev, first, last }, } as Page, }, }; diff --git a/packages/astro/src/core/sync/index.ts b/packages/astro/src/core/sync/index.ts index d1b80f00ac4f..ece724cec989 100644 --- a/packages/astro/src/core/sync/index.ts +++ b/packages/astro/src/core/sync/index.ts @@ -12,7 +12,6 @@ import { syncAstroEnv } from '../../env/sync.js'; import { telemetry } from '../../events/index.js'; import { eventCliSession } from '../../events/session.js'; import { runHookConfigSetup } from '../../integrations/hooks.js'; -import { setUpEnvTs } from '../../vite-plugin-inject-env-ts/index.js'; import { getTimeStat } from '../build/util.js'; import { resolveConfig } from '../config/config.js'; import { createNodeLogger } from '../config/logging.js'; @@ -29,46 +28,58 @@ import { import type { Logger } from '../logger/core.js'; import { formatErrorMessage } from '../messages.js'; import { ensureProcessNodeEnv } from '../util.js'; - -export type ProcessExit = 0 | 1; +import { setUpEnvTs } from './setup-env-ts.js'; export type SyncOptions = { /** * @internal only used for testing */ fs?: typeof fsMod; -}; - -export type SyncInternalOptions = SyncOptions & { logger: Logger; + settings: AstroSettings; + skip?: { + // Must be skipped in dev + content?: boolean; + }; }; type DBPackage = { typegen?: (args: Pick) => Promise; }; +export default async function sync({ + inlineConfig, + fs, + telemetry: _telemetry = false, +}: { inlineConfig: AstroInlineConfig; fs?: typeof fsMod; telemetry?: boolean }) { + ensureProcessNodeEnv('production'); + const logger = createNodeLogger(inlineConfig); + const { astroConfig, userConfig } = await resolveConfig(inlineConfig ?? {}, 'sync'); + if (_telemetry) { + telemetry.record(eventCliSession('sync', userConfig)); + } + let settings = await createSettings(astroConfig, inlineConfig.root); + settings = await runHookConfigSetup({ + command: 'build', + settings, + logger, + }); + return await syncInternal({ settings, logger, fs }); +} + /** * Generates TypeScript types for all Astro modules. This sets up a `src/env.d.ts` file for type inferencing, * and defines the `astro:content` module for the Content Collections API. * * @experimental The JavaScript API is experimental */ -export default async function sync( - inlineConfig: AstroInlineConfig, - options?: SyncOptions -): Promise { - ensureProcessNodeEnv('production'); - const logger = createNodeLogger(inlineConfig); - const { userConfig, astroConfig } = await resolveConfig(inlineConfig ?? {}, 'sync'); - telemetry.record(eventCliSession('sync', userConfig)); - - const _settings = await createSettings(astroConfig, fileURLToPath(astroConfig.root)); - - const settings = await runHookConfigSetup({ - settings: _settings, - logger: logger, - command: 'build', - }); +export async function syncInternal({ + logger, + fs = fsMod, + settings, + skip, +}: SyncOptions): Promise { + const cwd = fileURLToPath(settings.config.root); const timerStart = performance.now(); const dbPackage = await getPackage( @@ -76,28 +87,30 @@ export default async function sync( logger, { optional: true, - cwd: inlineConfig.root, + cwd, }, [] ); try { - await dbPackage?.typegen?.(astroConfig); - const exitCode = await syncContentCollections(settings, { ...options, logger }); - if (exitCode !== 0) return exitCode; - syncAstroEnv(settings, options?.fs); + await dbPackage?.typegen?.(settings.config); + if (!skip?.content) { + await syncContentCollections(settings, { fs, logger }); + } + syncAstroEnv(settings, fs); await syncContentLayer({ settings, logger }); - logger.info(null, `Types generated ${dim(getTimeStat(timerStart, performance.now()))}`); - return 0; + await setUpEnvTs({ settings, logger, fs }); + logger.info('types', `Generated ${dim(getTimeStat(timerStart, performance.now()))}`); } catch (err) { const error = createSafeError(err); logger.error( - 'content', + 'types', formatErrorMessage(collectErrorMetadata(error), logger.level() === 'debug') + '\n' ); - return 1; + // Will return exit code 1 in CLI + throw error; } } @@ -115,10 +128,10 @@ export default async function sync( * @param {LogOptions} options.logging Logging options * @return {Promise} */ -export async function syncContentCollections( +async function syncContentCollections( settings: AstroSettings, - { logger, fs }: SyncInternalOptions -): Promise { + { logger, fs }: Required> +): Promise { // Needed to load content config const tempViteServer = await createServer( await createVite( @@ -146,7 +159,7 @@ export async function syncContentCollections( const contentTypesGenerator = await createContentTypesGenerator({ contentConfigObserver: globalContentConfigObserver, logger: logger, - fs: fs ?? fsMod, + fs, settings, viteServer: tempViteServer, }); @@ -162,7 +175,6 @@ export async function syncContentCollections( case 'no-content-dir': default: logger.debug('types', 'No content directory found. Skipping type generation.'); - return 0; } } } catch (e) { @@ -182,8 +194,4 @@ export async function syncContentCollections( } finally { await tempViteServer.close(); } - - await setUpEnvTs({ settings, logger, fs: fs ?? fsMod }); - - return 0; } diff --git a/packages/astro/src/vite-plugin-inject-env-ts/index.ts b/packages/astro/src/core/sync/setup-env-ts.ts similarity index 68% rename from packages/astro/src/vite-plugin-inject-env-ts/index.ts rename to packages/astro/src/core/sync/setup-env-ts.ts index 3ebecce2dd51..5a380271a297 100644 --- a/packages/astro/src/vite-plugin-inject-env-ts/index.ts +++ b/packages/astro/src/core/sync/setup-env-ts.ts @@ -2,36 +2,12 @@ import type fsMod from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { bold } from 'kleur/colors'; -import { type Plugin, normalizePath } from 'vite'; -import type { AstroSettings } from '../@types/astro.js'; -import { ACTIONS_TYPES_FILE } from '../actions/consts.js'; -import { CONTENT_TYPES_FILE } from '../content/consts.js'; -import { type Logger } from '../core/logger/core.js'; -import { ENV_TYPES_FILE } from '../env/constants.js'; - -export function getEnvTsPath({ srcDir }: { srcDir: URL }) { - return new URL('env.d.ts', srcDir); -} - -export function astroInjectEnvTsPlugin({ - settings, - logger, - fs, -}: { - settings: AstroSettings; - logger: Logger; - fs: typeof fsMod; -}): Plugin { - return { - name: 'astro-inject-env-ts', - // Use `post` to ensure project setup is complete - // Ex. `.astro` types have been written - enforce: 'post', - async config() { - await setUpEnvTs({ settings, logger, fs }); - }, - }; -} +import { normalizePath } from 'vite'; +import type { AstroSettings } from '../../@types/astro.js'; +import { ACTIONS_TYPES_FILE } from '../../actions/consts.js'; +import { CONTENT_TYPES_FILE } from '../../content/consts.js'; +import { ENV_TYPES_FILE } from '../../env/constants.js'; +import { type Logger } from '../logger/core.js'; function getDotAstroTypeReference({ settings, @@ -58,7 +34,7 @@ export async function setUpEnvTs({ logger: Logger; fs: typeof fsMod; }) { - const envTsPath = getEnvTsPath(settings.config); + const envTsPath = new URL('env.d.ts', settings.config.srcDir); const envTsPathRelativetoRoot = normalizePath( path.relative(fileURLToPath(settings.config.root), fileURLToPath(envTsPath)) ); @@ -80,7 +56,8 @@ export async function setUpEnvTs({ } if (fs.existsSync(envTsPath)) { - let typesEnvContents = await fs.promises.readFile(envTsPath, 'utf-8'); + const initialEnvContents = await fs.promises.readFile(envTsPath, 'utf-8'); + let typesEnvContents = initialEnvContents; for (const injectedType of injectedTypes) { if (!injectedType.meetsCondition || (await injectedType.meetsCondition?.())) { @@ -95,8 +72,10 @@ export async function setUpEnvTs({ } } - logger.info('types', `Added ${bold(envTsPathRelativetoRoot)} type declarations.`); - await fs.promises.writeFile(envTsPath, typesEnvContents, 'utf-8'); + if (initialEnvContents !== typesEnvContents) { + logger.info('types', `Updated ${bold(envTsPathRelativetoRoot)} type declarations.`); + await fs.promises.writeFile(envTsPath, typesEnvContents, 'utf-8'); + } } else { // Otherwise, inject the `env.d.ts` file let referenceDefs: string[] = []; diff --git a/packages/astro/src/env/validators.ts b/packages/astro/src/env/validators.ts index a09eeda8fcff..4e5d342875c5 100644 --- a/packages/astro/src/env/validators.ts +++ b/packages/astro/src/env/validators.ts @@ -1,16 +1,16 @@ import type { EnumSchema, EnvFieldType, NumberSchema, StringSchema } from './schema.js'; export type ValidationResultValue = EnvFieldType['default']; +export type ValidationResultErrors = ['missing'] | ['type'] | Array; type ValidationResult = | { ok: true; - type: string; value: ValidationResultValue; } | { ok: false; - type: string; + errors: ValidationResultErrors; }; export function getEnvFieldType(options: EnvFieldType) { @@ -26,45 +26,50 @@ export function getEnvFieldType(options: EnvFieldType) { return `${type}${optional ? ' | undefined' : ''}`; } -type ValueValidator = (input: string | undefined) => { - valid: boolean; - parsed: ValidationResultValue; -}; +type ValueValidator = (input: string | undefined) => ValidationResult; const stringValidator = ({ max, min, length, url, includes, startsWith, endsWith }: StringSchema): ValueValidator => (input) => { - let valid = typeof input === 'string'; + if (typeof input !== 'string') { + return { + ok: false, + errors: ['type'], + }; + } + const errors: Array = []; - if (valid && max !== undefined) { - valid = input!.length <= max; + if (max !== undefined && !(input.length <= max)) { + errors.push('max'); } - if (valid && min !== undefined) { - valid = input!.length >= min; + if (min !== undefined && !(input.length >= min)) { + errors.push('min'); } - if (valid && length !== undefined) { - valid = input!.length === length; + if (length !== undefined && !(input.length === length)) { + errors.push('length'); } - if (valid && url !== undefined) { - try { - new URL(input!); - } catch (_) { - valid = false; - } + if (url !== undefined && !URL.canParse(input)) { + errors.push('url'); } - if (valid && includes !== undefined) { - valid = input!.includes(includes); + if (includes !== undefined && !input.includes(includes)) { + errors.push('includes'); } - if (valid && startsWith !== undefined) { - valid = input!.startsWith(startsWith); + if (startsWith !== undefined && !input.startsWith(startsWith)) { + errors.push('startsWith'); } - if (valid && endsWith !== undefined) { - valid = input!.endsWith(endsWith); + if (endsWith !== undefined && !input.endsWith(endsWith)) { + errors.push('endsWith'); } + if (errors.length > 0) { + return { + ok: false, + errors, + }; + } return { - valid, - parsed: input, + ok: true, + value: input, }; }; @@ -72,45 +77,71 @@ const numberValidator = ({ gt, min, lt, max, int }: NumberSchema): ValueValidator => (input) => { const num = parseFloat(input ?? ''); - let valid = !isNaN(num); + if (isNaN(num)) { + return { + ok: false, + errors: ['type'], + }; + } + const errors: Array = []; - if (valid && gt !== undefined) { - valid = num > gt; + if (gt !== undefined && !(num > gt)) { + errors.push('gt'); } - if (valid && min !== undefined) { - valid = num >= min; + if (min !== undefined && !(num >= min)) { + errors.push('min'); } - if (valid && lt !== undefined) { - valid = num < lt; + if (lt !== undefined && !(num < lt)) { + errors.push('lt'); } - if (valid && max !== undefined) { - valid = num <= max; + if (max !== undefined && !(num <= max)) { + errors.push('max'); } - if (valid && int !== undefined) { + if (int !== undefined) { const isInt = Number.isInteger(num); - valid = int ? isInt : !isInt; + if (!(int ? isInt : !isInt)) { + errors.push('int'); + } } + if (errors.length > 0) { + return { + ok: false, + errors, + }; + } return { - valid, - parsed: num, + ok: true, + value: num, }; }; const booleanValidator: ValueValidator = (input) => { const bool = input === 'true' ? true : input === 'false' ? false : undefined; + if (typeof bool !== 'boolean') { + return { + ok: false, + errors: ['type'], + }; + } return { - valid: typeof bool === 'boolean', - parsed: bool, + ok: true, + value: bool, }; }; const enumValidator = ({ values }: EnumSchema): ValueValidator => (input) => { + if (!(typeof input === 'string' ? values.includes(input) : false)) { + return { + ok: false, + errors: ['type'], + }; + } return { - valid: typeof input === 'string' ? values.includes(input) : false, - parsed: input, + ok: true, + value: input, }; }; @@ -131,29 +162,19 @@ export function validateEnvVariable( value: string | undefined, options: EnvFieldType ): ValidationResult { - const validator = selectValidator(options); - - const type = getEnvFieldType(options); - - if (options.optional || options.default !== undefined) { - if (value === undefined) { - return { - ok: true, - value: options.default, - type, - }; - } - } - const { valid, parsed } = validator(value); - if (valid) { + const isOptional = options.optional || options.default !== undefined; + if (isOptional && value === undefined) { return { ok: true, - value: parsed, - type, + value: options.default, }; } - return { - ok: false, - type, - }; + if (!isOptional && value === undefined) { + return { + ok: false, + errors: ['missing'], + }; + } + + return selectValidator(options)(value); } diff --git a/packages/astro/src/env/vite-plugin-env.ts b/packages/astro/src/env/vite-plugin-env.ts index 1bcb021e0f51..3f1ca2b6bde5 100644 --- a/packages/astro/src/env/vite-plugin-env.ts +++ b/packages/astro/src/env/vite-plugin-env.ts @@ -9,7 +9,7 @@ import { VIRTUAL_MODULES_IDS_VALUES, } from './constants.js'; import type { EnvSchema } from './schema.js'; -import { validateEnvVariable } from './validators.js'; +import { type ValidationResultErrors, getEnvFieldType, validateEnvVariable } from './validators.js'; // TODO: reminders for when astro:env comes out of experimental // Types should always be generated (like in types/content.d.ts). That means the client module will be empty @@ -105,7 +105,7 @@ function validatePublicVariables({ validateSecrets: boolean; }) { const valid: Array<{ key: string; value: any; type: string; context: 'server' | 'client' }> = []; - const invalid: Array<{ key: string; type: string }> = []; + const invalid: Array<{ key: string; type: string; errors: ValidationResultErrors }> = []; for (const [key, options] of Object.entries(schema)) { const variable = loadedEnv[key] === '' ? undefined : loadedEnv[key]; @@ -115,20 +115,30 @@ function validatePublicVariables({ } const result = validateEnvVariable(variable, options); + const type = getEnvFieldType(options); if (!result.ok) { - invalid.push({ key, type: result.type }); + invalid.push({ key, type, errors: result.errors }); // We don't do anything with validated secrets so we don't store them } else if (options.access === 'public') { - valid.push({ key, value: result.value, type: result.type, context: options.context }); + valid.push({ key, value: result.value, type, context: options.context }); } } if (invalid.length > 0) { + const _errors: Array = []; + for (const { key, type, errors } of invalid) { + if (errors[0] === 'missing') { + _errors.push(`${key} is missing`); + } else if (errors[0] === 'type') { + _errors.push(`${key}'s type is invalid, expected: ${type}`); + } else { + // constraints + _errors.push(`The following constraints for ${key} are not met: ${errors.join(', ')}`); + } + } throw new AstroError({ ...AstroErrorData.EnvInvalidVariables, - message: AstroErrorData.EnvInvalidVariables.message( - invalid.map(({ key, type }) => `Variable ${key} is not of type: ${type}.`).join('\n') - ), + message: AstroErrorData.EnvInvalidVariables.message(_errors), }); } diff --git a/packages/astro/src/integrations/hooks.ts b/packages/astro/src/integrations/hooks.ts index 0e58f7e8588f..48a9777e8c89 100644 --- a/packages/astro/src/integrations/hooks.ts +++ b/packages/astro/src/integrations/hooks.ts @@ -1,4 +1,4 @@ -import fs from 'node:fs'; +import fsMod from 'node:fs'; import type { AddressInfo } from 'node:net'; import { fileURLToPath } from 'node:url'; import { bold } from 'kleur/colors'; @@ -105,11 +105,13 @@ export async function runHookConfigSetup({ command, logger, isRestart = false, + fs = fsMod, }: { settings: AstroSettings; command: 'dev' | 'build' | 'preview'; logger: Logger; isRestart?: boolean; + fs?: typeof fsMod; }): Promise { // An adapter is an integration, so if one is provided push it. if (settings.config.adapter) { @@ -117,7 +119,7 @@ export async function runHookConfigSetup({ } if (settings.config.experimental?.actions) { const { default: actionsIntegration } = await import('../actions/index.js'); - settings.config.integrations.push(actionsIntegration()); + settings.config.integrations.push(actionsIntegration({ fs })); } let updatedConfig: AstroConfig = { ...settings.config }; @@ -532,7 +534,7 @@ export async function runHookBuildDone({ cacheManifest, }: RunHookBuildDone) { const dir = isServerLikeOutput(config) ? config.build.client : config.outDir; - await fs.promises.mkdir(dir, { recursive: true }); + await fsMod.promises.mkdir(dir, { recursive: true }); for (const integration of config.integrations) { if (integration?.hooks?.['astro:build:done']) { diff --git a/packages/astro/src/jsx-runtime/index.ts b/packages/astro/src/jsx-runtime/index.ts index b82fd97dc340..45093db14ef0 100644 --- a/packages/astro/src/jsx-runtime/index.ts +++ b/packages/astro/src/jsx-runtime/index.ts @@ -29,8 +29,7 @@ export function transformSlots(vnode: AstroVNode) { slots[name]['$$slot'] = true; delete child.props.slot; delete vnode.props.children; - } - if (Array.isArray(vnode.props.children)) { + } else if (Array.isArray(vnode.props.children)) { // Handle many children with slot attributes vnode.props.children = vnode.props.children .map((child) => { diff --git a/packages/astro/src/runtime/client/dev-toolbar/apps/audit/rules/a11y.ts b/packages/astro/src/runtime/client/dev-toolbar/apps/audit/rules/a11y.ts index 6d07f36e0ac7..64b13f67b172 100644 --- a/packages/astro/src/runtime/client/dev-toolbar/apps/audit/rules/a11y.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/apps/audit/rules/a11y.ts @@ -101,6 +101,7 @@ const aria_non_interactive_roles = [ 'rowheader', 'search', 'status', + 'tabpanel', 'term', 'timer', 'toolbar', @@ -503,7 +504,7 @@ export const a11y: AuditRuleWithSelector[] = [ description: 'The `tabindex` attribute should only be used on interactive elements, as it can be confusing for keyboard-only users to navigate through non-interactive elements. If your element is only conditionally interactive, consider using `tabindex="-1"` to make it focusable only when it is actually interactive.', message: (element) => `${element.localName} elements should not have \`tabindex\` attribute`, - selector: '[tabindex]', + selector: '[tabindex]:not([role="tabpanel"])', match(element) { // Scrollable elements are considered interactive // See: https://www.w3.org/WAI/standards-guidelines/act/rules/0ssw9k/proposed/ diff --git a/packages/astro/src/runtime/client/dev-toolbar/entrypoint.ts b/packages/astro/src/runtime/client/dev-toolbar/entrypoint.ts index 2558a4db2339..48cdac72f7af 100644 --- a/packages/astro/src/runtime/client/dev-toolbar/entrypoint.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/entrypoint.ts @@ -92,8 +92,9 @@ document.addEventListener('DOMContentLoaded', async () => { if (!(evt instanceof CustomEvent)) return; const target = overlay.shadowRoot?.querySelector(`[data-app-id="${app.id}"]`); - const notificationElement = target?.querySelector('.notification'); - if (!target || !notificationElement) return; + if (!target) return; + const notificationElement = target.querySelector('.notification'); + if (!notificationElement) return; let newState = evt.detail.state ?? true; let level = notificationLevels.includes(evt?.detail?.level) diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts index 64caf04f0940..36449ea24359 100644 --- a/packages/astro/src/vite-plugin-astro-server/route.ts +++ b/packages/astro/src/vite-plugin-astro-server/route.ts @@ -3,6 +3,7 @@ import type { ComponentInstance, ManifestData, RouteData } from '../@types/astro import { DEFAULT_404_COMPONENT, REROUTE_DIRECTIVE_HEADER, + REWRITE_DIRECTIVE_HEADER_KEY, clientLocalsSymbol, } from '../core/constants.js'; import { AstroErrorData, isAstroError } from '../core/errors/index.js'; @@ -218,8 +219,12 @@ export async function handleRoute({ }); let response; + let isReroute = false; + let isRewrite = false; try { response = await renderContext.render(mod); + isReroute = response.headers.has(REROUTE_DIRECTIVE_HEADER); + isRewrite = response.headers.has(REWRITE_DIRECTIVE_HEADER_KEY); } catch (err: any) { const custom500 = getCustom500Route(manifestData); if (!custom500) { @@ -264,7 +269,10 @@ export async function handleRoute({ } // We remove the internally-used header before we send the response to the user agent. - if (response.headers.has(REROUTE_DIRECTIVE_HEADER)) { + if (isReroute) { + response.headers.delete(REROUTE_DIRECTIVE_HEADER); + } + if (isRewrite) { response.headers.delete(REROUTE_DIRECTIVE_HEADER); } @@ -272,6 +280,14 @@ export async function handleRoute({ await writeWebResponse(incomingResponse, response); return; } + + // This check is important in case of rewrites. + // A route can start with a 404 code, then the rewrite kicks in and can return a 200 status code + if (isRewrite) { + await writeSSRResult(request, response, incomingResponse); + return; + } + // We are in a recursion, and it's possible that this function is called itself with a status code // By default, the status code passed via parameters is computed by the matched route. // diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index 6c0f76b0ffe5..cedf49e95bce 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -9,7 +9,7 @@ import type { } from './types.js'; import { normalizePath } from 'vite'; -import { normalizeFilename } from '../vite-plugin-utils/index.js'; +import { hasSpecialQueries, normalizeFilename } from '../vite-plugin-utils/index.js'; import { type CompileAstroResult, compileAstro } from './compile.js'; import { handleHotUpdate } from './hmr.js'; import { parseAstroRequest } from './query.js'; @@ -200,6 +200,8 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl } }, async transform(source, id) { + if (hasSpecialQueries(id)) return; + const parsedId = parseAstroRequest(id); // ignore astro file sub-requests, e.g. Foo.astro?astro&type=script&index=0&lang.ts if (!parsedId.filename.endsWith('.astro') || parsedId.query.astro) { diff --git a/packages/astro/src/vite-plugin-utils/index.ts b/packages/astro/src/vite-plugin-utils/index.ts index 74f60305d59a..74167a36a9cf 100644 --- a/packages/astro/src/vite-plugin-utils/index.ts +++ b/packages/astro/src/vite-plugin-utils/index.ts @@ -50,3 +50,12 @@ const postfixRE = /[?#].*$/s; export function cleanUrl(url: string): string { return url.replace(postfixRE, ''); } + +const specialQueriesRE = /(?:\?|&)(?:url|raw|direct)(?:&|$)/; +/** + * Detect `?url`, `?raw`, and `?direct`, in which case we usually want to skip + * transforming any code with this queries as Vite will handle it directly. + */ +export function hasSpecialQueries(id: string): boolean { + return specialQueriesRE.test(id); +} diff --git a/packages/astro/test/astro-basic.test.js b/packages/astro/test/astro-basic.test.js index cb7c06ce0efd..957be04fb6f2 100644 --- a/packages/astro/test/astro-basic.test.js +++ b/packages/astro/test/astro-basic.test.js @@ -159,6 +159,12 @@ describe('Astro basic build', () => { assert.equal($('h1').text(), 'WORKS'); }); + it('Handles importing .astro?raw correctly', async () => { + const html = await fixture.readFile('/import-queries/raw/index.html'); + const $ = cheerio.load(html); + assert.equal($('.raw-value').text(), '

Hello

\n'); + }); + describe('preview', () => { it('returns 200 for valid URLs', async () => { const result = await fixture.fetch('/'); @@ -211,4 +217,12 @@ describe('Astro basic development', () => { html.includes(''); assert.ok(isUtf8); }); + + it('Handles importing .astro?raw correctly', async () => { + const res = await fixture.fetch('/import-queries/raw/index.html'); + assert.equal(res.status, 200); + const html = await res.text(); + const $ = cheerio.load(html); + assert.equal($('.raw-value').text(), '

Hello

\n'); + }); }); diff --git a/packages/astro/test/astro-markdown-plugins.test.js b/packages/astro/test/astro-markdown-plugins.test.js index 1ea2afd8ef88..09cb76d2decc 100644 --- a/packages/astro/test/astro-markdown-plugins.test.js +++ b/packages/astro/test/astro-markdown-plugins.test.js @@ -60,7 +60,7 @@ describe('Astro Markdown plugins', () => { const smartypantsHtml = await fixture.readFile('/with-smartypants/index.html'); const $2 = cheerio.load(smartypantsHtml); - assert.equal($2('p').html(), '”Smartypants” is — awesome'); + assert.equal($2('p').html(), '“Smartypants” is — awesome'); testRemark(gfmHtml); testRehype(gfmHtml, '#github-flavored-markdown-test'); @@ -82,7 +82,7 @@ describe('Astro Markdown plugins', () => { const $ = cheerio.load(html); // test 1: smartypants applied correctly - assert.equal($('p').html(), '”Smartypants” is — awesome'); + assert.equal($('p').html(), '“Smartypants” is — awesome'); testRemark(html); testRehype(html, '#smartypants-test'); diff --git a/packages/astro/test/astro-markdown-shiki.test.js b/packages/astro/test/astro-markdown-shiki.test.js index 24ab7d2b3026..3e0fa9734a78 100644 --- a/packages/astro/test/astro-markdown-shiki.test.js +++ b/packages/astro/test/astro-markdown-shiki.test.js @@ -78,6 +78,22 @@ describe('Astro Markdown Shiki', () => { ); }); }); + + describe('Default color', async () => { + let fixture; + + before(async () => { + fixture = await loadFixture({ root: './fixtures/astro-markdown-shiki/default-color/' }); + await fixture.build(); + }); + + it('Renders default color without themes', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + + assert.doesNotMatch($('pre').attr().style, /background-color/); + }); + }); }); describe('Languages', () => { diff --git a/packages/astro/test/astro-sync.test.js b/packages/astro/test/astro-sync.test.js index 11152f77b2d8..f6faa6722551 100644 --- a/packages/astro/test/astro-sync.test.js +++ b/packages/astro/test/astro-sync.test.js @@ -1,6 +1,7 @@ import assert from 'node:assert/strict'; import * as fs from 'node:fs'; import { before, describe, it } from 'node:test'; +import { fileURLToPath } from 'node:url'; import ts from 'typescript'; import { loadFixture } from './test-utils.js'; @@ -47,10 +48,10 @@ const createFixture = () => { }, }; - const code = await astroFixture.sync({}, { fs: fsMock }); - if (code !== 0) { - throw new Error(`Process error code ${code}`); - } + await astroFixture.sync({ + inlineConfig: { root: fileURLToPath(new URL(root, import.meta.url)) }, + fs: fsMock, + }); }, /** @param {string} path */ thenFileShouldExist(path) { diff --git a/packages/astro/test/core-image-infersize.test.js b/packages/astro/test/core-image-infersize.test.js index 9abf24b1f79e..355d6a6911f2 100644 --- a/packages/astro/test/core-image-infersize.test.js +++ b/packages/astro/test/core-image-infersize.test.js @@ -70,6 +70,11 @@ describe('astro:image:infersize', () => { true ); }); + + it('direct function call work', async () => { + let $dimensions = $('#direct'); + assert.equal($dimensions.text().trim(), '64x64'); + }); }); }); }); diff --git a/packages/astro/test/env-secret.test.js b/packages/astro/test/env-secret.test.js index 4505254a6b62..7a569e35a524 100644 --- a/packages/astro/test/env-secret.test.js +++ b/packages/astro/test/env-secret.test.js @@ -84,7 +84,7 @@ describe('astro:env secret variables', () => { } catch (error) { assert.equal(error instanceof Error, true); assert.equal(error.title, 'Invalid Environment Variables'); - assert.equal(error.message.includes('Variable KNOWN_SECRET is not of type: number.'), true); + assert.equal(error.message.includes('KNOWN_SECRET is missing'), true); } }); }); diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/import-queries/_content.astro b/packages/astro/test/fixtures/astro-basic/src/pages/import-queries/_content.astro new file mode 100644 index 000000000000..986a4a1a25bc --- /dev/null +++ b/packages/astro/test/fixtures/astro-basic/src/pages/import-queries/_content.astro @@ -0,0 +1 @@ +

Hello

diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/import-queries/raw.astro b/packages/astro/test/fixtures/astro-basic/src/pages/import-queries/raw.astro new file mode 100644 index 000000000000..8b88cbe10d10 --- /dev/null +++ b/packages/astro/test/fixtures/astro-basic/src/pages/import-queries/raw.astro @@ -0,0 +1,5 @@ +--- +import contentStr from './_content.astro?raw'; +--- + +
{contentStr}
diff --git a/packages/astro/test/fixtures/astro-markdown-shiki/default-color/astro.config.mjs b/packages/astro/test/fixtures/astro-markdown-shiki/default-color/astro.config.mjs new file mode 100644 index 000000000000..815e56cf388c --- /dev/null +++ b/packages/astro/test/fixtures/astro-markdown-shiki/default-color/astro.config.mjs @@ -0,0 +1,13 @@ +export default { + markdown: { + syntaxHighlight: 'shiki', + shikiConfig: { + theme: 'github-light', + themes: { + light: 'github-light', + dark: 'github-light' + }, + defaultColor: false + }, + }, +} diff --git a/packages/astro/test/fixtures/astro-markdown-shiki/default-color/package.json b/packages/astro/test/fixtures/astro-markdown-shiki/default-color/package.json new file mode 100644 index 000000000000..12460ffa73c2 --- /dev/null +++ b/packages/astro/test/fixtures/astro-markdown-shiki/default-color/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/astro-markdown-skiki-default-color", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/astro-markdown-shiki/default-color/src/layouts/content.astro b/packages/astro/test/fixtures/astro-markdown-shiki/default-color/src/layouts/content.astro new file mode 100644 index 000000000000..925a243a9368 --- /dev/null +++ b/packages/astro/test/fixtures/astro-markdown-shiki/default-color/src/layouts/content.astro @@ -0,0 +1,10 @@ + + + + + +
+ +
+ + diff --git a/packages/astro/test/fixtures/astro-markdown-shiki/default-color/src/pages/index.md b/packages/astro/test/fixtures/astro-markdown-shiki/default-color/src/pages/index.md new file mode 100644 index 000000000000..a75170537cce --- /dev/null +++ b/packages/astro/test/fixtures/astro-markdown-shiki/default-color/src/pages/index.md @@ -0,0 +1,24 @@ +--- +layout: ../layouts/content.astro +--- + +# Hello world + +```yaml +apiVersion: v3 +kind: Pod +metadata: + name: rss-site + labels: + app: web +spec: + containers: + - name: front-end + image: nginx + ports: + - containerPort: 80 + - name: rss-reader + image: nickchase/rss-php-nginx:v1 + ports: + - containerPort: 88 +``` diff --git a/packages/astro/test/fixtures/core-image-infersize/src/pages/index.astro b/packages/astro/test/fixtures/core-image-infersize/src/pages/index.astro index 947c9a4f6bcd..ef7bf57c012d 100644 --- a/packages/astro/test/fixtures/core-image-infersize/src/pages/index.astro +++ b/packages/astro/test/fixtures/core-image-infersize/src/pages/index.astro @@ -1,6 +1,9 @@ --- // https://avatars.githubusercontent.com/u/622227?s=64 is a .jpeg -import { Image, Picture, getImage } from 'astro:assets'; +import { Image, Picture, getImage, inferRemoteSize } from 'astro:assets'; + +const { width, height } = await inferRemoteSize('https://avatars.githubusercontent.com/u/622227?s=64'); + const remoteImg = await getImage({ src: 'https://avatars.githubusercontent.com/u/622227?s=64', inferSize: true, @@ -10,3 +13,7 @@ const remoteImg = await getImage({ + +
+ {width}x{height} +
diff --git a/packages/astro/test/fixtures/postcss/package.json b/packages/astro/test/fixtures/postcss/package.json index 2afde7679b3f..783dc24380b8 100644 --- a/packages/astro/test/fixtures/postcss/package.json +++ b/packages/astro/test/fixtures/postcss/package.json @@ -14,6 +14,6 @@ "vue": "^3.4.31" }, "devDependencies": { - "postcss-preset-env": "^9.5.15" + "postcss-preset-env": "^9.6.0" } } diff --git a/packages/astro/test/fixtures/preact-component/package.json b/packages/astro/test/fixtures/preact-component/package.json index 341752468642..f0c105e1c184 100644 --- a/packages/astro/test/fixtures/preact-component/package.json +++ b/packages/astro/test/fixtures/preact-component/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "@astrojs/preact": "workspace:*", - "@preact/signals": "1.2.3", + "@preact/signals": "1.3.0", "astro": "workspace:*", "preact": "^10.22.1" } diff --git a/packages/astro/test/fixtures/solid-component/package.json b/packages/astro/test/fixtures/solid-component/package.json index bc58b8472526..aff7c7e7b42a 100644 --- a/packages/astro/test/fixtures/solid-component/package.json +++ b/packages/astro/test/fixtures/solid-component/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "@astrojs/solid-js": "workspace:*", - "@solidjs/router": "^0.13.6", + "@solidjs/router": "^0.14.1", "@test/solid-jsx-component": "file:./deps/solid-jsx-component", "astro": "workspace:*", "solid-js": "^1.8.18" diff --git a/packages/astro/test/fixtures/ssr-prerender-chunks/src/pages/index.astro b/packages/astro/test/fixtures/ssr-prerender-chunks/src/pages/index.astro index 21a503211bc0..05ac05b680cd 100644 --- a/packages/astro/test/fixtures/ssr-prerender-chunks/src/pages/index.astro +++ b/packages/astro/test/fixtures/ssr-prerender-chunks/src/pages/index.astro @@ -5,7 +5,7 @@ - Static Page + Static Page should not exist in chunks diff --git a/packages/astro/test/fixtures/tailwindcss-ts/package.json b/packages/astro/test/fixtures/tailwindcss-ts/package.json index 20970d06f1fc..772a7bf98f4c 100644 --- a/packages/astro/test/fixtures/tailwindcss-ts/package.json +++ b/packages/astro/test/fixtures/tailwindcss-ts/package.json @@ -6,6 +6,6 @@ "@astrojs/tailwind": "workspace:*", "astro": "workspace:*", "postcss": "^8.4.39", - "tailwindcss": "^3.4.4" + "tailwindcss": "^3.4.5" } } diff --git a/packages/astro/test/fixtures/tailwindcss/package.json b/packages/astro/test/fixtures/tailwindcss/package.json index cc9b1d82d001..2886bdf20d75 100644 --- a/packages/astro/test/fixtures/tailwindcss/package.json +++ b/packages/astro/test/fixtures/tailwindcss/package.json @@ -8,6 +8,6 @@ "astro": "workspace:*", "autoprefixer": "^10.4.19", "postcss": "^8.4.39", - "tailwindcss": "^3.4.4" + "tailwindcss": "^3.4.5" } } diff --git a/packages/astro/test/rewrite.test.js b/packages/astro/test/rewrite.test.js index d14fb069a7c7..7839e7d3350b 100644 --- a/packages/astro/test/rewrite.test.js +++ b/packages/astro/test/rewrite.test.js @@ -520,6 +520,34 @@ describe('Runtime error in SSR, custom 500', () => { }); }); +describe('Runtime error in dev, custom 500', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + let devServer; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/rewrite-i18n-manual-routing/', + }); + + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('should return a status 200 when rewriting from the middleware to the homepage', async () => { + const response = await fixture.fetch('/reroute'); + assert.equal(response.status, 200); + const html = await response.text(); + + const $ = cheerioLoad(html); + + assert.equal($('h1').text(), 'Expected http status of index page is 200'); + }); +}); + describe('Runtime error in SSR, custom 500', () => { /** @type {import('./test-utils').Fixture} */ let fixture; diff --git a/packages/astro/test/ssr-prerender-chunks.test.js b/packages/astro/test/ssr-prerender-chunks.test.js index 7bd916814938..86c906288628 100644 --- a/packages/astro/test/ssr-prerender-chunks.test.js +++ b/packages/astro/test/ssr-prerender-chunks.test.js @@ -18,4 +18,15 @@ describe('Chunks', () => { const hasImportFromPrerender = !content.includes(`React } from './chunks/prerender`); assert.ok(hasImportFromPrerender); }); + + it('does not have prerender code', async () => { + const files = await fixture.readdir('/_worker.js/chunks'); + assert.ok(files.length > 0); + for (const file of files) { + // Skip astro folder + if (file === 'astro') continue; + const content = await fixture.readFile(`/_worker.js/chunks/${file}`); + assert.doesNotMatch(content, /Static Page should not exist in chunks/); + } + }); }); diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index 18ef7966077f..acb2f1e5cba0 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -161,9 +161,7 @@ export async function loadFixture(inlineConfig) { process.env.NODE_ENV = 'production'; return build(mergeConfig(inlineConfig, extraInlineConfig), { teardownCompiler: false }); }, - sync: async (extraInlineConfig = {}, opts) => { - return sync(mergeConfig(inlineConfig, extraInlineConfig), opts); - }, + sync, check: async (opts) => { return await check(opts); }, diff --git a/packages/astro/test/units/dev/collections-mixed-content-errors.test.js b/packages/astro/test/units/dev/collections-mixed-content-errors.test.js index 0086b51e83ac..d63e42d53323 100644 --- a/packages/astro/test/units/dev/collections-mixed-content-errors.test.js +++ b/packages/astro/test/units/dev/collections-mixed-content-errors.test.js @@ -6,8 +6,19 @@ import { createFsWithFallback } from '../test-utils.js'; const root = new URL('../../fixtures/content-mixed-errors/', import.meta.url); -async function sync({ fs, config = {} }) { - return _sync({ ...config, root: fileURLToPath(root), logLevel: 'silent' }, { fs }); +async function sync({ fs }) { + try { + await _sync({ + inlineConfig: { + root: fileURLToPath(root), + logLevel: 'silent', + }, + fs, + }); + return 0; + } catch (_) { + return 1; + } } describe('Content Collections - mixed content errors', () => { @@ -114,7 +125,7 @@ title: Post const res = await sync({ fs }); assert.equal(res, 0); } catch (e) { - expect.fail(0, 1, `Did not expect sync to throw: ${e.message}`); + assert.fail(`Did not expect sync to throw: ${e.message}`); } }); }); diff --git a/packages/astro/test/units/env/env-validators.test.js b/packages/astro/test/units/env/env-validators.test.js index 468c86d8ef81..890123d2476c 100644 --- a/packages/astro/test/units/env/env-validators.test.js +++ b/packages/astro/test/units/env/env-validators.test.js @@ -29,9 +29,17 @@ const createFixture = () => { assert.equal(result.value, value); input = undefined; }, - thenResultShouldBeInvalid() { + /** + * @param {string | Array} providedErrors + */ + thenResultShouldBeInvalid(providedErrors) { const result = validateEnvVariable(input.value, input.options); assert.equal(result.ok, false); + const errors = typeof providedErrors === 'string' ? [providedErrors] : providedErrors; + assert.equal( + result.errors.every((element) => errors.includes(element)), + true + ); input = undefined; }, }; @@ -158,7 +166,7 @@ describe('astro:env validators', () => { fixture.givenInput(undefined, { type: 'string', }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('missing'); }); it('Should not fail is the variable type is incorrect', () => { @@ -179,7 +187,7 @@ describe('astro:env validators', () => { type: 'string', max: 3, }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('max'); fixture.givenInput('abc', { type: 'string', @@ -191,7 +199,7 @@ describe('astro:env validators', () => { type: 'string', min: 5, }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('min'); fixture.givenInput('abc', { type: 'string', @@ -203,13 +211,13 @@ describe('astro:env validators', () => { type: 'string', length: 10, }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('length'); fixture.givenInput('abc', { type: 'string', url: true, }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('url'); fixture.givenInput('https://example.com', { type: 'string', @@ -221,7 +229,7 @@ describe('astro:env validators', () => { type: 'string', includes: 'cd', }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('includes'); fixture.givenInput('abc', { type: 'string', @@ -233,7 +241,7 @@ describe('astro:env validators', () => { type: 'string', startsWith: 'za', }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('startsWith'); fixture.givenInput('abc', { type: 'string', @@ -245,7 +253,7 @@ describe('astro:env validators', () => { type: 'string', endsWith: 'za', }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('endsWith'); fixture.givenInput('abc', { type: 'string', @@ -264,7 +272,14 @@ describe('astro:env validators', () => { type: 'string', min: 5, }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('missing'); + + fixture.givenInput('ab', { + type: 'string', + startsWith: 'x', + min: 5, + }); + fixture.thenResultShouldBeInvalid(['startsWith', 'min']); }); it('Should not fail if the optional variable is missing', () => { @@ -297,14 +312,14 @@ describe('astro:env validators', () => { fixture.givenInput(undefined, { type: 'number', }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('missing'); }); it('Should fail is the variable type is incorrect', () => { fixture.givenInput('abc', { type: 'number', }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('type'); }); it('Should fail if conditions are not met', () => { @@ -312,13 +327,13 @@ describe('astro:env validators', () => { type: 'number', gt: 15, }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('gt'); fixture.givenInput('10', { type: 'number', gt: 10, }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('gt'); fixture.givenInput('10', { type: 'number', @@ -330,7 +345,7 @@ describe('astro:env validators', () => { type: 'number', min: 25, }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('min'); fixture.givenInput('20', { type: 'number', @@ -348,13 +363,13 @@ describe('astro:env validators', () => { type: 'number', lt: 10, }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('lt'); fixture.givenInput('10', { type: 'number', lt: 10, }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('lt'); fixture.givenInput('5', { type: 'number', @@ -366,7 +381,7 @@ describe('astro:env validators', () => { type: 'number', max: 20, }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('max'); fixture.givenInput('25', { type: 'number', @@ -384,7 +399,7 @@ describe('astro:env validators', () => { type: 'number', int: true, }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('int'); fixture.givenInput('25', { type: 'number', @@ -396,7 +411,7 @@ describe('astro:env validators', () => { type: 'number', int: false, }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('int'); fixture.givenInput('4.5', { type: 'number', @@ -408,7 +423,7 @@ describe('astro:env validators', () => { type: 'number', gt: 10, }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('missing'); }); it('Should accept integers', () => { @@ -455,14 +470,14 @@ describe('astro:env validators', () => { fixture.givenInput(undefined, { type: 'boolean', }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('missing'); }); it('Should fail is the variable type is incorrect', () => { fixture.givenInput('abc', { type: 'boolean', }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('type'); }); it('Should not fail if the optional variable is missing', () => { @@ -496,7 +511,7 @@ describe('astro:env validators', () => { type: 'enum', values: ['a', 'b'], }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('missing'); }); it('Should fail is the variable type is incorrect', () => { @@ -504,7 +519,7 @@ describe('astro:env validators', () => { type: 'enum', values: ['a', 'b'], }); - fixture.thenResultShouldBeInvalid(); + fixture.thenResultShouldBeInvalid('type'); }); it('Should not fail if the optional variable is missing', () => { diff --git a/packages/db/package.json b/packages/db/package.json index a1ec3c8f3f3a..c4508b0f72c5 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -94,7 +94,7 @@ "astro": "workspace:*", "astro-scripts": "workspace:*", "cheerio": "1.0.0-rc.12", - "typescript": "^5.5.2", - "vite": "^5.3.2" + "typescript": "^5.5.3", + "vite": "^5.3.4" } } diff --git a/packages/db/src/core/load-file.ts b/packages/db/src/core/load-file.ts index dd48df928cbd..cbdb6d2436ed 100644 --- a/packages/db/src/core/load-file.ts +++ b/packages/db/src/core/load-file.ts @@ -2,19 +2,16 @@ import { existsSync } from 'node:fs'; import { unlink, writeFile } from 'node:fs/promises'; import { createRequire } from 'node:module'; import { fileURLToPath, pathToFileURL } from 'node:url'; -import type { AstroConfig, AstroIntegration } from 'astro'; +import type { AstroConfig } from 'astro'; import { build as esbuild } from 'esbuild'; import { CONFIG_FILE_NAMES, VIRTUAL_MODULE_ID } from './consts.js'; import { INTEGRATION_TABLE_CONFLICT_ERROR } from './errors.js'; import { errorMap } from './integration/error-map.js'; import { getConfigVirtualModContents } from './integration/vite-plugin-db.js'; import { dbConfigSchema } from './schemas.js'; -import { type AstroDbIntegration } from './types.js'; +import './types.js'; import { getAstroEnv, getDbDirectoryUrl } from './utils.js'; -const isDbIntegration = (integration: AstroIntegration): integration is AstroDbIntegration => - 'astro:db:setup' in integration.hooks; - /** * Load a user’s `astro:db` configuration file and additional configuration files provided by integrations. */ @@ -31,7 +28,6 @@ export async function resolveDbConfig({ const integrationDbConfigPaths: Array<{ name: string; configEntrypoint: string | URL }> = []; const integrationSeedPaths: Array = []; for (const integration of integrations) { - if (!isDbIntegration(integration)) continue; const { name, hooks } = integration; if (hooks['astro:db:setup']) { hooks['astro:db:setup']({ diff --git a/packages/db/src/core/types.ts b/packages/db/src/core/types.ts index 79bbdf371927..5efc6507c828 100644 --- a/packages/db/src/core/types.ts +++ b/packages/db/src/core/types.ts @@ -1,4 +1,3 @@ -import type { AstroIntegration } from 'astro'; import type { z } from 'zod'; import type { MaybeArray, @@ -88,13 +87,16 @@ interface LegacyIndexConfig export type NumberColumnOpts = z.input; export type TextColumnOpts = z.input; -export type AstroDbIntegration = AstroIntegration & { - hooks: { - 'astro:db:setup'?: (options: { - extendDb: (options: { - configEntrypoint?: URL | string; - seedEntrypoint?: URL | string; - }) => void; - }) => void | Promise; - }; -}; +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Astro { + export interface IntegrationHooks { + 'astro:db:setup'?: (options: { + extendDb: (options: { + configEntrypoint?: URL | string; + seedEntrypoint?: URL | string; + }) => void; + }) => void | Promise; + } + } +} diff --git a/packages/db/src/core/utils.ts b/packages/db/src/core/utils.ts index 4fef5fbe18b1..c1cf5657940f 100644 --- a/packages/db/src/core/utils.ts +++ b/packages/db/src/core/utils.ts @@ -1,7 +1,7 @@ import { getAstroStudioEnv } from '@astrojs/studio'; import type { AstroConfig, AstroIntegration } from 'astro'; import { loadEnv } from 'vite'; -import type { AstroDbIntegration } from './types.js'; +import './types.js'; export type VitePlugin = Required['plugins'][number]; @@ -19,7 +19,7 @@ export function getDbDirectoryUrl(root: URL | string) { return new URL('db/', root); } -export function defineDbIntegration(integration: AstroDbIntegration): AstroIntegration { +export function defineDbIntegration(integration: AstroIntegration): AstroIntegration { return integration; } diff --git a/packages/db/test/fixtures/ticketing-example/package.json b/packages/db/test/fixtures/ticketing-example/package.json index e4771893061d..3f6e5510d9a6 100644 --- a/packages/db/test/fixtures/ticketing-example/package.json +++ b/packages/db/test/fixtures/ticketing-example/package.json @@ -10,18 +10,18 @@ "astro": "astro" }, "dependencies": { - "@astrojs/check": "^0.7.0", + "@astrojs/check": "^0.8.1", "@astrojs/db": "workspace:*", "@astrojs/node": "workspace:*", "@astrojs/react": "^3.6.0", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "astro": "workspace:*", - "open-props": "^1.7.4", + "open-props": "^1.7.5", "react": "^18.3.1", "react-dom": "^18.3.1", "simple-stack-form": "^0.1.12", - "typescript": "^5.5.2", + "typescript": "^5.5.3", "zod": "^3.23.8" } } diff --git a/packages/integrations/alpinejs/package.json b/packages/integrations/alpinejs/package.json index 3d3338aace12..b7699c22d55b 100644 --- a/packages/integrations/alpinejs/package.json +++ b/packages/integrations/alpinejs/package.json @@ -38,10 +38,10 @@ "alpinejs": "^3.0.0" }, "devDependencies": { - "@playwright/test": "1.45.0", + "@playwright/test": "1.45.2", "astro": "workspace:*", "astro-scripts": "workspace:*", - "vite": "^5.3.2" + "vite": "^5.3.4" }, "publishConfig": { "provenance": true diff --git a/packages/integrations/lit/package.json b/packages/integrations/lit/package.json index 2c5efcd9ffdf..a5c56370a48b 100644 --- a/packages/integrations/lit/package.json +++ b/packages/integrations/lit/package.json @@ -61,7 +61,7 @@ "astro-scripts": "workspace:*", "cheerio": "1.0.0-rc.12", "lit": "^3.1.4", - "sass": "^1.77.6" + "sass": "^1.77.8" }, "peerDependencies": { "@webcomponents/template-shadowroot": "^0.2.1", diff --git a/packages/integrations/markdoc/CHANGELOG.md b/packages/integrations/markdoc/CHANGELOG.md index 8548d7faa8ef..db4667b09e31 100644 --- a/packages/integrations/markdoc/CHANGELOG.md +++ b/packages/integrations/markdoc/CHANGELOG.md @@ -1,5 +1,11 @@ # @astrojs/markdoc +## 0.11.2 + +### Patch Changes + +- [#11450](https://github.com/withastro/astro/pull/11450) [`eb303e1`](https://github.com/withastro/astro/commit/eb303e1ad5dade7787c0d9bbb520c21292cf3950) Thanks [@schpet](https://github.com/schpet)! - Adds support for markdown-it's typographer option + ## 0.11.1 ### Patch Changes diff --git a/packages/integrations/markdoc/package.json b/packages/integrations/markdoc/package.json index 599d3cbc12d3..994f92983250 100644 --- a/packages/integrations/markdoc/package.json +++ b/packages/integrations/markdoc/package.json @@ -1,7 +1,7 @@ { "name": "@astrojs/markdoc", "description": "Add support for Markdoc in your Astro site", - "version": "0.11.1", + "version": "0.11.2", "type": "module", "types": "./dist/index.d.ts", "author": "withastro", @@ -83,7 +83,7 @@ "astro-scripts": "workspace:*", "devalue": "^5.0.0", "linkedom": "^0.18.4", - "vite": "^5.3.2" + "vite": "^5.3.4" }, "engines": { "node": "^18.17.1 || ^20.3.0 || >=21.0.0" diff --git a/packages/integrations/markdoc/src/options.ts b/packages/integrations/markdoc/src/options.ts index 450285bcf718..abaeb5a964e5 100644 --- a/packages/integrations/markdoc/src/options.ts +++ b/packages/integrations/markdoc/src/options.ts @@ -1,4 +1,5 @@ export interface MarkdocIntegrationOptions { allowHTML?: boolean; ignoreIndentation?: boolean; + typographer?: boolean; } diff --git a/packages/integrations/markdoc/src/tokenizer.ts b/packages/integrations/markdoc/src/tokenizer.ts index 79d0d7358beb..1f5b1de28165 100644 --- a/packages/integrations/markdoc/src/tokenizer.ts +++ b/packages/integrations/markdoc/src/tokenizer.ts @@ -24,6 +24,10 @@ export function getMarkdocTokenizer(options: MarkdocIntegrationOptions | undefin // allow indentation so nested Markdoc tags can be formatted for better readability tokenizerOptions.allowIndentation = true; } + if (options?.typographer) { + // enable typographer to convert straight quotes to curly quotes, etc. + tokenizerOptions.typographer = options.typographer; + } _cachedMarkdocTokenizers[key] = new Markdoc.Tokenizer(tokenizerOptions); } diff --git a/packages/integrations/markdoc/test/fixtures/render-typographer/astro.config.mjs b/packages/integrations/markdoc/test/fixtures/render-typographer/astro.config.mjs new file mode 100644 index 000000000000..408e036c7aca --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-typographer/astro.config.mjs @@ -0,0 +1,7 @@ +import markdoc from '@astrojs/markdoc'; +import { defineConfig } from 'astro/config'; + +// https://astro.build/config +export default defineConfig({ + integrations: [markdoc({ typographer: true })], +}); diff --git a/packages/integrations/markdoc/test/fixtures/render-typographer/package.json b/packages/integrations/markdoc/test/fixtures/render-typographer/package.json new file mode 100644 index 000000000000..02fd6788f310 --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-typographer/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/markdoc-render-typographer", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/markdoc": "workspace:*", + "astro": "workspace:*" + } +} diff --git a/packages/integrations/markdoc/test/fixtures/render-typographer/src/content/blog/typographer.mdoc b/packages/integrations/markdoc/test/fixtures/render-typographer/src/content/blog/typographer.mdoc new file mode 100644 index 000000000000..2180e7a47b1f --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-typographer/src/content/blog/typographer.mdoc @@ -0,0 +1,7 @@ +--- +title: Typographer +--- + +## Typographer's post + +This is a post to test the "typographer" option. diff --git a/packages/integrations/markdoc/test/fixtures/render-typographer/src/pages/index.astro b/packages/integrations/markdoc/test/fixtures/render-typographer/src/pages/index.astro new file mode 100644 index 000000000000..88fc531fa2e1 --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-typographer/src/pages/index.astro @@ -0,0 +1,19 @@ +--- +import { getEntryBySlug } from "astro:content"; + +const post = await getEntryBySlug('blog', 'typographer'); +const { Content } = await post.render(); +--- + + + + + + + + Content + + + + + diff --git a/packages/integrations/markdoc/test/render.test.js b/packages/integrations/markdoc/test/render.test.js index d439adcd2b8e..364604405d56 100644 --- a/packages/integrations/markdoc/test/render.test.js +++ b/packages/integrations/markdoc/test/render.test.js @@ -117,6 +117,15 @@ describe('Markdoc - render', () => { renderWithRootFolderContainingSpace(html); }); + + it('renders content - with typographer option', async () => { + const fixture = await getFixture('render-typographer'); + await fixture.build(); + + const html = await fixture.readFile('/index.html'); + + renderTypographerChecks(html); + }); }); }); @@ -173,3 +182,16 @@ function renderWithRootFolderContainingSpace(html) { const p = document.querySelector('p'); assert.equal(p.textContent, 'This is a simple Markdoc post with root folder containing a space.'); } + +/** + * @param {string} html + */ +function renderTypographerChecks(html) { + const { document } = parseHTML(html); + + const h2 = document.querySelector('h2'); + assert.equal(h2.textContent, 'Typographer’s post'); + + const p = document.querySelector('p'); + assert.equal(p.textContent, 'This is a post to test the “typographer” option.'); +} diff --git a/packages/integrations/mdx/package.json b/packages/integrations/mdx/package.json index 43d735f13edd..0ef90280c389 100644 --- a/packages/integrations/mdx/package.json +++ b/packages/integrations/mdx/package.json @@ -35,7 +35,7 @@ "dependencies": { "@astrojs/markdown-remark": "workspace:*", "@mdx-js/mdx": "^3.0.1", - "acorn": "^8.12.0", + "acorn": "^8.12.1", "es-module-lexer": "^1.5.4", "estree-util-visit": "^2.0.0", "github-slugger": "^2.0.0", @@ -44,10 +44,10 @@ "kleur": "^4.1.5", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", - "remark-smartypants": "^3.0.1", + "remark-smartypants": "^3.0.2", "source-map": "^0.7.4", "unist-util-visit": "^5.0.0", - "vfile": "^6.0.1" + "vfile": "^6.0.2" }, "peerDependencies": { "astro": "^4.8.0" @@ -72,7 +72,7 @@ "remark-shiki-twoslash": "^3.1.3", "remark-toc": "^9.0.0", "unified": "^11.0.5", - "vite": "^5.3.2" + "vite": "^5.3.4" }, "engines": { "node": "^18.17.1 || ^20.3.0 || >=21.0.0" diff --git a/packages/integrations/mdx/test/mdx-plugins.test.js b/packages/integrations/mdx/test/mdx-plugins.test.js index 6bc8e096c268..124ec52c14ec 100644 --- a/packages/integrations/mdx/test/mdx-plugins.test.js +++ b/packages/integrations/mdx/test/mdx-plugins.test.js @@ -47,7 +47,7 @@ describe('MDX plugins', () => { const quote = selectSmartypantsQuote(document); assert.notEqual(quote, null); - assert.equal(quote.textContent.includes('”Smartypants” is — awesome'), true); + assert.equal(quote.textContent.includes('“Smartypants” is — awesome'), true); }); it('supports custom rehype plugins', async () => { @@ -198,7 +198,7 @@ describe('MDX plugins', () => { ); } else { assert.equal( - quote.textContent.includes('”Smartypants” is — awesome'), + quote.textContent.includes('“Smartypants” is — awesome'), true, 'Respects `markdown.smartypants` unexpectedly.' ); diff --git a/packages/integrations/preact/CHANGELOG.md b/packages/integrations/preact/CHANGELOG.md index 9f02be58da74..e714d2471b00 100644 --- a/packages/integrations/preact/CHANGELOG.md +++ b/packages/integrations/preact/CHANGELOG.md @@ -1,5 +1,11 @@ # @astrojs/preact +## 3.5.1 + +### Patch Changes + +- [#11464](https://github.com/withastro/astro/pull/11464) [`2cdb685`](https://github.com/withastro/astro/commit/2cdb685ce757fc9932b67b8a52b465296dbaedcd) Thanks [@rschristian](https://github.com/rschristian)! - Swap out `preact-ssr-prepass` for `renderToStringAsync` from `preact-render-to-string` + ## 3.5.0 ### Minor Changes diff --git a/packages/integrations/preact/package.json b/packages/integrations/preact/package.json index 2b5249aa454b..9ab0a4649ee4 100644 --- a/packages/integrations/preact/package.json +++ b/packages/integrations/preact/package.json @@ -1,7 +1,7 @@ { "name": "@astrojs/preact", "description": "Use Preact components within Astro", - "version": "3.5.0", + "version": "3.5.1", "type": "module", "types": "./dist/index.d.ts", "author": "withastro", @@ -38,10 +38,9 @@ "@babel/plugin-transform-react-jsx": "^7.24.7", "@babel/plugin-transform-react-jsx-development": "^7.24.7", "@preact/preset-vite": "2.8.2", - "@preact/signals": "^1.2.3", + "@preact/signals": "^1.3.0", "babel-plugin-transform-hook-names": "^1.0.2", - "preact-render-to-string": "~6.3.1", - "preact-ssr-prepass": "^1.2.1" + "preact-render-to-string": "^6.5.5" }, "devDependencies": { "astro": "workspace:*", diff --git a/packages/integrations/preact/src/server.ts b/packages/integrations/preact/src/server.ts index c10c01c0e8fc..88e012d02047 100644 --- a/packages/integrations/preact/src/server.ts +++ b/packages/integrations/preact/src/server.ts @@ -1,7 +1,6 @@ import type { AstroComponentMetadata, NamedSSRLoadedRendererValue } from 'astro'; import { Component as BaseComponent, type VNode, h } from 'preact'; -import { render } from 'preact-render-to-string'; -import prepass from 'preact-ssr-prepass'; +import { renderToStringAsync } from 'preact-render-to-string'; import { getContext } from './context.js'; import { restoreSignalsOnProps, serializeSignals } from './signals.js'; import StaticHtml from './static-html.js'; @@ -89,8 +88,7 @@ async function renderToStaticMarkup( : children ); - await prepass(vNode); - const html = render(vNode); + const html = await renderToStringAsync(vNode); return { attrs, html }; } diff --git a/packages/integrations/react/package.json b/packages/integrations/react/package.json index 5dd00c110ae8..16ae0b279f77 100644 --- a/packages/integrations/react/package.json +++ b/packages/integrations/react/package.json @@ -66,7 +66,7 @@ "cheerio": "1.0.0-rc.12", "react": "^18.3.1", "react-dom": "^18.3.1", - "vite": "^5.3.2" + "vite": "^5.3.4" }, "peerDependencies": { "@types/react": "^17.0.50 || ^18.0.21", diff --git a/packages/integrations/solid/package.json b/packages/integrations/solid/package.json index 7905de118fb8..3ed5438bd727 100644 --- a/packages/integrations/solid/package.json +++ b/packages/integrations/solid/package.json @@ -41,7 +41,7 @@ "astro": "workspace:*", "astro-scripts": "workspace:*", "solid-js": "^1.8.18", - "vite": "^5.3.2" + "vite": "^5.3.4" }, "peerDependencies": { "solid-devtools": "^0.30.1", diff --git a/packages/integrations/svelte/package.json b/packages/integrations/svelte/package.json index b3495a725115..7d544328aca7 100644 --- a/packages/integrations/svelte/package.json +++ b/packages/integrations/svelte/package.json @@ -57,7 +57,7 @@ "astro": "workspace:*", "astro-scripts": "workspace:*", "svelte": "^4.2.18", - "vite": "^5.3.2" + "vite": "^5.3.4" }, "peerDependencies": { "astro": "^4.0.0", diff --git a/packages/integrations/tailwind/package.json b/packages/integrations/tailwind/package.json index f7c91ed39d6d..533608cac582 100644 --- a/packages/integrations/tailwind/package.json +++ b/packages/integrations/tailwind/package.json @@ -40,8 +40,8 @@ "devDependencies": { "astro": "workspace:*", "astro-scripts": "workspace:*", - "tailwindcss": "^3.4.4", - "vite": "^5.3.2" + "tailwindcss": "^3.4.5", + "vite": "^5.3.4" }, "peerDependencies": { "astro": "^3.0.0 || ^4.0.0", diff --git a/packages/integrations/vercel/package.json b/packages/integrations/vercel/package.json index b252d05a89f4..9294e69f1137 100644 --- a/packages/integrations/vercel/package.json +++ b/packages/integrations/vercel/package.json @@ -54,7 +54,7 @@ "@astrojs/internal-helpers": "workspace:*", "@vercel/analytics": "^1.3.1", "@vercel/edge": "^1.1.1", - "@vercel/nft": "^0.27.2", + "@vercel/nft": "^0.27.3", "esbuild": "^0.21.5", "fast-glob": "^3.3.2", "set-cookie-parser": "^2.6.0", @@ -64,7 +64,7 @@ "astro": "^4.2.0" }, "devDependencies": { - "@types/set-cookie-parser": "^2.4.9", + "@types/set-cookie-parser": "^2.4.10", "astro": "workspace:*", "astro-scripts": "workspace:*", "cheerio": "1.0.0-rc.12" diff --git a/packages/integrations/vue/package.json b/packages/integrations/vue/package.json index 5f8d0a4654bd..a8789251443e 100644 --- a/packages/integrations/vue/package.json +++ b/packages/integrations/vue/package.json @@ -47,14 +47,14 @@ "@vitejs/plugin-vue": "^5.0.5", "@vitejs/plugin-vue-jsx": "^4.0.0", "@vue/compiler-sfc": "^3.4.31", - "vite-plugin-vue-devtools": "^7.3.5" + "vite-plugin-vue-devtools": "^7.3.6" }, "devDependencies": { "astro": "workspace:*", "astro-scripts": "workspace:*", "cheerio": "1.0.0-rc.12", "linkedom": "^0.18.4", - "vite": "^5.3.2", + "vite": "^5.3.4", "vue": "^3.4.31" }, "peerDependencies": { diff --git a/packages/markdown/remark/package.json b/packages/markdown/remark/package.json index 8b6eb5266682..fe68eaca44e7 100644 --- a/packages/markdown/remark/package.json +++ b/packages/markdown/remark/package.json @@ -45,13 +45,13 @@ "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.0", - "remark-smartypants": "^3.0.1", - "shiki": "^1.10.0", + "remark-smartypants": "^3.0.2", + "shiki": "^1.10.3", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", - "vfile": "^6.0.1" + "vfile": "^6.0.2" }, "devDependencies": { "@types/estree": "^1.0.5", diff --git a/packages/markdown/remark/src/shiki.ts b/packages/markdown/remark/src/shiki.ts index 66f85b85bd09..fa29c9c06a1f 100644 --- a/packages/markdown/remark/src/shiki.ts +++ b/packages/markdown/remark/src/shiki.ts @@ -42,6 +42,7 @@ export async function createShikiHighlighter({ langs = [], theme = 'github-dark', themes = {}, + defaultColor, wrap = false, transformers = [], }: ShikiConfig = {}): Promise { @@ -73,6 +74,7 @@ export async function createShikiHighlighter({ return highlighter.codeToHtml(code, { ...themeOptions, + defaultColor, lang, // NOTE: while we can spread `options.attributes` here so that Shiki can auto-serialize this as rendered // attributes on the top-level tag, it's not clear whether it is fine to pass all attributes as meta, as diff --git a/packages/markdown/remark/src/types.ts b/packages/markdown/remark/src/types.ts index 5861f9e6f9c6..e3496ec5dd95 100644 --- a/packages/markdown/remark/src/types.ts +++ b/packages/markdown/remark/src/types.ts @@ -9,7 +9,7 @@ import type { ThemeRegistrationRaw, } from 'shiki'; import type * as unified from 'unified'; -import type { VFile } from 'vfile'; +import type { DataMap, VFile } from 'vfile'; export type { Node } from 'unist'; @@ -39,6 +39,7 @@ export interface ShikiConfig { langs?: LanguageRegistration[]; theme?: ThemePresets | ThemeRegistration | ThemeRegistrationRaw; themes?: Record; + defaultColor?: 'light' | 'dark' | string | false; wrap?: boolean | null; transformers?: ShikiTransformer[]; } @@ -82,9 +83,11 @@ export interface MarkdownHeading { text: string; } +// TODO: Remove `MarkdownVFile` and move all additional properties to `DataMap` instead export interface MarkdownVFile extends VFile { - data: { - __astroHeadings?: MarkdownHeading[]; - imagePaths?: Set; - }; + data: Record & + Partial & { + __astroHeadings?: MarkdownHeading[]; + imagePaths?: Set; + }; } diff --git a/packages/markdown/remark/test/shiki.test.js b/packages/markdown/remark/test/shiki.test.js index d856b54b7f25..149fa38bb5d5 100644 --- a/packages/markdown/remark/test/shiki.test.js +++ b/packages/markdown/remark/test/shiki.test.js @@ -85,4 +85,20 @@ describe('shiki syntax highlighting', () => { assert.match(html, /data-test="\{1,3-4\}"/); }); + + it('supports the defaultColor setting', async () => { + const processor = await createMarkdownProcessor({ + shikiConfig: { + themes: { + light: 'github-light', + dark: 'github-dark', + }, + defaultColor: false, + }, + }); + const { code } = await processor.render('```\ntest\n```'); + + // Doesn't have `color` or `background-color` properties. + assert.doesNotMatch(code, /color:/); + }); }); diff --git a/packages/studio/package.json b/packages/studio/package.json index c59e9ffbef3b..fc3ec911d74c 100644 --- a/packages/studio/package.json +++ b/packages/studio/package.json @@ -41,7 +41,7 @@ "devDependencies": { "astro": "workspace:*", "astro-scripts": "workspace:*", - "typescript": "^5.5.2", - "vite": "^5.3.2" + "typescript": "^5.5.3", + "vite": "^5.3.4" } } diff --git a/packages/upgrade/package.json b/packages/upgrade/package.json index 7fe27e1b5541..69cd744ba975 100644 --- a/packages/upgrade/package.json +++ b/packages/upgrade/package.json @@ -31,7 +31,7 @@ "dependencies": { "@astrojs/cli-kit": "^0.4.1", "semver": "^7.6.2", - "preferred-pm": "^3.1.3", + "preferred-pm": "^4.0.0", "terminal-link": "^3.0.0" }, "devDependencies": { diff --git a/scripts/package.json b/scripts/package.json index 61b480b0a44b..dd6807936b13 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -12,7 +12,7 @@ "esbuild": "^0.21.5", "globby": "^14.0.2", "kleur": "^4.1.5", - "p-limit": "^5.0.0", + "p-limit": "^6.1.0", "svelte": "^4.2.18", "tar": "^7.4.0" },