Merge remote-tracking branch 'origin/main' into next
This commit is contained in:
commit
7530d5689d
73 changed files with 870 additions and 164 deletions
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
'@astrojs/cloudflare': minor
|
||||
---
|
||||
|
||||
More efficient \_routes.json
|
5
.changeset/green-islands-repeat.md
Normal file
5
.changeset/green-islands-repeat.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fix AstroConfigSchema type export
|
5
.changeset/lazy-pillows-burn.md
Normal file
5
.changeset/lazy-pillows-burn.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@astrojs/sitemap': patch
|
||||
---
|
||||
|
||||
docs: fix github search link in README.md
|
5
.changeset/moody-houses-drum.md
Normal file
5
.changeset/moody-houses-drum.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'create-astro': minor
|
||||
---
|
||||
|
||||
Reduce dependency installation size, swap `execa` for light `node:child_process` wrapper
|
5
.changeset/odd-plants-tie.md
Normal file
5
.changeset/odd-plants-tie.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Add support for non-awaited imports to the Image component and `getImage`
|
5
.changeset/olive-queens-drum.md
Normal file
5
.changeset/olive-queens-drum.md
Normal 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 />`
|
5
.changeset/soft-colts-heal.md
Normal file
5
.changeset/soft-colts-heal.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
`astro add` now passes down `--save-prod`, `--save-dev`, `--save-exact`, and `--no-save` flags for installation
|
5
.changeset/thin-plums-drop.md
Normal file
5
.changeset/thin-plums-drop.md
Normal 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
|
|
@ -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 |
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
4
packages/astro/client-base.d.ts
vendored
4
packages/astro/client-base.d.ts
vendored
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
4
packages/astro/content-types.template.d.ts
vendored
4
packages/astro/content-types.template.d.ts
vendored
|
@ -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
|
||||
/**
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*",
|
||||
"@astrojs/node": "workspace:*",
|
||||
"@astrojs/react": "workspace:*",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0"
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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`.
|
||||
*
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
`;
|
|
@ -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) {
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
22
packages/astro/test/fixtures/content/src/pages/sort-blog-collection.astro
vendored
Normal file
22
packages/astro/test/fixtures/content/src/pages/sort-blog-collection.astro
vendored
Normal 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>
|
7
packages/astro/test/fixtures/core-image/src/pages/inlineImport.astro
vendored
Normal file
7
packages/astro/test/fixtures/core-image/src/pages/inlineImport.astro
vendored
Normal 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} />
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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' });
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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';
|
||||
|
|
49
packages/create-astro/src/shell.ts
Normal file
49
packages/create-astro/src/shell.ts
Normal 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 };
|
||||
}
|
1
packages/create-astro/test/fixtures/not-empty/git.json
vendored
Normal file
1
packages/create-astro/test/fixtures/not-empty/git.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -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 });
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 });
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
||||
|
|
8
packages/integrations/cloudflare/test/fixtures/runtime/astro.config.mjs
vendored
Normal file
8
packages/integrations/cloudflare/test/fixtures/runtime/astro.config.mjs
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import cloudflare from '@astrojs/cloudflare';
|
||||
|
||||
|
||||
export default defineConfig({
|
||||
adapter: cloudflare(),
|
||||
output: 'server',
|
||||
});
|
9
packages/integrations/cloudflare/test/fixtures/runtime/package.json
vendored
Normal file
9
packages/integrations/cloudflare/test/fixtures/runtime/package.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "@test/astro-cloudflare-runtime",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "workspace:*",
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
15
packages/integrations/cloudflare/test/fixtures/runtime/src/pages/index.astro
vendored
Normal file
15
packages/integrations/cloudflare/test/fixtures/runtime/src/pages/index.astro
vendored
Normal 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>
|
38
packages/integrations/cloudflare/test/runtime.test.js
Normal file
38
packages/integrations/cloudflare/test/runtime.test.js
Normal 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');
|
||||
});
|
||||
});
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
9
packages/integrations/netlify/builders-types.d.ts
vendored
Normal file
9
packages/integrations/netlify/builders-types.d.ts
vendored
Normal 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;
|
||||
};
|
||||
}
|
|
@ -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');
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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>
|
|
@ -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
|
||||
|
|
48
packages/integrations/node/src/get-network-address.ts
Normal file
48
packages/integrations/node/src/get-network-address.ts
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -31,7 +31,7 @@ export default (element) =>
|
|||
}
|
||||
if (client === 'only') {
|
||||
return startTransition(() => {
|
||||
createRoot(element, renderOptions).render(componentEl);
|
||||
createRoot(element).render(componentEl);
|
||||
});
|
||||
}
|
||||
return startTransition(() => {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue