Merge remote-tracking branch 'origin/main' into next

This commit is contained in:
Emanuele Stoppa 2023-08-15 09:24:06 +01:00
commit 7530d5689d
73 changed files with 870 additions and 164 deletions

View file

@ -1,5 +0,0 @@
---
'@astrojs/cloudflare': minor
---
More efficient \_routes.json

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fix AstroConfigSchema type export

View file

@ -0,0 +1,5 @@
---
'@astrojs/sitemap': patch
---
docs: fix github search link in README.md

View file

@ -0,0 +1,5 @@
---
'create-astro': minor
---
Reduce dependency installation size, swap `execa` for light `node:child_process` wrapper

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Add support for non-awaited imports to the Image component and `getImage`

View file

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

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
`astro add` now passes down `--save-prod`, `--save-dev`, `--save-exact`, and `--no-save` flags for installation

View file

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

View file

@ -1,5 +0,0 @@
---
'astro': patch
---
Move hoisted script analysis optimization behind the `experimental.optimizeHoistedScript` option

Binary file not shown.

Before

Width:  |  Height:  |  Size: 529 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"astro": "workspace:*",
"@astrojs/node": "workspace:*",
"@astrojs/react": "workspace:*",
"react": "^18.1.0",
"react-dom": "^18.1.0"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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`.
*

View file

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

View file

@ -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) => {

View file

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

View file

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

View file

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

View file

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

View file

@ -1,50 +0,0 @@
/**
* CSS is exported as a string so the error pages:
* 1. dont need to resolve a deep internal CSS import
* 2. dont 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;
}
`;

View file

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

View file

@ -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', () => {

View file

@ -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', () => {

View file

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

View file

@ -0,0 +1,7 @@
---
import { getImage } from "astro:assets";
const optimizedImage = await getImage({src: import('../assets/penguin1.jpg')})
---
<img src={optimizedImage.src} {...optimizedImage.attributes} />

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1 @@
{}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,8 @@
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
adapter: cloudflare(),
output: 'server',
});

View file

@ -0,0 +1,9 @@
{
"name": "@test/astro-cloudflare-runtime",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/cloudflare": "workspace:*",
"astro": "workspace:*"
}
}

View file

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

View file

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

View file

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

View file

@ -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 thats automatically cached on Netlifys 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 Netlifys 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -31,7 +31,7 @@ export default (element) =>
}
if (client === 'only') {
return startTransition(() => {
createRoot(element, renderOptions).render(componentEl);
createRoot(element).render(componentEl);
});
}
return startTransition(() => {

View file

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

View file

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

View file

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