summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/breezy-frogs-learn.md5
-rw-r--r--.changeset/green-islands-repeat.md5
-rw-r--r--.changeset/lazy-pillows-burn.md5
-rw-r--r--.changeset/moody-houses-drum.md5
-rw-r--r--.changeset/odd-plants-tie.md5
-rw-r--r--.changeset/olive-queens-drum.md5
-rw-r--r--.changeset/soft-colts-heal.md5
-rw-r--r--.changeset/thin-plums-drop.md5
-rw-r--r--.changeset/wild-jobs-tan.md5
-rw-r--r--examples/blog/public/blog-placeholder-5.jpgbin542101 -> 34890 bytes
-rw-r--r--examples/blog/public/placeholder-about.jpgbin72964 -> 0 bytes
-rw-r--r--examples/blog/src/components/Header.astro2
-rw-r--r--examples/blog/src/content/blog/markdown-style-guide.md2
-rw-r--r--packages/astro/CHANGELOG.md26
-rw-r--r--packages/astro/client-base.d.ts4
-rw-r--r--packages/astro/components/ViewTransitions.astro33
-rw-r--r--packages/astro/content-types.template.d.ts4
-rw-r--r--packages/astro/e2e/fixtures/react-component/src/components/WithId.jsx6
-rw-r--r--packages/astro/e2e/fixtures/react-component/src/pages/index.astro7
-rw-r--r--packages/astro/e2e/fixtures/view-transitions/astro.config.mjs3
-rw-r--r--packages/astro/e2e/fixtures/view-transitions/package.json1
-rw-r--r--packages/astro/e2e/fixtures/view-transitions/src/components/Layout.astro10
-rw-r--r--packages/astro/e2e/fixtures/view-transitions/src/pages/query.astro9
-rw-r--r--packages/astro/e2e/react-component.test.js19
-rw-r--r--packages/astro/e2e/view-transitions.test.js37
-rw-r--r--packages/astro/src/@types/astro.ts13
-rw-r--r--packages/astro/src/assets/internal.ts25
-rw-r--r--packages/astro/src/assets/types.ts6
-rw-r--r--packages/astro/src/cli/add/index.ts34
-rw-r--r--packages/astro/src/content/runtime.ts3
-rw-r--r--packages/astro/src/core/build/plugins/plugin-middleware.ts2
-rw-r--r--packages/astro/src/core/config/index.ts2
-rw-r--r--packages/astro/src/core/config/schema.ts2
-rw-r--r--packages/astro/src/template/4xx.ts38
-rw-r--r--packages/astro/src/template/css.ts50
-rw-r--r--packages/astro/src/vite-plugin-astro-server/plugin.ts11
-rw-r--r--packages/astro/test/content-collections-render.test.js16
-rw-r--r--packages/astro/test/core-image.test.js13
-rw-r--r--packages/astro/test/fixtures/content/src/pages/sort-blog-collection.astro22
-rw-r--r--packages/astro/test/fixtures/core-image/src/pages/inlineImport.astro7
-rw-r--r--packages/astro/test/middleware.test.js23
-rw-r--r--packages/create-astro/CHANGELOG.md6
-rw-r--r--packages/create-astro/src/actions/dependencies.ts18
-rw-r--r--packages/create-astro/src/actions/git.ts8
-rw-r--r--packages/create-astro/src/messages.ts4
-rw-r--r--packages/create-astro/src/shell.ts49
-rw-r--r--packages/create-astro/test/fixtures/not-empty/git.json1
-rw-r--r--packages/create-astro/test/git.test.js47
-rw-r--r--packages/integrations/cloudflare/CHANGELOG.md29
-rw-r--r--packages/integrations/cloudflare/README.md6
-rw-r--r--packages/integrations/cloudflare/src/index.ts2
-rw-r--r--packages/integrations/cloudflare/src/runtime.ts11
-rw-r--r--packages/integrations/cloudflare/src/server.advanced.ts28
-rw-r--r--packages/integrations/cloudflare/src/server.directory.ts53
-rw-r--r--packages/integrations/cloudflare/test/cf.test.js2
-rw-r--r--packages/integrations/cloudflare/test/fixtures/runtime/astro.config.mjs8
-rw-r--r--packages/integrations/cloudflare/test/fixtures/runtime/package.json9
-rw-r--r--packages/integrations/cloudflare/test/fixtures/runtime/src/pages/index.astro15
-rw-r--r--packages/integrations/cloudflare/test/runtime.test.js38
-rw-r--r--packages/integrations/netlify/CHANGELOG.md26
-rw-r--r--packages/integrations/netlify/README.md26
-rw-r--r--packages/integrations/netlify/builders-types.d.ts9
-rw-r--r--packages/integrations/netlify/src/netlify-functions.ts17
-rw-r--r--packages/integrations/netlify/test/functions/builders.test.js37
-rw-r--r--packages/integrations/netlify/test/functions/fixtures/builders/src/pages/index.astro11
-rw-r--r--packages/integrations/node/CHANGELOG.md9
-rw-r--r--packages/integrations/node/src/get-network-address.ts48
-rw-r--r--packages/integrations/node/src/preview.ts13
-rw-r--r--packages/integrations/node/src/standalone.ts13
-rw-r--r--packages/integrations/react/client.js2
-rw-r--r--packages/integrations/sitemap/README.md2
-rw-r--r--packages/integrations/vercel/CHANGELOG.md9
-rw-r--r--packages/integrations/vercel/src/lib/nft.ts3
73 files changed, 870 insertions, 164 deletions
diff --git a/.changeset/breezy-frogs-learn.md b/.changeset/breezy-frogs-learn.md
deleted file mode 100644
index b3f2f86b9..000000000
--- a/.changeset/breezy-frogs-learn.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@astrojs/cloudflare': minor
----
-
-More efficient \_routes.json
diff --git a/.changeset/green-islands-repeat.md b/.changeset/green-islands-repeat.md
new file mode 100644
index 000000000..9894fde96
--- /dev/null
+++ b/.changeset/green-islands-repeat.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fix AstroConfigSchema type export
diff --git a/.changeset/lazy-pillows-burn.md b/.changeset/lazy-pillows-burn.md
new file mode 100644
index 000000000..009955c7e
--- /dev/null
+++ b/.changeset/lazy-pillows-burn.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/sitemap': patch
+---
+
+docs: fix github search link in README.md
diff --git a/.changeset/moody-houses-drum.md b/.changeset/moody-houses-drum.md
new file mode 100644
index 000000000..1dfaefba2
--- /dev/null
+++ b/.changeset/moody-houses-drum.md
@@ -0,0 +1,5 @@
+---
+'create-astro': minor
+---
+
+Reduce dependency installation size, swap `execa` for light `node:child_process` wrapper
diff --git a/.changeset/odd-plants-tie.md b/.changeset/odd-plants-tie.md
new file mode 100644
index 000000000..b57376dee
--- /dev/null
+++ b/.changeset/odd-plants-tie.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Add support for non-awaited imports to the Image component and `getImage`
diff --git a/.changeset/olive-queens-drum.md b/.changeset/olive-queens-drum.md
new file mode 100644
index 000000000..258d9c726
--- /dev/null
+++ b/.changeset/olive-queens-drum.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Add second type argument to the AstroGlobal type to type Astro.self. This change will ultimately allow our editor tooling to provide props completions and intellisense for `<Astro.self />`
diff --git a/.changeset/soft-colts-heal.md b/.changeset/soft-colts-heal.md
new file mode 100644
index 000000000..4ceaf2a9b
--- /dev/null
+++ b/.changeset/soft-colts-heal.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+`astro add` now passes down `--save-prod`, `--save-dev`, `--save-exact`, and `--no-save` flags for installation
diff --git a/.changeset/thin-plums-drop.md b/.changeset/thin-plums-drop.md
new file mode 100644
index 000000000..ab3fb875a
--- /dev/null
+++ b/.changeset/thin-plums-drop.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/react': patch
+---
+
+fix a bug where react identifierPrefix was set to null for client:only components causing React.useId to generate ids prefixed with null
diff --git a/.changeset/wild-jobs-tan.md b/.changeset/wild-jobs-tan.md
deleted file mode 100644
index 8372c01b1..000000000
--- a/.changeset/wild-jobs-tan.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'astro': patch
----
-
-Move hoisted script analysis optimization behind the `experimental.optimizeHoistedScript` option
diff --git a/examples/blog/public/blog-placeholder-5.jpg b/examples/blog/public/blog-placeholder-5.jpg
index 1a59ad9b6..c5646746c 100644
--- a/examples/blog/public/blog-placeholder-5.jpg
+++ b/examples/blog/public/blog-placeholder-5.jpg
Binary files differ
diff --git a/examples/blog/public/placeholder-about.jpg b/examples/blog/public/placeholder-about.jpg
deleted file mode 100644
index 2f736d92a..000000000
--- a/examples/blog/public/placeholder-about.jpg
+++ /dev/null
Binary files differ
diff --git a/examples/blog/src/components/Header.astro b/examples/blog/src/components/Header.astro
index 647320834..c9ab99f1d 100644
--- a/examples/blog/src/components/Header.astro
+++ b/examples/blog/src/components/Header.astro
@@ -23,7 +23,7 @@ import { SITE_TITLE } from '../consts';
</a>
<a href="https://twitter.com/astrodotbuild" target="_blank">
<span class="sr-only">Follow Astro on Twitter</span>
- <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32" -
+ <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32"
><path
fill="currentColor"
d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"
diff --git a/examples/blog/src/content/blog/markdown-style-guide.md b/examples/blog/src/content/blog/markdown-style-guide.md
index fd402b87b..877ec2f4a 100644
--- a/examples/blog/src/content/blog/markdown-style-guide.md
+++ b/examples/blog/src/content/blog/markdown-style-guide.md
@@ -39,7 +39,7 @@ Itatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sap
#### Output
-![blog placeholder](../../../public/blog-placeholder-about.jpg)
+![blog placeholder](/blog-placeholder-about.jpg)
## Blockquotes
diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md
index 2d0ebe643..a551fa323 100644
--- a/packages/astro/CHANGELOG.md
+++ b/packages/astro/CHANGELOG.md
@@ -327,6 +327,32 @@
- @astrojs/internal-helpers@0.2.0-beta.0
- @astrojs/markdown-remark@3.0.0-beta.0
+## 2.10.7
+
+### Patch Changes
+
+- [#8042](https://github.com/withastro/astro/pull/8042) [`4a145c4c7`](https://github.com/withastro/astro/commit/4a145c4c7d176a3fb56342844690c6999e880069) Thanks [@matthewp](https://github.com/matthewp)! - Treat same pathname with different search params as different page
+
+## 2.10.6
+
+### Patch Changes
+
+- [#8027](https://github.com/withastro/astro/pull/8027) [`1b8d30209`](https://github.com/withastro/astro/commit/1b8d3020990130dabfaaf753db73a32c6e0c896a) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Ensure dev server restarts respect when `base` is removed
+
+- [#8033](https://github.com/withastro/astro/pull/8033) [`405913cdf`](https://github.com/withastro/astro/commit/405913cdf20b26407aa351c090f0a0859a4e6f54) Thanks [@matthewp](https://github.com/matthewp)! - Prevent script re-evaluation on page transition
+
+- [#8036](https://github.com/withastro/astro/pull/8036) [`87d4b1843`](https://github.com/withastro/astro/commit/87d4b18437c7565c48cad4bea81831c2a244ebb8) Thanks [@ematipico](https://github.com/ematipico)! - Fix a bug where the middleware entry point was passed to integrations even though the configuration `build.excludeMiddleware` was set to `false`.
+
+- [#8022](https://github.com/withastro/astro/pull/8022) [`c23377caa`](https://github.com/withastro/astro/commit/c23377caafbc75deb91c33b9678c1b6868ad40ea) Thanks [@bluwy](https://github.com/bluwy)! - Always return a new array instance from `getCollection` in prod
+
+- [#8013](https://github.com/withastro/astro/pull/8013) [`86bee2812`](https://github.com/withastro/astro/commit/86bee2812185df6e14025e5962a335f51853587b) Thanks [@martrapp](https://github.com/martrapp)! - Links with hash marks now trigger view transitions if they lead to a different page. Links to the same page do not trigger view transitions.
+
+## 2.10.5
+
+### Patch Changes
+
+- [#8011](https://github.com/withastro/astro/pull/8011) [`5b1e39ef6`](https://github.com/withastro/astro/commit/5b1e39ef6ec6dcebea96584f95d9530bd9aa715d) Thanks [@bluwy](https://github.com/bluwy)! - Move hoisted script analysis optimization behind the `experimental.optimizeHoistedScript` option
+
## 2.10.4
### Patch Changes
diff --git a/packages/astro/client-base.d.ts b/packages/astro/client-base.d.ts
index c0203e0ef..3b0ee4901 100644
--- a/packages/astro/client-base.d.ts
+++ b/packages/astro/client-base.d.ts
@@ -48,7 +48,9 @@ declare module 'astro:assets' {
* This is functionally equivalent to using the `<Image />` component, as the component calls this function internally.
*/
getImage: (
- options: import('./dist/assets/types.js').ImageTransform
+ options:
+ | import('./dist/assets/types.js').ImageTransform
+ | import('./dist/assets/types.js').UnresolvedImageTransform
) => Promise<import('./dist/assets/types.js').GetImageResult>;
getConfiguredImageService: typeof import('./dist/assets/index.js').getConfiguredImageService;
Image: typeof import('./components/Image.astro').default;
diff --git a/packages/astro/components/ViewTransitions.astro b/packages/astro/components/ViewTransitions.astro
index 4f43171f9..05fc57aa6 100644
--- a/packages/astro/components/ViewTransitions.astro
+++ b/packages/astro/components/ViewTransitions.astro
@@ -63,9 +63,16 @@ const { fallback = 'animate' } = Astro.props as Props;
return 'animate';
}
+ function markScriptsExec() {
+ for (const script of document.scripts) {
+ script.dataset.astroExec = '';
+ }
+ }
+
function runScripts() {
let wait = Promise.resolve();
for (const script of Array.from(document.scripts)) {
+ if (script.dataset.astroExec === '') continue;
const s = document.createElement('script');
s.innerHTML = script.innerHTML;
for (const attr of script.attributes) {
@@ -77,6 +84,7 @@ const { fallback = 'animate' } = Astro.props as Props;
}
s.setAttribute(attr.name, attr.value);
}
+ s.dataset.astroExec = '';
script.replaceWith(s);
}
return wait;
@@ -100,6 +108,19 @@ const { fallback = 'animate' } = Astro.props as Props;
const href = el.getAttribute('href');
return doc.head.querySelector(`link[rel=stylesheet][href="${href}"]`);
}
+ if (el.tagName === 'SCRIPT') {
+ let s1 = el as HTMLScriptElement;
+ for (const s2 of doc.scripts) {
+ if (
+ // Inline
+ (s1.textContent && s1.textContent === s2.textContent) ||
+ // External
+ (s1.type === s2.type && s1.src === s2.src)
+ ) {
+ return s2;
+ }
+ }
+ }
return null;
};
@@ -132,6 +153,10 @@ const { fallback = 'animate' } = Astro.props as Props;
}
}
+ if (state?.scrollY === 0 && location.hash) {
+ const id = decodeURIComponent(location.hash.slice(1));
+ state.scrollY = document.getElementById(id)?.offsetTop || 0;
+ }
if (state?.scrollY != null) {
scrollTo(0, state.scrollY);
}
@@ -203,6 +228,7 @@ const { fallback = 'animate' } = Astro.props as Props;
} finally {
document.documentElement.removeAttribute('data-astro-transition');
await runScripts();
+ markScriptsExec();
onload();
}
}
@@ -221,6 +247,8 @@ const { fallback = 'animate' } = Astro.props as Props;
}
if (supportsViewTransitions || getFallback() !== 'none') {
+ markScriptsExec();
+
document.addEventListener('click', (ev) => {
let link = ev.target;
if (link instanceof Element && link.tagName !== 'A') {
@@ -235,7 +263,10 @@ const { fallback = 'animate' } = Astro.props as Props;
link.href &&
(!link.target || link.target === '_self') &&
link.origin === location.origin &&
- !link.hash &&
+ !(
+ // Same page means same path and same query params
+ (location.pathname === link.pathname && location.search === link.search)
+ ) &&
ev.button === 0 && // left clicks only
!ev.metaKey && // new tab (mac)
!ev.ctrlKey && // new tab (windows)
diff --git a/packages/astro/content-types.template.d.ts b/packages/astro/content-types.template.d.ts
index ba9fe1c2a..cf825c25e 100644
--- a/packages/astro/content-types.template.d.ts
+++ b/packages/astro/content-types.template.d.ts
@@ -10,7 +10,9 @@ declare module 'astro:content' {
declare module 'astro:content' {
export { z } from 'astro/zod';
- export type CollectionEntry<C extends keyof AnyEntryMap> = AnyEntryMap[C][keyof AnyEntryMap[C]];
+
+ type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
+ export type CollectionEntry<C extends keyof AnyEntryMap> = Flatten<AnyEntryMap[C]>;
// TODO: Remove this when having this fallback is no longer relevant. 2.3? 3.0? - erika, 2023-04-04
/**
diff --git a/packages/astro/e2e/fixtures/react-component/src/components/WithId.jsx b/packages/astro/e2e/fixtures/react-component/src/components/WithId.jsx
new file mode 100644
index 000000000..0abe91c72
--- /dev/null
+++ b/packages/astro/e2e/fixtures/react-component/src/components/WithId.jsx
@@ -0,0 +1,6 @@
+import React from 'react';
+
+export default function () {
+ const id = React.useId();
+ return <p className='react-use-id' id={id}>{id}</p>;
+}
diff --git a/packages/astro/e2e/fixtures/react-component/src/pages/index.astro b/packages/astro/e2e/fixtures/react-component/src/pages/index.astro
index f9e0ae395..a3a0a4c1f 100644
--- a/packages/astro/e2e/fixtures/react-component/src/pages/index.astro
+++ b/packages/astro/e2e/fixtures/react-component/src/pages/index.astro
@@ -2,6 +2,7 @@
import Counter from '../components/Counter.jsx';
import ReactComponent from '../components/JSXComponent.jsx';
import Suffix from '../components/Suffix.react';
+import WithId from '../components/WithId.jsx';
const someProps = {
count: 0,
@@ -36,5 +37,11 @@ const someProps = {
<ReactComponent id="client-only" client:only="react" />
<Suffix client:load />
+
+ <WithId />
+ <WithId client:load />
+ <WithId client:load />
+ <WithId client:only="react" />
+ <WithId client:only="react" />
</body>
</html>
diff --git a/packages/astro/e2e/fixtures/view-transitions/astro.config.mjs b/packages/astro/e2e/fixtures/view-transitions/astro.config.mjs
index c0df0074c..8a9f43bcc 100644
--- a/packages/astro/e2e/fixtures/view-transitions/astro.config.mjs
+++ b/packages/astro/e2e/fixtures/view-transitions/astro.config.mjs
@@ -1,8 +1,11 @@
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
+import nodejs from '@astrojs/node';
// https://astro.build/config
export default defineConfig({
+ output: 'server',
+ adapter: nodejs({ mode: 'standalone' }),
integrations: [react()],
experimental: {
viewTransitions: true,
diff --git a/packages/astro/e2e/fixtures/view-transitions/package.json b/packages/astro/e2e/fixtures/view-transitions/package.json
index 90a07f839..f4ba9b17b 100644
--- a/packages/astro/e2e/fixtures/view-transitions/package.json
+++ b/packages/astro/e2e/fixtures/view-transitions/package.json
@@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"astro": "workspace:*",
+ "@astrojs/node": "workspace:*",
"@astrojs/react": "workspace:*",
"react": "^18.1.0",
"react-dom": "^18.1.0"
diff --git a/packages/astro/e2e/fixtures/view-transitions/src/components/Layout.astro b/packages/astro/e2e/fixtures/view-transitions/src/components/Layout.astro
index 7016a1bf7..1c0f9bbdf 100644
--- a/packages/astro/e2e/fixtures/view-transitions/src/components/Layout.astro
+++ b/packages/astro/e2e/fixtures/view-transitions/src/components/Layout.astro
@@ -20,6 +20,16 @@ const { link } = Astro.props as Props;
</style>
<ViewTransitions />
<DarkMode />
+ <meta name="script-executions" content="0">
+ <script is:inline defer>
+ {
+ // Increment a global to see if this is running more than once
+ globalThis.scriptExecutions = globalThis.scriptExecutions == null ? -1 : globalThis.scriptExecutions;
+ globalThis.scriptExecutions++;
+ const el = document.querySelector('[name="script-executions"]');
+ el.setAttribute('content', globalThis.scriptExecutions);
+ }
+ </script>
</head>
<body>
<header transition:animate="morph">
diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/query.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/query.astro
new file mode 100644
index 000000000..44dd03ce0
--- /dev/null
+++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/query.astro
@@ -0,0 +1,9 @@
+---
+import Layout from '../components/Layout.astro';
+
+const page = Astro.url.searchParams.get('page') || 1;
+---
+<Layout>
+ <p id="query-page">Page {page}</p>
+ <a id="click-two" href="/query?page=2">go to 2</a>
+</Layout>
diff --git a/packages/astro/e2e/react-component.test.js b/packages/astro/e2e/react-component.test.js
index 00d747079..b19a071d6 100644
--- a/packages/astro/e2e/react-component.test.js
+++ b/packages/astro/e2e/react-component.test.js
@@ -34,3 +34,22 @@ test.describe('dev', () => {
expect(await suffix.textContent()).toBe('suffix toggle true');
});
});
+
+test.describe('React client id generation', () => {
+ test('react components generate unique ids', async ({ page, astro }) => {
+ await page.goto(astro.resolveUrl('/'));
+
+ const components = page.locator('.react-use-id');
+ await expect(components).toHaveCount(5);
+ const staticId = await components.nth(0).getAttribute('id');
+ const hydratedId0 = await components.nth(1).getAttribute('id');
+ const hydratedId1 = await components.nth(2).getAttribute('id');
+ const clientOnlyId0 = await components.nth(3).getAttribute('id');
+ const clientOnlyId1 = await components.nth(4).getAttribute('id');
+ console.log('ho ho', staticId, hydratedId0, hydratedId1, clientOnlyId0, clientOnlyId1);
+ expect(staticId).not.toEqual(hydratedId0);
+ expect(hydratedId0).not.toEqual(hydratedId1);
+ expect(hydratedId1).not.toEqual(clientOnlyId0);
+ expect(clientOnlyId0).not.toEqual(clientOnlyId1);
+ });
+});
diff --git a/packages/astro/e2e/view-transitions.test.js b/packages/astro/e2e/view-transitions.test.js
index f30cd9902..7aeb6502a 100644
--- a/packages/astro/e2e/view-transitions.test.js
+++ b/packages/astro/e2e/view-transitions.test.js
@@ -279,4 +279,41 @@ test.describe('View Transitions', () => {
// Count should remain
await expect(cnt).toHaveText('6');
});
+
+ test('Scripts are only executed once', async ({ page, astro }) => {
+ // Go to page 1
+ await page.goto(astro.resolveUrl('/one'));
+ const p = page.locator('#one');
+ await expect(p, 'should have content').toHaveText('Page 1');
+
+ // go to page 2
+ await page.click('#click-two');
+ const article = page.locator('#twoarticle');
+ await expect(article, 'should have script content').toHaveText('works');
+
+ const meta = page.locator('[name="script-executions"]');
+ await expect(meta).toHaveAttribute('content', '0');
+ });
+
+ test('Navigating to the same path but with different query params should result in transition', async ({
+ page,
+ astro,
+ }) => {
+ const loads = [];
+ page.addListener('load', (p) => {
+ loads.push(p.title());
+ });
+
+ // Go to page 1
+ await page.goto(astro.resolveUrl('/query'));
+ let p = page.locator('#query-page');
+ await expect(p, 'should have content').toHaveText('Page 1');
+
+ // go to page 2
+ await page.click('#click-two');
+ p = page.locator('#query-page');
+ await expect(p, 'should have content').toHaveText('Page 2');
+
+ await expect(loads.length, 'There should only be 1 page load').toEqual(1);
+ });
});
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index 8bdd35173..d070b9825 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -13,10 +13,9 @@ import type { AddressInfo } from 'node:net';
import type * as rollup from 'rollup';
import type { TsConfigJson } from 'tsconfig-resolver';
import type * as vite from 'vite';
-import type { z } from 'zod';
import type { SerializedSSRManifest } from '../core/app/types';
import type { PageBuildData } from '../core/build/types';
-import type { AstroConfigSchema } from '../core/config';
+import type { AstroConfigType } from '../core/config';
import type { AstroTimer } from '../core/config/timer';
import type { AstroCookies } from '../core/cookies';
import type { LogOptions, LoggerLevel } from '../core/logger/core';
@@ -141,8 +140,10 @@ export interface CLIFlags {
*
* [Astro reference](https://docs.astro.build/reference/api-reference/#astro-global)
*/
-export interface AstroGlobal<Props extends Record<string, any> = Record<string, any>>
- extends AstroGlobalPartial,
+export interface AstroGlobal<
+ Props extends Record<string, any> = Record<string, any>,
+ Self = AstroComponentFactory
+> extends AstroGlobalPartial,
AstroSharedContext<Props> {
/**
* A full URL object of the request URL.
@@ -219,7 +220,7 @@ export interface AstroGlobal<Props extends Record<string, any> = Record<string,
*
* [Astro reference](https://docs.astro.build/en/guides/api-reference/#astroself)
*/
- self: AstroComponentFactory;
+ self: Self;
/** Utility functions for modifying an Astro component’s slotted children
*
* [Astro reference](https://docs.astro.build/en/reference/api-reference/#astroslots)
@@ -1351,7 +1352,7 @@ export interface ResolvedInjectedRoute extends InjectedRoute {
* Resolved Astro Config
* Config with user settings along with all defaults filled in.
*/
-export interface AstroConfig extends z.output<typeof AstroConfigSchema> {
+export interface AstroConfig extends AstroConfigType {
// Public:
// This is a more detailed type than zod validation gives us.
// TypeScript still confirms zod validation matches this type.
diff --git a/packages/astro/src/assets/internal.ts b/packages/astro/src/assets/internal.ts
index c56b5369c..06e4f8cc0 100644
--- a/packages/astro/src/assets/internal.ts
+++ b/packages/astro/src/assets/internal.ts
@@ -1,7 +1,12 @@
import type { AstroSettings } from '../@types/astro.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js';
import { isLocalService, type ImageService } from './services/service.js';
-import type { GetImageResult, ImageMetadata, ImageTransform } from './types.js';
+import type {
+ GetImageResult,
+ ImageMetadata,
+ ImageTransform,
+ UnresolvedImageTransform,
+} from './types.js';
export function injectImageEndpoint(settings: AstroSettings) {
settings.injectedRoutes.push({
@@ -37,7 +42,7 @@ export async function getConfiguredImageService(): Promise<ImageService> {
}
export async function getImage(
- options: ImageTransform,
+ options: ImageTransform | UnresolvedImageTransform,
serviceConfig: Record<string, any>
): Promise<GetImageResult> {
if (!options || typeof options !== 'object') {
@@ -48,9 +53,19 @@ export async function getImage(
}
const service = await getConfiguredImageService();
+
+ // If the user inlined an import, something fairly common especially in MDX, await it for them
+ const resolvedOptions: ImageTransform = {
+ ...options,
+ src:
+ typeof options.src === 'object' && 'then' in options.src
+ ? (await options.src).default
+ : options.src,
+ };
+
const validatedOptions = service.validateOptions
- ? await service.validateOptions(options, serviceConfig)
- : options;
+ ? await service.validateOptions(resolvedOptions, serviceConfig)
+ : resolvedOptions;
let imageURL = await service.getURL(validatedOptions, serviceConfig);
@@ -60,7 +75,7 @@ export async function getImage(
}
return {
- rawOptions: options,
+ rawOptions: resolvedOptions,
options: validatedOptions,
src: imageURL,
attributes:
diff --git a/packages/astro/src/assets/types.ts b/packages/astro/src/assets/types.ts
index 5632d7691..9c5990cb7 100644
--- a/packages/astro/src/assets/types.ts
+++ b/packages/astro/src/assets/types.ts
@@ -27,6 +27,10 @@ export interface ImageMetadata {
orientation?: number;
}
+export type UnresolvedImageTransform = Omit<ImageTransform, 'src'> & {
+ src: Promise<{ default: ImageMetadata }>;
+};
+
/**
* Options accepted by the image transformation service.
*/
@@ -93,7 +97,7 @@ export type LocalImageProps<T> = ImageSharedProps<T> & {
* <Image src={myImage} alt="..."></Image>
* ```
*/
- src: ImageMetadata;
+ src: ImageMetadata | Promise<{ default: ImageMetadata }>;
/**
* Desired output format for the image. Defaults to `webp`.
*
diff --git a/packages/astro/src/cli/add/index.ts b/packages/astro/src/cli/add/index.ts
index 55502fc40..f09d74a08 100644
--- a/packages/astro/src/cli/add/index.ts
+++ b/packages/astro/src/cli/add/index.ts
@@ -628,6 +628,18 @@ async function getInstallIntegrationsCommand({
}
}
+// Allow forwarding of standard `npm install` flags
+// See https://docs.npmjs.com/cli/v8/commands/npm-install#description
+const INHERITED_FLAGS = new Set<string>([
+ 'P',
+ 'save-prod',
+ 'D',
+ 'save-dev',
+ 'E',
+ 'save-exact',
+ 'no-save',
+]);
+
async function tryToInstallIntegrations({
integrations,
cwd,
@@ -641,12 +653,24 @@ async function tryToInstallIntegrations({
}): Promise<UpdateResult> {
const installCommand = await getInstallIntegrationsCommand({ integrations, cwd });
+ const inheritedFlags = Object.entries(flags)
+ .map(([flag]) => {
+ if (flag == '_') return;
+ if (INHERITED_FLAGS.has(flag)) {
+ if (flag.length === 1) return `-${flag}`;
+ return `--${flag}`;
+ }
+ })
+ .filter(Boolean)
+ .flat() as string[];
+
if (installCommand === null) {
return UpdateResult.none;
} else {
const coloredOutput = `${bold(installCommand.pm)} ${installCommand.command}${[
'',
...installCommand.flags,
+ ...inheritedFlags,
].join(' ')} ${cyan(installCommand.dependencies.join(' '))}`;
const message = `\n${boxen(coloredOutput, {
margin: 0.5,
@@ -666,14 +690,20 @@ async function tryToInstallIntegrations({
try {
await execa(
installCommand.pm,
- [installCommand.command, ...installCommand.flags, ...installCommand.dependencies],
+ [
+ installCommand.command,
+ ...installCommand.flags,
+ ...inheritedFlags,
+ ...installCommand.dependencies,
+ ],
{ cwd }
);
spinner.succeed();
return UpdateResult.updated;
} catch (err) {
- debug('add', 'Error installing dependencies', err);
spinner.fail();
+ debug('add', 'Error installing dependencies', err);
+ console.error('\n', (err as any).stdout, '\n');
return UpdateResult.failure;
}
} else {
diff --git a/packages/astro/src/content/runtime.ts b/packages/astro/src/content/runtime.ts
index a1afb2201..7b8654ee7 100644
--- a/packages/astro/src/content/runtime.ts
+++ b/packages/astro/src/content/runtime.ts
@@ -69,7 +69,8 @@ export function createGetCollection({
// Cache `getCollection()` calls in production only
// prevents stale cache in development
if (import.meta.env.PROD && cacheEntriesByCollection.has(collection)) {
- entries = cacheEntriesByCollection.get(collection)!;
+ // Always return a new instance so consumers can safely mutate it
+ entries = [...cacheEntriesByCollection.get(collection)!];
} else {
entries = await Promise.all(
lazyImports.map(async (lazyImport) => {
diff --git a/packages/astro/src/core/build/plugins/plugin-middleware.ts b/packages/astro/src/core/build/plugins/plugin-middleware.ts
index 8afca2fbc..5ed532d5e 100644
--- a/packages/astro/src/core/build/plugins/plugin-middleware.ts
+++ b/packages/astro/src/core/build/plugins/plugin-middleware.ts
@@ -56,7 +56,7 @@ export function vitePluginMiddleware(
if (chunk.type === 'asset') {
continue;
}
- if (chunk.fileName === 'middleware.mjs') {
+ if (chunk.fileName === 'middleware.mjs' && opts.settings.config.build.excludeMiddleware) {
internals.middlewareEntryPoint = new URL(chunkName, opts.settings.config.build.server);
}
}
diff --git a/packages/astro/src/core/config/index.ts b/packages/astro/src/core/config/index.ts
index 23db73382..4699a624c 100644
--- a/packages/astro/src/core/config/index.ts
+++ b/packages/astro/src/core/config/index.ts
@@ -1,6 +1,6 @@
export { resolveConfig, resolveConfigPath, resolveFlags, resolveRoot } from './config.js';
export { createNodeLogging } from './logging.js';
export { mergeConfig } from './merge.js';
-export type { AstroConfigSchema } from './schema';
+export type { AstroConfigType } from './schema';
export { createSettings } from './settings.js';
export { loadTSConfig, updateTSConfigForFramework } from './tsconfig.js';
diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts
index 59bd76b53..9b7f42327 100644
--- a/packages/astro/src/core/config/schema.ts
+++ b/packages/astro/src/core/config/schema.ts
@@ -274,6 +274,8 @@ export const AstroConfigSchema = z.object({
legacy: z.object({}).optional().default({}),
});
+export type AstroConfigType = z.infer<typeof AstroConfigSchema>;
+
export function createRelativeSchema(cmd: string, fileProtocolRoot: string) {
// We need to extend the global schema to add transforms that are relative to root.
// This is type checked against the global schema to make sure we still match.
diff --git a/packages/astro/src/template/4xx.ts b/packages/astro/src/template/4xx.ts
index c5d87929e..45f4ac10a 100644
--- a/packages/astro/src/template/4xx.ts
+++ b/packages/astro/src/template/4xx.ts
@@ -1,5 +1,4 @@
import { escape } from 'html-escaper';
-import { baseCSS } from './css.js';
interface ErrorTemplateOptions {
/** a short description of the error */
@@ -28,14 +27,40 @@ export default function template({
<meta charset="UTF-8">
<title>${tabTitle}</title>
<style>
- ${baseCSS}
:root {
+ --gray-10: hsl(258, 7%, 10%);
+ --gray-20: hsl(258, 7%, 20%);
+ --gray-30: hsl(258, 7%, 30%);
+ --gray-40: hsl(258, 7%, 40%);
+ --gray-50: hsl(258, 7%, 50%);
+ --gray-60: hsl(258, 7%, 60%);
+ --gray-70: hsl(258, 7%, 70%);
+ --gray-80: hsl(258, 7%, 80%);
+ --gray-90: hsl(258, 7%, 90%);
--black: #13151A;
--accent-light: #E0CCFA;
}
- body {
+ * {
+ box-sizing: border-box;
+ }
+
+ html {
background: var(--black);
+ color-scheme: dark;
+ accent-color: var(--accent-light);
+ }
+
+ body {
+ background-color: var(--gray-10);
+ color: var(--gray-80);
+ font-family: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", monospace;
+ line-height: 1.5;
+ margin: 0;
+ }
+
+ a {
+ color: var(--accent-light);
}
.center {
@@ -52,6 +77,8 @@ export default function template({
color: white;
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-weight: 700;
+ margin-top: 1rem;
+ margin-bottom: 0;
}
.statusCode {
@@ -63,11 +90,14 @@ export default function template({
width: 124px;
}
- pre {
+ pre, code {
padding: 2px 8px;
background: rgba(0,0,0, 0.25);
border: 1px solid rgba(255,255,255, 0.25);
border-radius: 4px;
+ font-size: 1.2em;
+ margin-top: 0;
+ max-width: 60em;
}
</style>
</head>
diff --git a/packages/astro/src/template/css.ts b/packages/astro/src/template/css.ts
deleted file mode 100644
index 87fbfd1b8..000000000
--- a/packages/astro/src/template/css.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * CSS is exported as a string so the error pages:
- * 1. don’t need to resolve a deep internal CSS import
- * 2. don’t need external dependencies to render (they may be shown because of a dep!)
- */
-
-// Base CSS: shared CSS among pages
-export const baseCSS = `
-:root {
- --gray-10: hsl(258, 7%, 10%);
- --gray-20: hsl(258, 7%, 20%);
- --gray-30: hsl(258, 7%, 30%);
- --gray-40: hsl(258, 7%, 40%);
- --gray-50: hsl(258, 7%, 50%);
- --gray-60: hsl(258, 7%, 60%);
- --gray-70: hsl(258, 7%, 70%);
- --gray-80: hsl(258, 7%, 80%);
- --gray-90: hsl(258, 7%, 90%);
- --orange: #ff5d01;
-}
-
-* {
- box-sizing: border-box;
-}
-
-body {
- background-color: var(--gray-10);
- color: var(--gray-80);
- font-family: monospace;
- line-height: 1.5;
- margin: 0;
-}
-
-a {
- color: var(--orange);
-}
-
-h1 {
- font-weight: 800;
- margin-top: 1rem;
- margin-bottom: 0;
-}
-
-pre {
- color:;
- font-size: 1.2em;
- margin-top: 0;
- max-width: 60em;
-}
-`;
diff --git a/packages/astro/src/vite-plugin-astro-server/plugin.ts b/packages/astro/src/vite-plugin-astro-server/plugin.ts
index cb74f9d9a..dfaf976bf 100644
--- a/packages/astro/src/vite-plugin-astro-server/plugin.ts
+++ b/packages/astro/src/vite-plugin-astro-server/plugin.ts
@@ -44,12 +44,11 @@ export default function createVitePluginAstroServer({
return () => {
// Push this middleware to the front of the stack so that it can intercept responses.
- if (settings.config.base !== '/') {
- viteServer.middlewares.stack.unshift({
- route: '',
- handle: baseMiddleware(settings, logging),
- });
- }
+ // fix(#6067): always inject this to ensure zombie base handling is killed after restarts
+ viteServer.middlewares.stack.unshift({
+ route: '',
+ handle: baseMiddleware(settings, logging),
+ });
// Note that this function has a name so other middleware can find it.
viteServer.middlewares.use(async function astroDevHandler(request, response) {
if (request.url === undefined || !request.method) {
diff --git a/packages/astro/test/content-collections-render.test.js b/packages/astro/test/content-collections-render.test.js
index e1107b10f..f6a9c3c72 100644
--- a/packages/astro/test/content-collections-render.test.js
+++ b/packages/astro/test/content-collections-render.test.js
@@ -168,6 +168,22 @@ describe('Content Collections - render()', () => {
expect(h2).to.have.a.lengthOf(1);
expect(h2.attr('data-components-export-applied')).to.equal('true');
});
+
+ it('getCollection should return new instances of the array to be mutated safely', async () => {
+ const app = await fixture.loadTestAdapterApp();
+
+ let request = new Request('http://example.com/sort-blog-collection');
+ let response = await app.render(request);
+ let html = await response.text();
+ let $ = cheerio.load(html);
+ expect($('li').first().text()).to.equal('With Layout Prop');
+
+ request = new Request('http://example.com/');
+ response = await app.render(request);
+ html = await response.text();
+ $ = cheerio.load(html);
+ expect($('li').first().text()).to.equal('Hello world');
+ });
});
describe('Dev - SSG', () => {
diff --git a/packages/astro/test/core-image.test.js b/packages/astro/test/core-image.test.js
index 4d5f467bf..8c09de245 100644
--- a/packages/astro/test/core-image.test.js
+++ b/packages/astro/test/core-image.test.js
@@ -147,6 +147,19 @@ describe('astro:image', () => {
})
).to.be.true;
});
+
+ it('supports inlined imports', async () => {
+ let res = await fixture.fetch('/inlineImport');
+ let html = await res.text();
+ $ = cheerio.load(html);
+
+ let $img = $('img');
+ expect($img).to.have.a.lengthOf(1);
+
+ let src = $img.attr('src');
+ res = await fixture.fetch(src);
+ expect(res.status).to.equal(200);
+ });
});
describe('vite-isms', () => {
diff --git a/packages/astro/test/fixtures/content/src/pages/sort-blog-collection.astro b/packages/astro/test/fixtures/content/src/pages/sort-blog-collection.astro
new file mode 100644
index 000000000..542606819
--- /dev/null
+++ b/packages/astro/test/fixtures/content/src/pages/sort-blog-collection.astro
@@ -0,0 +1,22 @@
+---
+import { getCollection } from 'astro:content';
+
+const blog = await getCollection('blog');
+
+// Sort descending by title, make sure mutating `blog` doesn't mutate other pages that call `getCollection` too
+blog.sort((a, b) => a.data.title < b.data.title ? 1 : -1)
+---
+<html>
+<head>
+ <title>Index</title>
+</head>
+<body>
+ <h1>Blog Posts</h1>
+
+ <ul>
+ {blog.map(post => (
+ <li>{ post.data.title }</li>
+ ))}
+ </ul>
+</body>
+</html>
diff --git a/packages/astro/test/fixtures/core-image/src/pages/inlineImport.astro b/packages/astro/test/fixtures/core-image/src/pages/inlineImport.astro
new file mode 100644
index 000000000..323587993
--- /dev/null
+++ b/packages/astro/test/fixtures/core-image/src/pages/inlineImport.astro
@@ -0,0 +1,7 @@
+---
+import { getImage } from "astro:assets";
+
+const optimizedImage = await getImage({src: import('../assets/penguin1.jpg')})
+---
+
+<img src={optimizedImage.src} {...optimizedImage.attributes} />
diff --git a/packages/astro/test/middleware.test.js b/packages/astro/test/middleware.test.js
index 1ed857d5b..3796a341f 100644
--- a/packages/astro/test/middleware.test.js
+++ b/packages/astro/test/middleware.test.js
@@ -118,13 +118,7 @@ describe('Middleware API in PROD mode, SSR', () => {
fixture = await loadFixture({
root: './fixtures/middleware-dev/',
output: 'server',
- adapter: testAdapter({
- setEntryPoints(entryPointsOrMiddleware) {
- if (entryPointsOrMiddleware instanceof URL) {
- middlewarePath = entryPointsOrMiddleware;
- }
- },
- }),
+ adapter: testAdapter({}),
});
await fixture.build();
});
@@ -218,6 +212,21 @@ describe('Middleware API in PROD mode, SSR', () => {
});
it('the integration should receive the path to the middleware', async () => {
+ fixture = await loadFixture({
+ root: './fixtures/middleware-dev/',
+ output: 'server',
+ build: {
+ excludeMiddleware: true,
+ },
+ adapter: testAdapter({
+ setEntryPoints(entryPointsOrMiddleware) {
+ if (entryPointsOrMiddleware instanceof URL) {
+ middlewarePath = entryPointsOrMiddleware;
+ }
+ },
+ }),
+ });
+ await fixture.build();
expect(middlewarePath).to.not.be.undefined;
try {
const path = fileURLToPath(middlewarePath);
diff --git a/packages/create-astro/CHANGELOG.md b/packages/create-astro/CHANGELOG.md
index 76642c962..519133c71 100644
--- a/packages/create-astro/CHANGELOG.md
+++ b/packages/create-astro/CHANGELOG.md
@@ -6,6 +6,12 @@
- [`1eae2e3f7`](https://github.com/withastro/astro/commit/1eae2e3f7d693c9dfe91c8ccfbe606d32bf2fb81) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Remove support for Node 16. The lowest supported version by Astro and all integrations is now v18.14.1. As a reminder, Node 16 will be deprecated on the 11th September 2023.
+## 3.1.13
+
+### Patch Changes
+
+- [#8028](https://github.com/withastro/astro/pull/8028) [`8292c4131`](https://github.com/withastro/astro/commit/8292c41311ec41d9d50921fbb2bdeed69e039443) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Improve yarn berry support
+
## 3.1.12
### Patch Changes
diff --git a/packages/create-astro/src/actions/dependencies.ts b/packages/create-astro/src/actions/dependencies.ts
index ed19fe485..339e3379f 100644
--- a/packages/create-astro/src/actions/dependencies.ts
+++ b/packages/create-astro/src/actions/dependencies.ts
@@ -1,6 +1,8 @@
import { color } from '@astrojs/cli-kit';
-import { execa } from 'execa';
+import fs from 'node:fs';
+import path from 'node:path';
import { error, info, spinner, title } from '../messages.js';
+import { shell } from '../shell.js';
import type { Context } from './context';
export async function dependencies(
@@ -46,10 +48,12 @@ export async function dependencies(
}
async function install({ pkgManager, cwd }: { pkgManager: string; cwd: string }) {
- const installExec = execa(pkgManager, ['install'], { cwd });
- return new Promise<void>((resolve, reject) => {
- setTimeout(() => reject(`Request timed out after one minute`), 60_000);
- installExec.on('error', (e) => reject(e));
- installExec.on('close', () => resolve());
- });
+ if (pkgManager === 'yarn') await ensureYarnLock({ cwd });
+ return shell(pkgManager, ['install'], { cwd, timeout: 90_000, stdio: 'ignore' });
+}
+
+async function ensureYarnLock({ cwd }: { cwd: string }) {
+ const yarnLock = path.join(cwd, 'yarn.lock');
+ if (fs.existsSync(yarnLock)) return;
+ return fs.promises.writeFile(yarnLock, '', { encoding: 'utf-8' });
}
diff --git a/packages/create-astro/src/actions/git.ts b/packages/create-astro/src/actions/git.ts
index 00c42dae5..c2a59b0b3 100644
--- a/packages/create-astro/src/actions/git.ts
+++ b/packages/create-astro/src/actions/git.ts
@@ -3,8 +3,8 @@ import path from 'node:path';
import type { Context } from './context';
import { color } from '@astrojs/cli-kit';
-import { execa } from 'execa';
import { error, info, spinner, title } from '../messages.js';
+import { shell } from '../shell.js';
export async function git(ctx: Pick<Context, 'cwd' | 'git' | 'yes' | 'prompt' | 'dryRun'>) {
if (fs.existsSync(path.join(ctx.cwd, '.git'))) {
@@ -45,9 +45,9 @@ export async function git(ctx: Pick<Context, 'cwd' | 'git' | 'yes' | 'prompt' |
async function init({ cwd }: { cwd: string }) {
try {
- await execa('git', ['init'], { cwd, stdio: 'ignore' });
- await execa('git', ['add', '-A'], { cwd, stdio: 'ignore' });
- await execa(
+ await shell('git', ['init'], { cwd, stdio: 'ignore' });
+ await shell('git', ['add', '-A'], { cwd, stdio: 'ignore' });
+ await shell(
'git',
[
'commit',
diff --git a/packages/create-astro/src/messages.ts b/packages/create-astro/src/messages.ts
index ee67f3b19..89ccddcdb 100644
--- a/packages/create-astro/src/messages.ts
+++ b/packages/create-astro/src/messages.ts
@@ -1,11 +1,11 @@
/* eslint no-console: 'off' */
import { color, say as houston, label, spinner as load } from '@astrojs/cli-kit';
import { align, sleep } from '@astrojs/cli-kit/utils';
-import { execa } from 'execa';
import fetch from 'node-fetch-native';
import { exec } from 'node:child_process';
import stripAnsi from 'strip-ansi';
import detectPackageManager from 'which-pm-runs';
+import { shell } from './shell.js';
// Users might lack access to the global npm registry, this function
// checks the user's project type and will return the proper npm registry
@@ -14,7 +14,7 @@ import detectPackageManager from 'which-pm-runs';
async function getRegistry(): Promise<string> {
const packageManager = detectPackageManager()?.name || 'npm';
try {
- const { stdout } = await execa(packageManager, ['config', 'get', 'registry']);
+ const { stdout } = await shell(packageManager, ['config', 'get', 'registry']);
return stdout?.trim()?.replace(/\/$/, '') || 'https://registry.npmjs.org';
} catch (e) {
return 'https://registry.npmjs.org';
diff --git a/packages/create-astro/src/shell.ts b/packages/create-astro/src/shell.ts
new file mode 100644
index 000000000..d2d7ef033
--- /dev/null
+++ b/packages/create-astro/src/shell.ts
@@ -0,0 +1,49 @@
+// This is an extremely simplified version of [`execa`](https://github.com/sindresorhus/execa)
+// intended to keep our dependency size down
+import type { StdioOptions } from 'node:child_process';
+import type { Readable } from 'node:stream';
+
+import { spawn } from 'node:child_process';
+import { text as textFromStream } from 'node:stream/consumers';
+import { setTimeout as sleep } from 'node:timers/promises';
+
+export interface ExecaOptions {
+ cwd?: string | URL;
+ stdio?: StdioOptions;
+ timeout?: number;
+}
+export interface Output {
+ stdout: string;
+ stderr: string;
+ exitCode: number;
+}
+const text = (stream: NodeJS.ReadableStream | Readable | null) =>
+ stream ? textFromStream(stream).then((t) => t.trimEnd()) : '';
+
+export async function shell(
+ command: string,
+ flags: string[],
+ opts: ExecaOptions = {}
+): Promise<Output> {
+ const controller = opts.timeout ? new AbortController() : undefined;
+ const child = spawn(command, flags, {
+ cwd: opts.cwd,
+ shell: true,
+ stdio: opts.stdio,
+ signal: controller?.signal,
+ });
+ const stdout = await text(child.stdout);
+ const stderr = await text(child.stderr);
+ if (opts.timeout) {
+ sleep(opts.timeout).then(() => {
+ controller!.abort();
+ throw { stdout, stderr, exitCode: 1 };
+ });
+ }
+ await new Promise((resolve) => child.on('exit', resolve));
+ const { exitCode } = child;
+ if (exitCode !== 0) {
+ throw { stdout, stderr, exitCode };
+ }
+ return { stdout, stderr, exitCode };
+}
diff --git a/packages/create-astro/test/fixtures/not-empty/git.json b/packages/create-astro/test/fixtures/not-empty/git.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/packages/create-astro/test/fixtures/not-empty/git.json
@@ -0,0 +1 @@
+{} \ No newline at end of file
diff --git a/packages/create-astro/test/git.test.js b/packages/create-astro/test/git.test.js
index c8fa86e86..d05ad5bdc 100644
--- a/packages/create-astro/test/git.test.js
+++ b/packages/create-astro/test/git.test.js
@@ -1,7 +1,6 @@
import { expect } from 'chai';
-
-import { execa } from 'execa';
-import fs from 'node:fs';
+import { mkdir, writeFile } from 'node:fs/promises';
+import { rmSync } from 'node:fs';
import { git } from '../dist/index.js';
import { setup } from './utils.js';
@@ -16,22 +15,6 @@ describe('git', () => {
expect(fixture.hasMessage('Skipping Git initialization')).to.be.true;
});
- it('already initialized', async () => {
- const context = {
- git: true,
- cwd: './test/fixtures/not-empty',
- dryRun: true,
- prompt: () => ({ git: false }),
- };
- await execa('git', ['init'], { cwd: './test/fixtures/not-empty' });
- await git(context);
-
- expect(fixture.hasMessage('Git has already been initialized')).to.be.true;
-
- // Cleanup
- fs.rmSync('./test/fixtures/not-empty/.git', { recursive: true, force: true });
- });
-
it('yes (--dry-run)', async () => {
const context = { cwd: '', dryRun: true, prompt: () => ({ git: true }) };
await git(context);
@@ -46,3 +29,29 @@ describe('git', () => {
expect(fixture.hasMessage('Skipping Git initialization')).to.be.true;
});
});
+
+describe('git initialized', () => {
+ const fixture = setup();
+ const dir = new URL(new URL('./fixtures/not-empty/.git', import.meta.url));
+
+ before(async () => {
+ await mkdir(dir, { recursive: true });
+ await writeFile(new URL('./git.json', dir), '{}', { encoding: 'utf8' });
+ });
+
+ it('already initialized', async () => {
+ const context = {
+ git: true,
+ cwd: './test/fixtures/not-empty',
+ dryRun: false,
+ prompt: () => ({ git: false }),
+ };
+ await git(context);
+
+ expect(fixture.hasMessage('Git has already been initialized')).to.be.true;
+ });
+
+ after(() => {
+ rmSync(dir, { recursive: true, force: true });
+ });
+});
diff --git a/packages/integrations/cloudflare/CHANGELOG.md b/packages/integrations/cloudflare/CHANGELOG.md
index 74c6f8c43..6111c047e 100644
--- a/packages/integrations/cloudflare/CHANGELOG.md
+++ b/packages/integrations/cloudflare/CHANGELOG.md
@@ -52,6 +52,35 @@
- astro@3.0.0-beta.0
- @astrojs/underscore-redirects@0.3.0-beta.0
+## 6.8.0
+
+### Minor Changes
+
+- [#7541](https://github.com/withastro/astro/pull/7541) [`ffcfcddb7`](https://github.com/withastro/astro/commit/ffcfcddb7575030d62b4ef979d46a74425e6d3fe) Thanks [@alexanderniebuhr](https://github.com/alexanderniebuhr)! - The `getRuntime` utility has been deprecated and should be updated to the new [`Astro.locals`](https://docs.astro.build/en/guides/middleware/#locals) API.
+
+ ```diff
+ - import { getRuntime } from '@astrojs/cloudflare/runtime';
+ - getRuntime(Astro.request);
+
+ + const runtime = Astro.locals.runtime;
+ ```
+
+### Patch Changes
+
+- Updated dependencies [[`1b8d30209`](https://github.com/withastro/astro/commit/1b8d3020990130dabfaaf753db73a32c6e0c896a), [`405913cdf`](https://github.com/withastro/astro/commit/405913cdf20b26407aa351c090f0a0859a4e6f54), [`87d4b1843`](https://github.com/withastro/astro/commit/87d4b18437c7565c48cad4bea81831c2a244ebb8), [`c23377caa`](https://github.com/withastro/astro/commit/c23377caafbc75deb91c33b9678c1b6868ad40ea), [`86bee2812`](https://github.com/withastro/astro/commit/86bee2812185df6e14025e5962a335f51853587b)]:
+ - astro@2.10.6
+
+## 6.7.0
+
+### Minor Changes
+
+- [#7846](https://github.com/withastro/astro/pull/7846) [`ea30a9d4f`](https://github.com/withastro/astro/commit/ea30a9d4f2d7a12345869e971f3051cf803dbe74) Thanks [@schummar](https://github.com/schummar)! - More efficient \_routes.json
+
+### Patch Changes
+
+- Updated dependencies [[`5b1e39ef6`](https://github.com/withastro/astro/commit/5b1e39ef6ec6dcebea96584f95d9530bd9aa715d)]:
+ - astro@2.10.5
+
## 6.6.2
### Patch Changes
diff --git a/packages/integrations/cloudflare/README.md b/packages/integrations/cloudflare/README.md
index 45f8e01ba..61db6effc 100644
--- a/packages/integrations/cloudflare/README.md
+++ b/packages/integrations/cloudflare/README.md
@@ -73,12 +73,10 @@ It's then possible to update the preview script in your `package.json` to `"prev
## Access to the Cloudflare runtime
-You can access all the Cloudflare bindings and environment variables from Astro components and API routes through the adapter API.
+You can access all the Cloudflare bindings and environment variables from Astro components and API routes through `Astro.locals`.
```js
-import { getRuntime } from '@astrojs/cloudflare/runtime';
-
-getRuntime(Astro.request);
+const env = Astro.locals.runtime.env;
```
Depending on your adapter mode (advanced = worker, directory = pages), the runtime object will look a little different due to differences in the Cloudflare API.
diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts
index c378a195f..6b64a0a1f 100644
--- a/packages/integrations/cloudflare/src/index.ts
+++ b/packages/integrations/cloudflare/src/index.ts
@@ -230,7 +230,7 @@ export default function createIntegration(args?: Options): AstroIntegration {
}
}
- // // // throw the server folder in the bin
+ // throw the server folder in the bin
const serverUrl = new URL(_buildConfig.server);
await fs.promises.rm(serverUrl, { recursive: true, force: true });
diff --git a/packages/integrations/cloudflare/src/runtime.ts b/packages/integrations/cloudflare/src/runtime.ts
index cd3dfff47..03c15d4a3 100644
--- a/packages/integrations/cloudflare/src/runtime.ts
+++ b/packages/integrations/cloudflare/src/runtime.ts
@@ -1,3 +1,4 @@
+// TODO: remove `getRuntime()` in Astro 3.0
import type { Cache, CacheStorage, IncomingRequestCfProperties } from '@cloudflare/workers-types';
export type WorkerRuntime<T = unknown> = {
@@ -21,6 +22,16 @@ export type PagesRuntime<T = unknown, U = unknown> = {
cf?: IncomingRequestCfProperties;
};
+/**
+ * @deprecated since version 6.8.0
+ * The `getRuntime` utility has been deprecated and should be updated to the new [`Astro.locals`](https://docs.astro.build/en/guides/middleware/#locals) API.
+ * ```diff
+ * - import { getRuntime } from '@astrojs/cloudflare/runtime';
+ * - getRuntime(Astro.request);
+ *
+ * + const runtime = Astro.locals.runtime;
+ * ```
+ */
export function getRuntime<T = unknown, U = unknown>(
request: Request
): WorkerRuntime<T> | PagesRuntime<T, U> {
diff --git a/packages/integrations/cloudflare/src/server.advanced.ts b/packages/integrations/cloudflare/src/server.advanced.ts
index 9758b8b19..175756d6a 100644
--- a/packages/integrations/cloudflare/src/server.advanced.ts
+++ b/packages/integrations/cloudflare/src/server.advanced.ts
@@ -12,10 +12,21 @@ type Env = {
name: string;
};
+interface WorkerRuntime {
+ runtime: {
+ waitUntil: (promise: Promise<any>) => void;
+ env: Env;
+ cf: CFRequest['cf'];
+ caches: typeof caches;
+ };
+}
+
export function createExports(manifest: SSRManifest) {
const app = new App(manifest);
const fetch = async (request: Request & CFRequest, env: Env, context: ExecutionContext) => {
+ // TODO: remove this any cast in the future
+ // REF: the type cast to any is needed because the Cloudflare Env Type is not assignable to type 'ProcessEnv'
process.env = env as any;
const { pathname } = new URL(request.url);
@@ -32,6 +43,9 @@ export function createExports(manifest: SSRManifest) {
Symbol.for('astro.clientAddress'),
request.headers.get('cf-connecting-ip')
);
+
+ // `getRuntime()` is deprecated, currently available additionally to new Astro.locals.runtime
+ // TODO: remove `getRuntime()` in Astro 3.0
Reflect.set(request, Symbol.for('runtime'), {
env,
name: 'cloudflare',
@@ -42,7 +56,19 @@ export function createExports(manifest: SSRManifest) {
context.waitUntil(promise);
},
});
- let response = await app.render(request, routeData);
+
+ const locals: WorkerRuntime = {
+ runtime: {
+ waitUntil: (promise: Promise<any>) => {
+ context.waitUntil(promise);
+ },
+ env: env,
+ cf: request.cf,
+ caches: caches,
+ },
+ };
+
+ let response = await app.render(request, routeData, locals);
if (app.setCookieHeaders) {
for (const setCookieHeader of app.setCookieHeaders(response)) {
diff --git a/packages/integrations/cloudflare/src/server.directory.ts b/packages/integrations/cloudflare/src/server.directory.ts
index 3e9531a56..d4e4094de 100644
--- a/packages/integrations/cloudflare/src/server.directory.ts
+++ b/packages/integrations/cloudflare/src/server.directory.ts
@@ -7,28 +7,30 @@ if (!isNode) {
process.env = getProcessEnvProxy();
}
+interface FunctionRuntime {
+ runtime: {
+ waitUntil: (promise: Promise<any>) => void;
+ env: EventContext<unknown, string, unknown>['env'];
+ cf: CFRequest['cf'];
+ caches: typeof caches;
+ };
+}
+
export function createExports(manifest: SSRManifest) {
const app = new App(manifest);
- const onRequest = async ({
- request,
- next,
- ...runtimeEnv
- }: {
- request: Request & CFRequest;
- next: (request: Request) => void;
- waitUntil: EventContext<unknown, any, unknown>['waitUntil'];
- } & Record<string, unknown>) => {
- process.env = runtimeEnv.env as any;
+ const onRequest = async (context: EventContext<unknown, string, unknown>) => {
+ const request = context.request as CFRequest & Request;
+ const { next, env } = context;
+
+ // TODO: remove this any cast in the future
+ // REF: the type cast to any is needed because the Cloudflare Env Type is not assignable to type 'ProcessEnv'
+ process.env = env as any;
const { pathname } = new URL(request.url);
// static assets fallback, in case default _routes.json is not used
if (manifest.assets.has(pathname)) {
- // we need this so the page does not error
- // https://developers.cloudflare.com/pages/platform/functions/advanced-mode/#set-up-a-function
- return (runtimeEnv.env as EventContext<unknown, string, unknown>['env']).ASSETS.fetch(
- request
- );
+ return env.ASSETS.fetch(request);
}
let routeData = app.match(request, { matchNotFound: true });
@@ -38,17 +40,32 @@ export function createExports(manifest: SSRManifest) {
Symbol.for('astro.clientAddress'),
request.headers.get('cf-connecting-ip')
);
+
+ // `getRuntime()` is deprecated, currently available additionally to new Astro.locals.runtime
+ // TODO: remove `getRuntime()` in Astro 3.0
Reflect.set(request, Symbol.for('runtime'), {
- ...runtimeEnv,
+ ...context,
waitUntil: (promise: Promise<any>) => {
- runtimeEnv.waitUntil(promise);
+ context.waitUntil(promise);
},
name: 'cloudflare',
next,
caches,
cf: request.cf,
});
- let response = await app.render(request, routeData);
+
+ const locals: FunctionRuntime = {
+ runtime: {
+ waitUntil: (promise: Promise<any>) => {
+ context.waitUntil(promise);
+ },
+ env: context.env,
+ cf: request.cf,
+ caches: caches,
+ },
+ };
+
+ let response = await app.render(request, routeData, locals);
if (app.setCookieHeaders) {
for (const setCookieHeader of app.setCookieHeaders(response)) {
diff --git a/packages/integrations/cloudflare/test/cf.test.js b/packages/integrations/cloudflare/test/cf.test.js
index 559df5c76..f8ab9c02f 100644
--- a/packages/integrations/cloudflare/test/cf.test.js
+++ b/packages/integrations/cloudflare/test/cf.test.js
@@ -17,7 +17,7 @@ describe('Cf metadata and caches', () => {
});
await fixture.build();
- cli = runCLI('./fixtures/cf/', { silent: true, port: 8788 });
+ cli = runCLI('./fixtures/cf/', { silent: false, port: 8788 });
await cli.ready;
});
diff --git a/packages/integrations/cloudflare/test/fixtures/runtime/astro.config.mjs b/packages/integrations/cloudflare/test/fixtures/runtime/astro.config.mjs
new file mode 100644
index 000000000..f92829843
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/runtime/astro.config.mjs
@@ -0,0 +1,8 @@
+import { defineConfig } from 'astro/config';
+import cloudflare from '@astrojs/cloudflare';
+
+
+export default defineConfig({
+ adapter: cloudflare(),
+ output: 'server',
+});
diff --git a/packages/integrations/cloudflare/test/fixtures/runtime/package.json b/packages/integrations/cloudflare/test/fixtures/runtime/package.json
new file mode 100644
index 000000000..71ac16647
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/runtime/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@test/astro-cloudflare-runtime",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "@astrojs/cloudflare": "workspace:*",
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/integrations/cloudflare/test/fixtures/runtime/src/pages/index.astro b/packages/integrations/cloudflare/test/fixtures/runtime/src/pages/index.astro
new file mode 100644
index 000000000..320e8e162
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/runtime/src/pages/index.astro
@@ -0,0 +1,15 @@
+---
+const runtime = Astro.locals.runtime;
+const env = runtime.env;
+---
+<html>
+ <head>
+ <title>Testing</title>
+ </head>
+ <body>
+ <h1>Testing</h1>
+ <div id="cf">{JSON.stringify(runtime.cf)}</div>
+ <div id="env">{JSON.stringify(env)}</div>
+ <div id="hasCache">{!!runtime.caches}</div>
+ </body>
+</html>
diff --git a/packages/integrations/cloudflare/test/runtime.test.js b/packages/integrations/cloudflare/test/runtime.test.js
new file mode 100644
index 000000000..243c1dd67
--- /dev/null
+++ b/packages/integrations/cloudflare/test/runtime.test.js
@@ -0,0 +1,38 @@
+import { loadFixture, runCLI } from './test-utils.js';
+import { expect } from 'chai';
+import * as cheerio from 'cheerio';
+import cloudflare from '../dist/index.js';
+
+describe('Runtime Locals', () => {
+ /** @type {import('./test-utils.js').Fixture} */
+ let fixture;
+ /** @type {import('./test-utils.js').WranglerCLI} */
+ let cli;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/runtime/',
+ output: 'server',
+ adapter: cloudflare(),
+ });
+ await fixture.build();
+
+ cli = runCLI('./fixtures/runtime/', { silent: true, port: 8793 });
+ await cli.ready;
+ });
+
+ after(async () => {
+ await cli.stop();
+ });
+
+ it('has CF and Caches', async () => {
+ let res = await fetch(`http://localhost:8793/`);
+ expect(res.status).to.equal(200);
+ let html = await res.text();
+ let $ = cheerio.load(html);
+ expect($('#cf').text()).to.contain('city');
+ expect($('#env').text()).to.contain('SECRET_STUFF');
+ expect($('#env').text()).to.contain('secret');
+ expect($('#hasCache').text()).to.equal('true');
+ });
+});
diff --git a/packages/integrations/netlify/CHANGELOG.md b/packages/integrations/netlify/CHANGELOG.md
index a3ec39ce2..0d1e64302 100644
--- a/packages/integrations/netlify/CHANGELOG.md
+++ b/packages/integrations/netlify/CHANGELOG.md
@@ -106,6 +106,32 @@
- astro@3.0.0-beta.0
- @astrojs/underscore-redirects@0.3.0-beta.0
+## 2.6.0
+
+### Minor Changes
+
+- [#7975](https://github.com/withastro/astro/pull/7975) [`f974c95a2`](https://github.com/withastro/astro/commit/f974c95a27ccbf91adbc66f6f1433f4cf11be33e) Thanks [@lilnasy](https://github.com/lilnasy)! - If you are using Netlify's On-demand Builders, you can now specify how long your pages should remain cached. By default, all pages will be rendered on first visit and reused on every subsequent visit until a redeploy. To set a custom revalidation time, call the `runtime.setBuildersTtl()` local in either your frontmatter or middleware.
+
+ ```astro
+ ---
+ import Layout from '../components/Layout.astro';
+
+ if (import.meta.env.PROD) {
+ // revalidates every 45 seconds
+ Astro.locals.runtime.setBuildersTtl(45);
+ }
+ ---
+
+ <Layout title="Astro on Netlify">
+ {new Date(Date.now())}
+ </Layout>
+ ```
+
+### Patch Changes
+
+- Updated dependencies [[`1b8d30209`](https://github.com/withastro/astro/commit/1b8d3020990130dabfaaf753db73a32c6e0c896a), [`405913cdf`](https://github.com/withastro/astro/commit/405913cdf20b26407aa351c090f0a0859a4e6f54), [`87d4b1843`](https://github.com/withastro/astro/commit/87d4b18437c7565c48cad4bea81831c2a244ebb8), [`c23377caa`](https://github.com/withastro/astro/commit/c23377caafbc75deb91c33b9678c1b6868ad40ea), [`86bee2812`](https://github.com/withastro/astro/commit/86bee2812185df6e14025e5962a335f51853587b)]:
+ - astro@2.10.6
+
## 2.5.2
### Patch Changes
diff --git a/packages/integrations/netlify/README.md b/packages/integrations/netlify/README.md
index 91a0c41d8..8b652180b 100644
--- a/packages/integrations/netlify/README.md
+++ b/packages/integrations/netlify/README.md
@@ -144,6 +144,30 @@ Once you run `astro build` there will be a `dist/_redirects` file. Netlify will
> **Note**
> You can still include a `public/_redirects` file for manual redirects. Any redirects you specify in the redirects config are appended to the end of your own.
+### On-demand Builders
+
+[Netlify On-demand Builders](https://docs.netlify.com/configure-builds/on-demand-builders/) are serverless functions used to generate web content as needed that’s automatically cached on Netlify’s Edge CDN. You can enable these functions using the [`builders` configuration](#builders).
+
+By default, all pages will be rendered on first visit and the rendered result will be reused for every subsequent visit until you redeploy. To set a revalidation time, call the [`runtime.setBuildersTtl(ttl)` local](https://docs.astro.build/en/guides/middleware/#locals) with the duration (in seconds).
+
+The following example sets a revalidation time of 45, causing Netlify to store the rendered HTML for 45 seconds.
+
+```astro
+---
+import Layout from '../components/Layout.astro';
+
+if (import.meta.env.PROD) {
+ Astro.locals.runtime.setBuildersTtl(45);
+}
+---
+
+<Layout title="Astro on Netlify">
+ {new Date(Date.now())}
+</Layout>
+```
+
+It is important to note that On-demand Builders ignore query params when checking for cached pages. For example, if `example.com/?x=y` is cached, it will be served for `example.com/?a=b` (different query params) and `example.com/` (no query params) as well.
+
## Usage
[Read the full deployment guide here.](https://docs.astro.build/en/guides/deploy/netlify/)
@@ -188,7 +212,7 @@ directory = "dist/functions"
### builders
-[Netlify On-demand Builders](https://docs.netlify.com/configure-builds/on-demand-builders/) are serverless functions used to build and cache page content on Netlify’s Edge CDN. You can enable these functions with the `builders` option:
+You can enable On-demand Builders using the `builders` option:
```js
// astro.config.mjs
diff --git a/packages/integrations/netlify/builders-types.d.ts b/packages/integrations/netlify/builders-types.d.ts
new file mode 100644
index 000000000..7c778be4f
--- /dev/null
+++ b/packages/integrations/netlify/builders-types.d.ts
@@ -0,0 +1,9 @@
+interface NetlifyLocals {
+ runtime: {
+ /**
+ * On-demand Builders support an optional time to live (TTL) pattern that allows you to set a fixed duration of time after which a cached builder response is invalidated. This allows you to force a refresh of a builder-generated response without a new deploy.
+ * @param ttl time to live, in seconds
+ */
+ setBuildersTtl(ttl: number): void;
+ };
+}
diff --git a/packages/integrations/netlify/src/netlify-functions.ts b/packages/integrations/netlify/src/netlify-functions.ts
index 3da0718b0..8c051d9f6 100644
--- a/packages/integrations/netlify/src/netlify-functions.ts
+++ b/packages/integrations/netlify/src/netlify-functions.ts
@@ -68,18 +68,32 @@ export const createExports = (manifest: SSRManifest, args: Args) => {
init.body =
typeof requestBody === 'string' ? Buffer.from(requestBody, encoding) : requestBody;
}
+
const request = new Request(rawUrl, init);
const routeData = app.match(request);
const ip = headers['x-nf-client-connection-ip'];
Reflect.set(request, clientAddressSymbol, ip);
- let locals = {};
+
+ let locals: Record<string, unknown> = {};
+
if (request.headers.has(ASTRO_LOCALS_HEADER)) {
let localsAsString = request.headers.get(ASTRO_LOCALS_HEADER);
if (localsAsString) {
locals = JSON.parse(localsAsString);
}
}
+
+ let responseTtl = undefined;
+
+ locals.runtime = builders
+ ? {
+ setBuildersTtl(ttl: number) {
+ responseTtl = ttl;
+ },
+ }
+ : {};
+
const response: Response = await app.render(request, routeData, locals);
const responseHeaders = Object.fromEntries(response.headers.entries());
@@ -99,6 +113,7 @@ export const createExports = (manifest: SSRManifest, args: Args) => {
headers: responseHeaders,
body: responseBody,
isBase64Encoded: responseIsBase64Encoded,
+ ttl: responseTtl,
};
const cookies = response.headers.get('set-cookie');
diff --git a/packages/integrations/netlify/test/functions/builders.test.js b/packages/integrations/netlify/test/functions/builders.test.js
new file mode 100644
index 000000000..d47af92c0
--- /dev/null
+++ b/packages/integrations/netlify/test/functions/builders.test.js
@@ -0,0 +1,37 @@
+import { expect } from 'chai';
+import { loadFixture, testIntegration } from './test-utils.js';
+import netlifyAdapter from '../../dist/index.js';
+
+describe('Builders', () => {
+ /** @type {import('../../../astro/test/test-utils').Fixture} */
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/builders/', import.meta.url).toString(),
+ output: 'server',
+ adapter: netlifyAdapter({
+ dist: new URL('./fixtures/builders/dist/', import.meta.url),
+ builders: true,
+ }),
+ site: `http://example.com`,
+ integrations: [testIntegration()],
+ });
+ await fixture.build();
+ });
+
+ it('A route can set builders ttl', async () => {
+ const entryURL = new URL(
+ './fixtures/builders/.netlify/functions-internal/entry.mjs',
+ import.meta.url
+ );
+ const { handler } = await import(entryURL);
+ const resp = await handler({
+ httpMethod: 'GET',
+ headers: {},
+ rawUrl: 'http://example.com/',
+ isBase64Encoded: false,
+ });
+ expect(resp.ttl).to.equal(45);
+ });
+});
diff --git a/packages/integrations/netlify/test/functions/fixtures/builders/src/pages/index.astro b/packages/integrations/netlify/test/functions/fixtures/builders/src/pages/index.astro
new file mode 100644
index 000000000..ab8853785
--- /dev/null
+++ b/packages/integrations/netlify/test/functions/fixtures/builders/src/pages/index.astro
@@ -0,0 +1,11 @@
+---
+Astro.locals.runtime.setBuildersTtl(45)
+---
+<html>
+ <head>
+ <title>Astro on Netlify</title>
+ </head>
+ <body>
+ <h1>{new Date(Date.now())}</h1>
+ </body>
+</html>
diff --git a/packages/integrations/node/CHANGELOG.md b/packages/integrations/node/CHANGELOG.md
index 884eb21f8..01e61307f 100644
--- a/packages/integrations/node/CHANGELOG.md
+++ b/packages/integrations/node/CHANGELOG.md
@@ -39,6 +39,15 @@
- Updated dependencies [[`1eae2e3f7`](https://github.com/withastro/astro/commit/1eae2e3f7d693c9dfe91c8ccfbe606d32bf2fb81), [`76ddef19c`](https://github.com/withastro/astro/commit/76ddef19ccab6e5f7d3a5740cd41acf10e334b38), [`9b4f70a62`](https://github.com/withastro/astro/commit/9b4f70a629f55e461759ba46f68af7097a2e9215), [`3fdf509b2`](https://github.com/withastro/astro/commit/3fdf509b2731a9b2f972d89291e57cf78d62c769), [`2f951cd40`](https://github.com/withastro/astro/commit/2f951cd403dfcc2c3ca6aae618ae3e1409516e32), [`c022a4217`](https://github.com/withastro/astro/commit/c022a4217a805d223c1494e9eda4e48bbf810388), [`67becaa58`](https://github.com/withastro/astro/commit/67becaa580b8f787df58de66b7008b7098f1209c), [`bc37331d8`](https://github.com/withastro/astro/commit/bc37331d8154e3e95a8df9131e4e014e78a7a9e7), [`dfc2d93e3`](https://github.com/withastro/astro/commit/dfc2d93e3c645995379358fabbdfa9aab99f43d8), [`3dc1ca2fa`](https://github.com/withastro/astro/commit/3dc1ca2fac8d9965cc5085a5d09e72ed87b4281a), [`1be84dfee`](https://github.com/withastro/astro/commit/1be84dfee3ce8e6f5cc624f99aec4e980f6fde37), [`35f01df79`](https://github.com/withastro/astro/commit/35f01df797d23315f2bee2fc3fd795adb0559c58), [`3fdf509b2`](https://github.com/withastro/astro/commit/3fdf509b2731a9b2f972d89291e57cf78d62c769), [`78de801f2`](https://github.com/withastro/astro/commit/78de801f21fd4ca1653950027d953bf08614566b), [`59d6e569f`](https://github.com/withastro/astro/commit/59d6e569f63e175c97e82e94aa7974febfb76f7c), [`7723c4cc9`](https://github.com/withastro/astro/commit/7723c4cc93298c2e6530e55da7afda048f22cf81), [`fb5cd6b56`](https://github.com/withastro/astro/commit/fb5cd6b56dc27a71366ed5e1ab8bfe9b8f96bac5), [`631b9c410`](https://github.com/withastro/astro/commit/631b9c410d5d66fa384674027ba95d69ebb5063f)]:
- astro@3.0.0-beta.0
+## 5.3.3
+
+### Patch Changes
+
+- [#6928](https://github.com/withastro/astro/pull/6928) [`b16cb787f`](https://github.com/withastro/astro/commit/b16cb787fd16ebaaf860d8bb183789caf01c0fb7) Thanks [@JerryWu1234](https://github.com/JerryWu1234)! - Support the `--host` flag when running the standalone server (also works for `astro preview --host`)
+
+- Updated dependencies [[`1b8d30209`](https://github.com/withastro/astro/commit/1b8d3020990130dabfaaf753db73a32c6e0c896a), [`405913cdf`](https://github.com/withastro/astro/commit/405913cdf20b26407aa351c090f0a0859a4e6f54), [`87d4b1843`](https://github.com/withastro/astro/commit/87d4b18437c7565c48cad4bea81831c2a244ebb8), [`c23377caa`](https://github.com/withastro/astro/commit/c23377caafbc75deb91c33b9678c1b6868ad40ea), [`86bee2812`](https://github.com/withastro/astro/commit/86bee2812185df6e14025e5962a335f51853587b)]:
+ - astro@2.10.6
+
## 5.3.2
### Patch Changes
diff --git a/packages/integrations/node/src/get-network-address.ts b/packages/integrations/node/src/get-network-address.ts
new file mode 100644
index 000000000..3834c7617
--- /dev/null
+++ b/packages/integrations/node/src/get-network-address.ts
@@ -0,0 +1,48 @@
+import os from 'os';
+interface NetworkAddressOpt {
+ local: string[];
+ network: string[];
+}
+
+const wildcardHosts = new Set(['0.0.0.0', '::', '0000:0000:0000:0000:0000:0000:0000:0000']);
+type Protocol = 'http' | 'https';
+
+// this code from vite https://github.com/vitejs/vite/blob/d09bbd093a4b893e78f0bbff5b17c7cf7821f403/packages/vite/src/node/utils.ts#L892-L914
+export function getNetworkAddress(
+ protocol: Protocol = 'http',
+ hostname: string | undefined,
+ port: number,
+ base?: string
+) {
+ const NetworkAddress: NetworkAddressOpt = {
+ local: [],
+ network: [],
+ };
+ Object.values(os.networkInterfaces())
+ .flatMap((nInterface) => nInterface ?? [])
+ .filter(
+ (detail) =>
+ detail &&
+ detail.address &&
+ (detail.family === 'IPv4' ||
+ // @ts-expect-error Node 18.0 - 18.3 returns number
+ detail.family === 4)
+ )
+ .forEach((detail) => {
+ let host = detail.address.replace(
+ '127.0.0.1',
+ hostname === undefined || wildcardHosts.has(hostname) ? 'localhost' : hostname
+ );
+ // ipv6 host
+ if (host.includes(':')) {
+ host = `[${host}]`;
+ }
+ const url = `${protocol}://${host}:${port}${base ? base : ''}`;
+ if (detail.address.includes('127.0.0.1')) {
+ NetworkAddress.local.push(url);
+ } else {
+ NetworkAddress.network.push(url);
+ }
+ });
+ return NetworkAddress;
+}
diff --git a/packages/integrations/node/src/preview.ts b/packages/integrations/node/src/preview.ts
index 92f9b86ba..4a4db4632 100644
--- a/packages/integrations/node/src/preview.ts
+++ b/packages/integrations/node/src/preview.ts
@@ -1,6 +1,7 @@
import type { CreatePreviewServer } from 'astro';
import type http from 'node:http';
import { fileURLToPath } from 'node:url';
+import { getNetworkAddress } from './get-network-address.js';
import { createServer } from './http-server.js';
import type { createExports } from './server';
@@ -67,9 +68,17 @@ const preview: CreatePreviewServer = async function ({
},
handler
);
+ const address = getNetworkAddress('http', host, port);
- // eslint-disable-next-line no-console
- console.log(`Preview server listening on http://${host}:${port}`);
+ if (host === undefined) {
+ // eslint-disable-next-line no-console
+ console.log(
+ `Preview server listening on \n local: ${address.local[0]} \t\n network: ${address.network[0]}\n`
+ );
+ } else {
+ // eslint-disable-next-line no-console
+ console.log(`Preview server listening on ${address.local[0]}`);
+ }
return server;
};
diff --git a/packages/integrations/node/src/standalone.ts b/packages/integrations/node/src/standalone.ts
index 68b2cebcd..94dc26758 100644
--- a/packages/integrations/node/src/standalone.ts
+++ b/packages/integrations/node/src/standalone.ts
@@ -2,6 +2,7 @@ import type { NodeApp } from 'astro/app/node';
import https from 'https';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
+import { getNetworkAddress } from './get-network-address.js';
import { createServer } from './http-server.js';
import middleware from './nodeMiddleware.js';
import type { Options } from './types';
@@ -55,9 +56,17 @@ export default function startServer(app: NodeApp, options: Options) {
);
const protocol = server.server instanceof https.Server ? 'https' : 'http';
+ const address = getNetworkAddress(protocol, host, port);
- // eslint-disable-next-line no-console
- console.log(`Server listening on ${protocol}://${host}:${port}`);
+ if (host === undefined) {
+ // eslint-disable-next-line no-console
+ console.log(
+ `Preview server listening on \n local: ${address.local[0]} \t\n network: ${address.network[0]}\n`
+ );
+ } else {
+ // eslint-disable-next-line no-console
+ console.log(`Preview server listening on ${address.local[0]}`);
+ }
return {
server,
diff --git a/packages/integrations/react/client.js b/packages/integrations/react/client.js
index ef5929af1..d8948e7bb 100644
--- a/packages/integrations/react/client.js
+++ b/packages/integrations/react/client.js
@@ -31,7 +31,7 @@ export default (element) =>
}
if (client === 'only') {
return startTransition(() => {
- createRoot(element, renderOptions).render(componentEl);
+ createRoot(element).render(componentEl);
});
}
return startTransition(() => {
diff --git a/packages/integrations/sitemap/README.md b/packages/integrations/sitemap/README.md
index ecf59a12d..b2fb5e9dd 100644
--- a/packages/integrations/sitemap/README.md
+++ b/packages/integrations/sitemap/README.md
@@ -339,7 +339,7 @@ The resulting sitemap looks like this:
## Examples
- The official Astro website uses Astro Sitemap to generate [its sitemap](https://astro.build/sitemap-index.xml).
-- [Browse projects with Astro Sitemap on GitHub](https://github.com/search?q=%22@astrojs/sitemap%22+filename:package.json&type=Code) for more examples!
+- [Browse projects with Astro Sitemap on GitHub](https://github.com/search?q=%22%40astrojs%2Fsitemap%22+path%3Apackage.json&type=Code) for more examples!
## Troubleshooting
diff --git a/packages/integrations/vercel/CHANGELOG.md b/packages/integrations/vercel/CHANGELOG.md
index 42bbd00be..455efb793 100644
--- a/packages/integrations/vercel/CHANGELOG.md
+++ b/packages/integrations/vercel/CHANGELOG.md
@@ -113,6 +113,15 @@
- astro@3.0.0-beta.0
- @astrojs/internal-helpers@0.2.0-beta.0
+## 3.8.1
+
+### Patch Changes
+
+- [#8039](https://github.com/withastro/astro/pull/8039) [`6b57628d1`](https://github.com/withastro/astro/commit/6b57628d128779290db3344bbb6de7282196fb97) Thanks [@matthewp](https://github.com/matthewp)! - Prevent Vercel NFT from scanning /dev
+
+- Updated dependencies [[`1b8d30209`](https://github.com/withastro/astro/commit/1b8d3020990130dabfaaf753db73a32c6e0c896a), [`405913cdf`](https://github.com/withastro/astro/commit/405913cdf20b26407aa351c090f0a0859a4e6f54), [`87d4b1843`](https://github.com/withastro/astro/commit/87d4b18437c7565c48cad4bea81831c2a244ebb8), [`c23377caa`](https://github.com/withastro/astro/commit/c23377caafbc75deb91c33b9678c1b6868ad40ea), [`86bee2812`](https://github.com/withastro/astro/commit/86bee2812185df6e14025e5962a335f51853587b)]:
+ - astro@2.10.6
+
## 3.8.0
### Minor Changes
diff --git a/packages/integrations/vercel/src/lib/nft.ts b/packages/integrations/vercel/src/lib/nft.ts
index 752f87251..10c298a1d 100644
--- a/packages/integrations/vercel/src/lib/nft.ts
+++ b/packages/integrations/vercel/src/lib/nft.ts
@@ -28,6 +28,9 @@ export async function copyDependenciesToFunction({
const { nodeFileTrace } = await import('@vercel/nft');
const result = await nodeFileTrace([entryPath], {
base: fileURLToPath(base),
+ // If you have a route of /dev this appears in source and NFT will try to
+ // scan your local /dev :8
+ ignore: ['/dev/**'],
});
for (const error of result.warnings) {