Merge branch 'main' into feat/vercel-adapter
This commit is contained in:
commit
7e5f6f1cbb
121 changed files with 1320 additions and 1127 deletions
16
.changeset/forty-coins-attend.md
Normal file
16
.changeset/forty-coins-attend.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
"astro": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Implement RFC [#0017](https://github.com/withastro/rfcs/blob/main/proposals/0017-markdown-content-redesign.md)
|
||||||
|
|
||||||
|
- New Markdown API
|
||||||
|
- New `Astro.glob()` API
|
||||||
|
- **BREAKING CHANGE:** Removed `Astro.fetchContent()` (replaced by `Astro.glob()`)
|
||||||
|
|
||||||
|
```diff
|
||||||
|
// v0.25
|
||||||
|
- let allPosts = Astro.fetchContent('./posts/*.md');
|
||||||
|
// v0.26+
|
||||||
|
+ let allPosts = await Astro.glob('./posts/*.md');
|
||||||
|
```
|
5
.changeset/perfect-dogs-turn.md
Normal file
5
.changeset/perfect-dogs-turn.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Implements the Astro.request RFC
|
61
.changeset/pre.json
Normal file
61
.changeset/pre.json
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"mode": "pre",
|
||||||
|
"tag": "next",
|
||||||
|
"initialVersions": {
|
||||||
|
"@example/blog": "0.0.1",
|
||||||
|
"@example/blog-multiple-authors": "0.0.1",
|
||||||
|
"@example/component": "0.0.1",
|
||||||
|
"@example/my-component-demo": "0.0.1",
|
||||||
|
"@example/my-component": "0.0.1",
|
||||||
|
"@example/docs": "0.0.1",
|
||||||
|
"@example/env-vars": "0.0.1",
|
||||||
|
"@example/framework-alpine": "0.0.1",
|
||||||
|
"@example/framework-lit": "0.0.1",
|
||||||
|
"@example/framework-multiple": "0.0.1",
|
||||||
|
"@example/framework-preact": "0.0.1",
|
||||||
|
"@example/framework-react": "0.0.1",
|
||||||
|
"@example/framework-solid": "0.0.1",
|
||||||
|
"@example/framework-svelte": "0.0.1",
|
||||||
|
"@example/framework-vue": "0.0.1",
|
||||||
|
"@example/integrations-playground": "0.0.1",
|
||||||
|
"@example/minimal": "0.0.1",
|
||||||
|
"@example/non-html-pages": "0.0.1",
|
||||||
|
"@example/portfolio": "0.0.1",
|
||||||
|
"@example/ssr": "0.0.1",
|
||||||
|
"@example/starter": "0.0.1",
|
||||||
|
"@example/subpath": "0.0.1",
|
||||||
|
"@example/with-markdown": "0.0.1",
|
||||||
|
"@example/with-markdown-plugins": "0.0.2",
|
||||||
|
"@example/with-markdown-shiki": "0.0.1",
|
||||||
|
"@example/with-nanostores": "0.0.1",
|
||||||
|
"@example/with-tailwindcss": "0.0.1",
|
||||||
|
"@example/with-vite-plugin-pwa": "0.0.1",
|
||||||
|
"astro": "0.25.4",
|
||||||
|
"@astrojs/prism": "0.4.1",
|
||||||
|
"@test/custom-element-renderer": "0.1.0",
|
||||||
|
"@test/static-build-pkg": "0.0.0",
|
||||||
|
"create-astro": "0.8.0",
|
||||||
|
"@astrojs/lit": "0.0.2",
|
||||||
|
"@astrojs/netlify": "0.0.2",
|
||||||
|
"@astrojs/node": "0.0.2",
|
||||||
|
"@astrojs/partytown": "0.0.2",
|
||||||
|
"@astrojs/preact": "0.0.2",
|
||||||
|
"@astrojs/react": "0.0.2",
|
||||||
|
"@astrojs/sitemap": "0.0.2",
|
||||||
|
"@astrojs/solid-js": "0.0.3",
|
||||||
|
"@astrojs/svelte": "0.0.2",
|
||||||
|
"@astrojs/tailwind": "0.0.2",
|
||||||
|
"@astrojs/turbolinks": "0.0.2",
|
||||||
|
"@astrojs/vue": "0.0.2",
|
||||||
|
"@astrojs/markdown-remark": "0.7.0",
|
||||||
|
"@astrojs/renderer-lit": "0.4.0",
|
||||||
|
"@astrojs/renderer-preact": "0.5.0",
|
||||||
|
"@astrojs/renderer-react": "0.5.0",
|
||||||
|
"@astrojs/renderer-solid": "0.4.0",
|
||||||
|
"@astrojs/renderer-svelte": "0.5.2",
|
||||||
|
"@astrojs/renderer-vue": "0.4.0",
|
||||||
|
"@astrojs/webapi": "0.11.0",
|
||||||
|
"astro-scripts": "0.0.2"
|
||||||
|
},
|
||||||
|
"changesets": []
|
||||||
|
}
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
'astro': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Fix typing of `integrations` array in user config
|
|
5
.changeset/small-radios-remain.md
Normal file
5
.changeset/small-radios-remain.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Update CLI error format and style
|
5
.changeset/stale-walls-whisper.md
Normal file
5
.changeset/stale-walls-whisper.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@example/portfolio': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix import in astro config
|
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/preact": "^0.0.2",
|
"@astrojs/preact": "^0.0.2",
|
||||||
"astro": "^0.25.3",
|
"astro": "^0.25.4",
|
||||||
"sass": "^1.49.9"
|
"sass": "^1.49.9"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -4,6 +4,7 @@ export interface Props {
|
||||||
author: string;
|
author: string;
|
||||||
}
|
}
|
||||||
const { post, author } = Astro.props;
|
const { post, author } = Astro.props;
|
||||||
|
const { frontmatter } = post;
|
||||||
|
|
||||||
function formatDate(date) {
|
function formatDate(date) {
|
||||||
return new Date(date).toUTCString().replace(/(\d\d\d\d) .*/, '$1'); // remove everything after YYYY
|
return new Date(date).toUTCString().replace(/(\d\d\d\d) .*/, '$1'); // remove everything after YYYY
|
||||||
|
@ -12,12 +13,12 @@ function formatDate(date) {
|
||||||
|
|
||||||
<article class="post">
|
<article class="post">
|
||||||
<div class="data">
|
<div class="data">
|
||||||
<h2>{post.title}</h2>
|
<h2>{frontmatter.title}</h2>
|
||||||
<a class="author" href={`/authors/${post.author}`}>{author.name}</a>
|
<a class="author" href={`/authors/${frontmatter.author}`}>{author.name}</a>
|
||||||
<time class="date" datetime={post.date}>{formatDate(post.date)}</time>
|
<time class="date" datetime={frontmatter.date}>{formatDate(frontmatter.date)}</time>
|
||||||
<p class="description">
|
<p class="description">
|
||||||
{post.description}
|
{frontmatter.description}
|
||||||
<a class="link" href={post.url} aria-label={`Read ${post.title}`}>Read</a>
|
<a class="link" href={post.url} aria-label={`Read ${frontmatter.title}`}>Read</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import Nav from '../components/Nav.astro';
|
||||||
import authorData from '../data/authors.json';
|
import authorData from '../data/authors.json';
|
||||||
|
|
||||||
const { content } = Astro.props;
|
const { content } = Astro.props;
|
||||||
let canonicalURL = Astro.request.canonicalURL;
|
let canonicalURL = Astro.canonicalURL;
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang={content.lang || 'en'}>
|
<html lang={content.lang || 'en'}>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import Nav from '../components/Nav.astro';
|
||||||
|
|
||||||
let title = 'About';
|
let title = 'About';
|
||||||
let description = 'About page of an example blog on Astro';
|
let description = 'About page of an example blog on Astro';
|
||||||
let canonicalURL = Astro.request.canonicalURL;
|
let canonicalURL = Astro.canonicalURL;
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
|
@ -2,36 +2,28 @@
|
||||||
import MainHead from '../../components/MainHead.astro';
|
import MainHead from '../../components/MainHead.astro';
|
||||||
import Nav from '../../components/Nav.astro';
|
import Nav from '../../components/Nav.astro';
|
||||||
import PostPreview from '../../components/PostPreview.astro';
|
import PostPreview from '../../components/PostPreview.astro';
|
||||||
import Pagination from '../../components/Pagination.astro';
|
|
||||||
import authorData from '../../data/authors.json';
|
import authorData from '../../data/authors.json';
|
||||||
|
|
||||||
export function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const allPosts = Astro.fetchContent<MarkdownFrontmatter>('../post/*.md');
|
const allPosts = await Astro.glob('../post/*.md');
|
||||||
let allAuthorsUnique = [...new Set(allPosts.map((p) => p.author))];
|
let allAuthorsUnique = [...new Set(allPosts.map((p) => p.frontmatter.author))];
|
||||||
return allAuthorsUnique.map((author) => ({ params: { author }, props: { allPosts } }));
|
return allAuthorsUnique.map((author) => ({ params: { author }, props: { allPosts } }));
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MarkdownFrontmatter {
|
|
||||||
date: number;
|
|
||||||
description: string;
|
|
||||||
title: string;
|
|
||||||
author: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { allPosts } = Astro.props;
|
const { allPosts } = Astro.props;
|
||||||
const { params, canonicalURL } = Astro.request;
|
const { params, canonicalURL } = Astro.request;
|
||||||
const title = 'Don’s Blog';
|
const title = 'Don’s Blog';
|
||||||
const description = 'An example blog on Astro';
|
const description = 'An example blog on Astro';
|
||||||
|
|
||||||
/** filter posts by author, sort by date */
|
/** filter posts by author, sort by date */
|
||||||
const posts = allPosts.filter((post) => post.author === params.author).sort((a, b) => new Date(b.date).valueOf() - new Date(a.date).valueOf());
|
const posts = allPosts.filter((post) => post.frontmatter.author === params.author).sort((a, b) => new Date(b.frontmatter.date).valueOf() - new Date(a.frontmatter.date).valueOf());
|
||||||
const author = authorData[posts[0].author];
|
const author = authorData[posts[0].frontmatter.author];
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<MainHead {title} {description} image={posts[0].image} canonicalURL={canonicalURL.toString()} />
|
<MainHead {title} {description} image={posts[0].frontmatter.image} canonicalURL={canonicalURL.toString()} />
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.title {
|
.title {
|
||||||
|
|
|
@ -6,25 +6,18 @@ import PostPreview from '../components/PostPreview.astro';
|
||||||
import Pagination from '../components/Pagination.astro';
|
import Pagination from '../components/Pagination.astro';
|
||||||
import authorData from '../data/authors.json';
|
import authorData from '../data/authors.json';
|
||||||
|
|
||||||
interface MarkdownFrontmatter {
|
|
||||||
date: number;
|
|
||||||
image: string;
|
|
||||||
author: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Component Script:
|
// Component Script:
|
||||||
// You can write any JavaScript/TypeScript that you'd like here.
|
// You can write any JavaScript/TypeScript that you'd like here.
|
||||||
// It will run during the build, but never in the browser.
|
// It will run during the build, but never in the browser.
|
||||||
// All variables are available to use in the HTML template below.
|
// All variables are available to use in the HTML template below.
|
||||||
let title = 'Don’s Blog';
|
let title = 'Don’s Blog';
|
||||||
let description = 'An example blog on Astro';
|
let description = 'An example blog on Astro';
|
||||||
let canonicalURL = Astro.request.canonicalURL;
|
let canonicalURL = Astro.canonicalURL;
|
||||||
|
|
||||||
// Data Fetching: List all Markdown posts in the repo.
|
// Data Fetching: List all Markdown posts in the repo.
|
||||||
let allPosts = Astro.fetchContent<MarkdownFrontmatter>('./post/*.md');
|
let allPosts = await Astro.glob('./post/*.md');
|
||||||
allPosts.sort((a, b) => new Date(b.date).valueOf() - new Date(a.date).valueOf());
|
allPosts.sort((a, b) => new Date(b.frontmatter.date).valueOf() - new Date(a.frontmatter.date).valueOf());
|
||||||
let firstPage = allPosts.slice(0, 2);
|
let firstPage = allPosts.slice(0, 2);
|
||||||
|
|
||||||
// Full Astro Component Syntax:
|
// Full Astro Component Syntax:
|
||||||
// https://docs.astro.build/core-concepts/astro-components/
|
// https://docs.astro.build/core-concepts/astro-components/
|
||||||
---
|
---
|
||||||
|
@ -32,14 +25,14 @@ let firstPage = allPosts.slice(0, 2);
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<MainHead {title} {description} image={allPosts[0].image} {canonicalURL} />
|
<MainHead {title} {description} image={allPosts[0].frontmatter.image} {canonicalURL} />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<Nav {title} />
|
<Nav {title} />
|
||||||
|
|
||||||
<main class="wrapper">
|
<main class="wrapper">
|
||||||
{allPosts.map((post) => <PostPreview post={post} author={authorData[post.author]} />)}
|
{allPosts.map((post) => <PostPreview post={post} author={authorData[post.frontmatter.author]} />)}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
|
|
|
@ -6,8 +6,8 @@ import Pagination from '../../components/Pagination.astro';
|
||||||
import authorData from '../../data/authors.json';
|
import authorData from '../../data/authors.json';
|
||||||
|
|
||||||
export async function getStaticPaths({ paginate, rss }) {
|
export async function getStaticPaths({ paginate, rss }) {
|
||||||
const allPosts = Astro.fetchContent<MarkdownFrontmatter>('../post/*.md');
|
const allPosts = await Astro.glob('../post/*.md');
|
||||||
const sortedPosts = allPosts.sort((a, b) => new Date(b.date).valueOf() - new Date(a.date).valueOf());
|
const sortedPosts = allPosts.sort((a, b) => new Date(b.frontmatter.date).valueOf() - new Date(a.frontmatter.date).valueOf());
|
||||||
|
|
||||||
// Generate an RSS feed from this collection of posts.
|
// Generate an RSS feed from this collection of posts.
|
||||||
// NOTE: This is disabled by default, since it requires `buildOptions.site` to be set in your "astro.config.mjs" file.
|
// NOTE: This is disabled by default, since it requires `buildOptions.site` to be set in your "astro.config.mjs" file.
|
||||||
|
@ -28,24 +28,16 @@ export async function getStaticPaths({ paginate, rss }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// page
|
// page
|
||||||
let title = 'Don’s Blog';
|
const title = 'Don’s Blog';
|
||||||
let description = 'An example blog on Astro';
|
const description = 'An example blog on Astro';
|
||||||
let canonicalURL = Astro.request.canonicalURL;
|
const { canonicalURL } = Astro;
|
||||||
|
|
||||||
// collection
|
|
||||||
interface MarkdownFrontmatter {
|
|
||||||
date: number;
|
|
||||||
description: string;
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { page } = Astro.props;
|
const { page } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<MainHead {title} {description} image={page.data[0].image} canonicalURL={canonicalURL.toString()} prev={page.url.prev} next={page.url.next} />
|
<MainHead {title} {description} image={page.data[0].frontmatter.image} canonicalURL={canonicalURL.toString()} prev={page.url.prev} next={page.url.next} />
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.title {
|
.title {
|
||||||
|
@ -70,7 +62,7 @@ const { page } = Astro.props;
|
||||||
<main class="wrapper">
|
<main class="wrapper">
|
||||||
<h2 class="title">All Posts</h2>
|
<h2 class="title">All Posts</h2>
|
||||||
<small class="count">{page.start + 1}–{page.end + 1} of {page.total}</small>
|
<small class="count">{page.start + 1}–{page.end + 1} of {page.total}</small>
|
||||||
{page.data.map((post) => <PostPreview post={post} author={authorData[post.author]} />)}
|
{page.data.map((post) => <PostPreview post={post} author={authorData[post.frontmatter.author]} />)}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"preview": "astro preview"
|
"preview": "astro preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"astro": "^0.25.3",
|
"astro": "^0.25.4",
|
||||||
"@astrojs/preact": "^0.0.2"
|
"@astrojs/preact": "^0.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -8,10 +8,10 @@ const { post } = Astro.props;
|
||||||
|
|
||||||
<article class="post-preview">
|
<article class="post-preview">
|
||||||
<header>
|
<header>
|
||||||
<p class="publish-date">{post.publishDate}</p>
|
<p class="publish-date">{post.frontmatter.publishDate}</p>
|
||||||
<a href={post.url}><h1 class="title">{post.title}</h1></a>
|
<a href={post.url}><h1 class="title">{post.frontmatter.title}</h1></a>
|
||||||
</header>
|
</header>
|
||||||
<p>{post.description}</p>
|
<p>{post.frontmatter.description}</p>
|
||||||
<a href={post.url}>Read more</a>
|
<a href={post.url}>Read more</a>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,6 @@ import BaseHead from '../components/BaseHead.astro';
|
||||||
import BlogHeader from '../components/BlogHeader.astro';
|
import BlogHeader from '../components/BlogHeader.astro';
|
||||||
import BlogPostPreview from '../components/BlogPostPreview.astro';
|
import BlogPostPreview from '../components/BlogPostPreview.astro';
|
||||||
|
|
||||||
interface MarkdownFrontmatter {
|
|
||||||
publishDate: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Component Script:
|
// Component Script:
|
||||||
// You can write any JavaScript/TypeScript that you'd like here.
|
// You can write any JavaScript/TypeScript that you'd like here.
|
||||||
// It will run during the build, but never in the browser.
|
// It will run during the build, but never in the browser.
|
||||||
|
@ -18,8 +14,8 @@ let permalink = 'https://example.com/';
|
||||||
|
|
||||||
// Data Fetching: List all Markdown posts in the repo.
|
// Data Fetching: List all Markdown posts in the repo.
|
||||||
|
|
||||||
let allPosts = await Astro.fetchContent('./posts/*.md');
|
let allPosts = await Astro.glob('./posts/*.md');
|
||||||
allPosts = allPosts.sort((a, b) => new Date(b.publishDate).valueOf() - new Date(a.publishDate).valueOf());
|
allPosts = allPosts.sort((a, b) => new Date(b.frontmatter.publishDate).valueOf() - new Date(a.frontmatter.publishDate).valueOf());
|
||||||
|
|
||||||
// Full Astro Component Syntax:
|
// Full Astro Component Syntax:
|
||||||
// https://docs.astro.build/core-concepts/astro-components/
|
// https://docs.astro.build/core-concepts/astro-components/
|
||||||
|
|
|
@ -10,6 +10,6 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@example/my-component": "workspace:*",
|
"@example/my-component": "workspace:*",
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,6 @@
|
||||||
"serve": "astro --project-root demo preview"
|
"serve": "astro --project-root demo preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,6 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/preact": "^0.0.2",
|
"@astrojs/preact": "^0.0.2",
|
||||||
"@astrojs/react": "^0.0.2",
|
"@astrojs/react": "^0.0.2",
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ const githubEditUrl = CONFIG.GITHUB_EDIT_URL && CONFIG.GITHUB_EDIT_URL + current
|
||||||
<html dir={content.dir ?? 'ltr'} lang={content.lang ?? 'en-us'} class="initial">
|
<html dir={content.dir ?? 'ltr'} lang={content.lang ?? 'en-us'} class="initial">
|
||||||
<head>
|
<head>
|
||||||
<HeadCommon />
|
<HeadCommon />
|
||||||
<HeadSEO {content} canonicalURL={Astro.request.canonicalURL} />
|
<HeadSEO {content} canonicalURL={Astro.canonicalURL} />
|
||||||
<title>{content.title ? `${content.title} 🚀 ${CONFIG.SITE.title}` : CONFIG.SITE.title}</title>
|
<title>{content.title ? `${content.title} 🚀 ${CONFIG.SITE.title}` : CONFIG.SITE.title}</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
|
|
|
@ -9,6 +9,6 @@
|
||||||
"preview": "astro preview"
|
"preview": "astro preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,6 @@
|
||||||
"preview": "astro preview"
|
"preview": "astro preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/lit": "^0.0.2",
|
"@astrojs/lit": "^0.0.2",
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@webcomponents/template-shadowroot": "^0.1.0",
|
"@webcomponents/template-shadowroot": "^0.1.0",
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
"@astrojs/solid-js": "^0.0.3",
|
"@astrojs/solid-js": "^0.0.3",
|
||||||
"@astrojs/svelte": "^0.0.2",
|
"@astrojs/svelte": "^0.0.2",
|
||||||
"@astrojs/vue": "^0.0.2",
|
"@astrojs/vue": "^0.0.2",
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@webcomponents/template-shadowroot": "^0.1.0",
|
"@webcomponents/template-shadowroot": "^0.1.0",
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/preact": "^0.0.2",
|
"@astrojs/preact": "^0.0.2",
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"preact": "^10.6.6"
|
"preact": "^10.6.6"
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/react": "^0.0.2",
|
"@astrojs/react": "^0.0.2",
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/solid-js": "^0.0.3",
|
"@astrojs/solid-js": "^0.0.3",
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"solid-js": "^1.3.13"
|
"solid-js": "^1.3.13"
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/svelte": "^0.0.2",
|
"@astrojs/svelte": "^0.0.2",
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"svelte": "^3.46.4"
|
"svelte": "^3.46.4"
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/vue": "^0.0.2",
|
"@astrojs/vue": "^0.0.2",
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vue": "^3.2.31"
|
"vue": "^3.2.31"
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
"@astrojs/sitemap": "^0.0.2",
|
"@astrojs/sitemap": "^0.0.2",
|
||||||
"@astrojs/tailwind": "^0.0.2",
|
"@astrojs/tailwind": "^0.0.2",
|
||||||
"@astrojs/turbolinks": "^0.0.2",
|
"@astrojs/turbolinks": "^0.0.2",
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@webcomponents/template-shadowroot": "^0.1.0",
|
"@webcomponents/template-shadowroot": "^0.1.0",
|
||||||
|
|
|
@ -9,6 +9,6 @@
|
||||||
"preview": "astro preview"
|
"preview": "astro preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,6 @@
|
||||||
"preview": "astro preview"
|
"preview": "astro preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from 'astro/config';
|
||||||
import preact from '@astrojs/render-preact';
|
import preact from '@astrojs/preact';
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/preact": "^0.0.2",
|
"@astrojs/preact": "^0.0.2",
|
||||||
"astro": "^0.25.3",
|
"astro": "^0.25.4",
|
||||||
"sass": "^1.49.9"
|
"sass": "^1.49.9"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -2,16 +2,17 @@ import { h } from 'preact';
|
||||||
import Styles from './styles.module.scss';
|
import Styles from './styles.module.scss';
|
||||||
|
|
||||||
function PortfolioPreview({ project }) {
|
function PortfolioPreview({ project }) {
|
||||||
|
const { frontmatter } = project;
|
||||||
return (
|
return (
|
||||||
<div className={Styles.card}>
|
<div className={Styles.card}>
|
||||||
<div className={Styles.titleCard} style={`background-image:url(${project.img})`}>
|
<div className={Styles.titleCard} style={`background-image:url(${frontmatter.img})`}>
|
||||||
<h1 className={Styles.title}>{project.title}</h1>
|
<h1 className={Styles.title}>{frontmatter.title}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="pa3">
|
<div className="pa3">
|
||||||
<p className={`${Styles.desc} mt0 mb2`}>{project.description}</p>
|
<p className={`${Styles.desc} mt0 mb2`}>{frontmatter.description}</p>
|
||||||
<div className={Styles.tags}>
|
<div className={Styles.tags}>
|
||||||
Tagged:
|
Tagged:
|
||||||
{project.tags.map((t) => (
|
{frontmatter.tags.map((t) => (
|
||||||
<div className={Styles.tag} data-tag={t}>
|
<div className={Styles.tag} data-tag={t}>
|
||||||
{t}
|
{t}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,7 +7,7 @@ import Footer from '../components/Footer/index.jsx';
|
||||||
import PortfolioPreview from '../components/PortfolioPreview/index.jsx';
|
import PortfolioPreview from '../components/PortfolioPreview/index.jsx';
|
||||||
|
|
||||||
// Data Fetching: List all Markdown posts in the repo.
|
// Data Fetching: List all Markdown posts in the repo.
|
||||||
const projects = Astro.fetchContent('./project/**/*.md');
|
const projects = await Astro.glob('./project/**/*.md');
|
||||||
const featuredProject = projects[0];
|
const featuredProject = projects[0];
|
||||||
|
|
||||||
// Full Astro Component Syntax:
|
// Full Astro Component Syntax:
|
||||||
|
|
|
@ -4,13 +4,9 @@ import Footer from '../components/Footer/index.jsx';
|
||||||
import Nav from '../components/Nav/index.jsx';
|
import Nav from '../components/Nav/index.jsx';
|
||||||
import PortfolioPreview from '../components/PortfolioPreview/index.jsx';
|
import PortfolioPreview from '../components/PortfolioPreview/index.jsx';
|
||||||
|
|
||||||
interface MarkdownFrontmatter {
|
const projects = (await Astro.glob('./project/**/*.md'))
|
||||||
publishDate: number;
|
.filter(({ frontmatter }) => !!frontmatter.publishDate)
|
||||||
}
|
.sort((a, b) => new Date(b.frontmatter.publishDate).valueOf() - new Date(a.frontmatter.publishDate).valueOf());
|
||||||
|
|
||||||
const projects = Astro.fetchContent<MarkdownFrontmatter>('./project/**/*.md')
|
|
||||||
.filter(({ publishDate }) => !!publishDate)
|
|
||||||
.sort((a, b) => new Date(b.publishDate).valueOf() - new Date(a.publishDate).valueOf());
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
|
@ -6,17 +6,4 @@ import nodejs from '@astrojs/node';
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
adapter: nodejs(),
|
adapter: nodejs(),
|
||||||
integrations: [svelte()],
|
integrations: [svelte()],
|
||||||
vite: {
|
|
||||||
server: {
|
|
||||||
cors: {
|
|
||||||
credentials: true,
|
|
||||||
},
|
|
||||||
proxy: {
|
|
||||||
'/api': {
|
|
||||||
target: 'http://127.0.0.1:8085',
|
|
||||||
changeOrigin: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,9 +3,7 @@
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev-api": "node server/dev-api.mjs",
|
"dev": "astro dev --experimental-ssr",
|
||||||
"dev-server": "astro dev --experimental-ssr",
|
|
||||||
"dev": "concurrently \"npm run dev-api\" \"astro dev --experimental-ssr\"",
|
|
||||||
"start": "astro dev",
|
"start": "astro dev",
|
||||||
"build": "astro build --experimental-ssr",
|
"build": "astro build --experimental-ssr",
|
||||||
"server": "node server/server.mjs"
|
"server": "node server/server.mjs"
|
||||||
|
@ -13,7 +11,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/svelte": "^0.0.2",
|
"@astrojs/svelte": "^0.0.2",
|
||||||
"@astrojs/node": "^0.0.2",
|
"@astrojs/node": "^0.0.2",
|
||||||
"astro": "^0.25.3",
|
"astro": "^0.25.4",
|
||||||
"concurrently": "^7.0.0",
|
"concurrently": "^7.0.0",
|
||||||
"lightcookie": "^1.0.25",
|
"lightcookie": "^1.0.25",
|
||||||
"unocss": "^0.15.6",
|
"unocss": "^0.15.6",
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
import fs from 'fs';
|
|
||||||
import lightcookie from 'lightcookie';
|
|
||||||
|
|
||||||
const dbJSON = fs.readFileSync(new URL('./db.json', import.meta.url));
|
|
||||||
const db = JSON.parse(dbJSON);
|
|
||||||
const products = db.products;
|
|
||||||
const productMap = new Map(products.map((product) => [product.id, product]));
|
|
||||||
|
|
||||||
// Normally this would be in a database.
|
|
||||||
const userCartItems = new Map();
|
|
||||||
|
|
||||||
const routes = [
|
|
||||||
{
|
|
||||||
match: /\/api\/products\/([0-9])+/,
|
|
||||||
async handle(_req, res, [, idStr]) {
|
|
||||||
const id = Number(idStr);
|
|
||||||
if (productMap.has(id)) {
|
|
||||||
const product = productMap.get(id);
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
});
|
|
||||||
res.end(JSON.stringify(product));
|
|
||||||
} else {
|
|
||||||
res.writeHead(404, {
|
|
||||||
'Content-Type': 'text/plain',
|
|
||||||
});
|
|
||||||
res.end('Not found');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
match: /\/api\/products/,
|
|
||||||
async handle(_req, res) {
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
});
|
|
||||||
res.end(JSON.stringify(products));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
match: /\/api\/cart/,
|
|
||||||
async handle(req, res) {
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
});
|
|
||||||
let cookie = req.headers.cookie;
|
|
||||||
let userId = cookie ? lightcookie.parse(cookie)['user-id'] : '1'; // default for testing
|
|
||||||
if (!userId || !userCartItems.has(userId)) {
|
|
||||||
res.end(JSON.stringify({ items: [] }));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let items = userCartItems.get(userId);
|
|
||||||
let array = Array.from(items.values());
|
|
||||||
res.end(JSON.stringify({ items: array }));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
match: /\/api\/add-to-cart/,
|
|
||||||
async handle(req, res) {
|
|
||||||
let body = '';
|
|
||||||
req.on('data', (chunk) => (body += chunk));
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
req.on('end', () => {
|
|
||||||
let cookie = req.headers.cookie;
|
|
||||||
let userId = lightcookie.parse(cookie)['user-id'];
|
|
||||||
let msg = JSON.parse(body);
|
|
||||||
|
|
||||||
if (!userCartItems.has(userId)) {
|
|
||||||
userCartItems.set(userId, new Map());
|
|
||||||
}
|
|
||||||
|
|
||||||
let cart = userCartItems.get(userId);
|
|
||||||
if (cart.has(msg.id)) {
|
|
||||||
cart.get(msg.id).count++;
|
|
||||||
} else {
|
|
||||||
cart.set(msg.id, { id: msg.id, name: msg.name, count: 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
});
|
|
||||||
res.end(JSON.stringify({ ok: true }));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export async function apiHandler(req, res) {
|
|
||||||
for (const route of routes) {
|
|
||||||
const match = route.match.exec(req.url);
|
|
||||||
if (match) {
|
|
||||||
return route.handle(req, res, match);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res.writeHead(404, {
|
|
||||||
'Content-Type': 'text/plain',
|
|
||||||
});
|
|
||||||
res.end('Not found');
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { createServer } from 'http';
|
|
||||||
import { apiHandler } from './api.mjs';
|
|
||||||
|
|
||||||
const PORT = process.env.PORT || 8085;
|
|
||||||
|
|
||||||
const server = createServer((req, res) => {
|
|
||||||
apiHandler(req, res).catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
res.writeHead(500, {
|
|
||||||
'Content-Type': 'text/plain',
|
|
||||||
});
|
|
||||||
res.end(err.toString());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
server.listen(PORT);
|
|
||||||
console.log(`API running at http://localhost:${PORT}`);
|
|
|
@ -1,29 +1,28 @@
|
||||||
import { createServer } from 'http';
|
import { createServer } from 'http';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import mime from 'mime';
|
import mime from 'mime';
|
||||||
import { apiHandler } from './api.mjs';
|
|
||||||
import { handler as ssrHandler } from '../dist/server/entry.mjs';
|
import { handler as ssrHandler } from '../dist/server/entry.mjs';
|
||||||
|
|
||||||
const clientRoot = new URL('../dist/client/', import.meta.url);
|
const clientRoot = new URL('../dist/client/', import.meta.url);
|
||||||
|
|
||||||
async function handle(req, res) {
|
async function handle(req, res) {
|
||||||
ssrHandler(req, res, async () => {
|
ssrHandler(req, res, async (err) => {
|
||||||
// Did not match an SSR route
|
if (err) {
|
||||||
|
res.writeHead(500);
|
||||||
|
res.end(err.stack);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (/^\/api\//.test(req.url)) {
|
let local = new URL('.' + req.url, clientRoot);
|
||||||
return apiHandler(req, res);
|
try {
|
||||||
} else {
|
const data = await fs.promises.readFile(local);
|
||||||
let local = new URL('.' + req.url, clientRoot);
|
res.writeHead(200, {
|
||||||
try {
|
'Content-Type': mime.getType(req.url),
|
||||||
const data = await fs.promises.readFile(local);
|
});
|
||||||
res.writeHead(200, {
|
res.end(data);
|
||||||
'Content-Type': mime.getType(req.url),
|
} catch {
|
||||||
});
|
res.writeHead(404);
|
||||||
res.end(data);
|
res.end();
|
||||||
} catch {
|
|
||||||
res.writeHead(404);
|
|
||||||
res.end();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,12 @@ interface Cart {
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { MODE } = import.meta.env;
|
function getOrigin(request: Request): string {
|
||||||
const origin = MODE === 'development' ? `http://127.0.0.1:3000` : `http://127.0.0.1:8085`;
|
return new URL(request.url).origin.replace('localhost', '127.0.0.1');
|
||||||
|
}
|
||||||
|
|
||||||
async function get<T>(endpoint: string, cb: (response: Response) => Promise<T>): Promise<T> {
|
async function get<T>(incomingReq: Request, endpoint: string, cb: (response: Response) => Promise<T>): Promise<T> {
|
||||||
const response = await fetch(`${origin}${endpoint}`, {
|
const response = await fetch(`${getOrigin(incomingReq)}${endpoint}`, {
|
||||||
credentials: 'same-origin',
|
credentials: 'same-origin',
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
@ -31,36 +32,36 @@ async function get<T>(endpoint: string, cb: (response: Response) => Promise<T>):
|
||||||
return cb(response);
|
return cb(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProducts(): Promise<Product[]> {
|
export async function getProducts(incomingReq: Request): Promise<Product[]> {
|
||||||
return get<Product[]>('/api/products', async (response) => {
|
return get<Product[]>(incomingReq, '/api/products', async (response) => {
|
||||||
const products: Product[] = await response.json();
|
const products: Product[] = await response.json();
|
||||||
return products;
|
return products;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProduct(id: number): Promise<Product> {
|
export async function getProduct(incomingReq: Request, id: number): Promise<Product> {
|
||||||
return get<Product>(`/api/products/${id}`, async (response) => {
|
return get<Product>(incomingReq, `/api/products/${id}`, async (response) => {
|
||||||
const product: Product = await response.json();
|
const product: Product = await response.json();
|
||||||
return product;
|
return product;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUser(): Promise<User> {
|
export async function getUser(incomingReq: Request): Promise<User> {
|
||||||
return get<User>(`/api/user`, async (response) => {
|
return get<User>(incomingReq, `/api/user`, async (response) => {
|
||||||
const user: User = await response.json();
|
const user: User = await response.json();
|
||||||
return user;
|
return user;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCart(): Promise<Cart> {
|
export async function getCart(incomingReq: Request): Promise<Cart> {
|
||||||
return get<Cart>(`/api/cart`, async (response) => {
|
return get<Cart>(incomingReq, `/api/cart`, async (response) => {
|
||||||
const cart: Cart = await response.json();
|
const cart: Cart = await response.json();
|
||||||
return cart;
|
return cart;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addToUserCart(id: number | string, name: string): Promise<void> {
|
export async function addToUserCart(id: number | string, name: string): Promise<void> {
|
||||||
await fetch(`${origin}/api/add-to-cart`, {
|
await fetch(`${location.origin}/api/cart`, {
|
||||||
credentials: 'same-origin',
|
credentials: 'same-origin',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
mode: 'no-cors',
|
mode: 'no-cors',
|
||||||
|
|
|
@ -3,7 +3,7 @@ import TextDecorationSkip from './TextDecorationSkip.astro';
|
||||||
import Cart from './Cart.svelte';
|
import Cart from './Cart.svelte';
|
||||||
import { getCart } from '../api';
|
import { getCart } from '../api';
|
||||||
|
|
||||||
const cart = await getCart();
|
const cart = await getCart(Astro.request);
|
||||||
const cartCount = cart.items.reduce((sum, item) => sum + item.count, 0);
|
const cartCount = cart.items.reduce((sum, item) => sum + item.count, 0);
|
||||||
---
|
---
|
||||||
<style>
|
<style>
|
||||||
|
|
6
examples/ssr/src/models/db.ts
Normal file
6
examples/ssr/src/models/db.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import db from './db.json';
|
||||||
|
|
||||||
|
const products = db.products;
|
||||||
|
const productMap = new Map(products.map((product) => [product.id, product]));
|
||||||
|
|
||||||
|
export { products, productMap };
|
2
examples/ssr/src/models/session.ts
Normal file
2
examples/ssr/src/models/session.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Normally this would be in a database.
|
||||||
|
export const userCartItems = new Map();
|
47
examples/ssr/src/pages/api/cart.ts
Normal file
47
examples/ssr/src/pages/api/cart.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import lightcookie from 'lightcookie';
|
||||||
|
import { userCartItems } from '../../models/session';
|
||||||
|
|
||||||
|
export function get(_params: any, request: Request) {
|
||||||
|
let cookie = request.headers.get('cookie');
|
||||||
|
let userId = cookie ? lightcookie.parse(cookie)['user-id'] : '1'; // default for testing
|
||||||
|
if (!userId || !userCartItems.has(userId)) {
|
||||||
|
return {
|
||||||
|
body: JSON.stringify({ items: [] }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let items = userCartItems.get(userId);
|
||||||
|
let array = Array.from(items.values());
|
||||||
|
|
||||||
|
return {
|
||||||
|
body: JSON.stringify({ items: array }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddToCartItem {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function post(_params: any, request: Request) {
|
||||||
|
const item: AddToCartItem = await request.json();
|
||||||
|
|
||||||
|
let cookie = request.headers.get('cookie');
|
||||||
|
let userId = lightcookie.parse(cookie)['user-id'];
|
||||||
|
|
||||||
|
if (!userCartItems.has(userId)) {
|
||||||
|
userCartItems.set(userId, new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
let cart = userCartItems.get(userId);
|
||||||
|
if (cart.has(item.id)) {
|
||||||
|
cart.get(item.id).count++;
|
||||||
|
} else {
|
||||||
|
cart.set(item.id, { id: item.id, name: item.name, count: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
body: JSON.stringify({
|
||||||
|
ok: true,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
7
examples/ssr/src/pages/api/products.ts
Normal file
7
examples/ssr/src/pages/api/products.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { products } from '../../models/db';
|
||||||
|
|
||||||
|
export function get() {
|
||||||
|
return {
|
||||||
|
body: JSON.stringify(products),
|
||||||
|
};
|
||||||
|
}
|
17
examples/ssr/src/pages/api/products/[id].ts
Normal file
17
examples/ssr/src/pages/api/products/[id].ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { productMap } from '../../../models/db';
|
||||||
|
|
||||||
|
export function get({ id: idStr }) {
|
||||||
|
const id = Number(idStr);
|
||||||
|
if (productMap.has(id)) {
|
||||||
|
const product = productMap.get(id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
body: JSON.stringify(product),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return new Response(null, {
|
||||||
|
status: 400,
|
||||||
|
statusText: 'Not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ if(!isLoggedIn(Astro.request)) {
|
||||||
// They must be logged in.
|
// They must be logged in.
|
||||||
|
|
||||||
const user = { name: 'test'}; // getUser?
|
const user = { name: 'test'}; // getUser?
|
||||||
const cart = await getCart();
|
const cart = await getCart(Astro.request);
|
||||||
---
|
---
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import ProductListing from '../components/ProductListing.astro';
|
||||||
import { getProducts } from '../api';
|
import { getProducts } from '../api';
|
||||||
import '../styles/common.css';
|
import '../styles/common.css';
|
||||||
|
|
||||||
const products = await getProducts();
|
const products = await getProducts(Astro.request);
|
||||||
---
|
---
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|
|
@ -5,8 +5,8 @@ import AddToCart from '../../components/AddToCart.svelte';
|
||||||
import { getProduct } from '../../api';
|
import { getProduct } from '../../api';
|
||||||
import '../../styles/common.css';
|
import '../../styles/common.css';
|
||||||
|
|
||||||
const id = Number(Astro.request.params.id);
|
const id = Number(Astro.params.id);
|
||||||
const product = await getProduct(id);
|
const product = await getProduct(Astro.request, id);
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"lib": ["ES2015", "DOM"],
|
"lib": ["ES2015", "DOM"],
|
||||||
"module": "ES2022",
|
"module": "ES2022",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
"types": ["astro/env"]
|
"types": ["astro/env"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,6 @@
|
||||||
"preview": "astro preview"
|
"preview": "astro preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/react": "^0.0.2",
|
"@astrojs/react": "^0.0.2",
|
||||||
"astro": "^0.25.3",
|
"astro": "^0.25.4",
|
||||||
"sass": "^1.49.9"
|
"sass": "^1.49.9"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/markdown-remark": "^0.7.0",
|
"@astrojs/markdown-remark": "^0.7.0",
|
||||||
"astro": "^0.25.3",
|
"astro": "^0.25.4",
|
||||||
"hast-util-select": "5.0.1",
|
"hast-util-select": "5.0.1",
|
||||||
"rehype-autolink-headings": "^6.1.1",
|
"rehype-autolink-headings": "^6.1.1",
|
||||||
"rehype-slug": "^5.0.1",
|
"rehype-slug": "^5.0.1",
|
||||||
|
|
|
@ -10,6 +10,6 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/markdown-remark": "^0.7.0",
|
"@astrojs/markdown-remark": "^0.7.0",
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
"@astrojs/react": "^0.0.2",
|
"@astrojs/react": "^0.0.2",
|
||||||
"@astrojs/svelte": "^0.0.2",
|
"@astrojs/svelte": "^0.0.2",
|
||||||
"@astrojs/vue": "^0.0.2",
|
"@astrojs/vue": "^0.0.2",
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"preact": "^10.6.6",
|
"preact": "^10.6.6",
|
||||||
|
|
|
@ -25,6 +25,6 @@
|
||||||
"@astrojs/solid-js": "^0.0.3",
|
"@astrojs/solid-js": "^0.0.3",
|
||||||
"@astrojs/svelte": "^0.0.2",
|
"@astrojs/svelte": "^0.0.2",
|
||||||
"@astrojs/vue": "^0.0.2",
|
"@astrojs/vue": "^0.0.2",
|
||||||
"astro": "^0.25.3"
|
"astro": "^0.25.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/tailwind": "^0.0.2",
|
"@astrojs/tailwind": "^0.0.2",
|
||||||
"astro": "^0.25.3",
|
"astro": "^0.25.4",
|
||||||
"autoprefixer": "^10.4.4",
|
"autoprefixer": "^10.4.4",
|
||||||
"canvas-confetti": "^1.5.1",
|
"canvas-confetti": "^1.5.1",
|
||||||
"postcss": "^8.4.12",
|
"postcss": "^8.4.12",
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"preview": "astro preview"
|
"preview": "astro preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"astro": "^0.25.3",
|
"astro": "^0.25.4",
|
||||||
"vite-plugin-pwa": "0.11.11",
|
"vite-plugin-pwa": "0.11.11",
|
||||||
"workbox-window": "^6.5.2"
|
"workbox-window": "^6.5.2"
|
||||||
}
|
}
|
||||||
|
|
10
package.json
10
package.json
|
@ -55,14 +55,14 @@
|
||||||
"@astrojs/webapi": "workspace:*"
|
"@astrojs/webapi": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/changelog-github": "^0.4.3",
|
"@changesets/changelog-github": "^0.4.4",
|
||||||
"@changesets/cli": "^2.21.1",
|
"@changesets/cli": "^2.22.0",
|
||||||
"@octokit/action": "^3.18.0",
|
"@octokit/action": "^3.18.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.16.0",
|
"@typescript-eslint/eslint-plugin": "^5.17.0",
|
||||||
"@typescript-eslint/parser": "^5.16.0",
|
"@typescript-eslint/parser": "^5.17.0",
|
||||||
"del": "^6.0.0",
|
"del": "^6.0.0",
|
||||||
"esbuild": "0.14.25",
|
"esbuild": "0.14.25",
|
||||||
"eslint": "^8.11.0",
|
"eslint": "^8.12.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"execa": "^6.1.0",
|
"execa": "^6.1.0",
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
# astro
|
# astro
|
||||||
|
|
||||||
|
## 0.25.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- [#2907](https://github.com/withastro/astro/pull/2907) [`22b1432e`](https://github.com/withastro/astro/commit/22b1432e3eed6ff40a0ab383c8f1f06f0df10d62) Thanks [@delucis](https://github.com/delucis)! - Fix typing of `integrations` array in user config
|
||||||
|
|
||||||
## 0.25.3
|
## 0.25.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
2
packages/astro/env.d.ts
vendored
2
packages/astro/env.d.ts
vendored
|
@ -1,6 +1,6 @@
|
||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
type Astro = import('./dist/types/@types/astro').AstroGlobal;
|
type Astro = import('astro').AstroGlobal;
|
||||||
|
|
||||||
// We duplicate the description here because editors won't show the JSDoc comment from the imported type (but will for its properties, ex: Astro.request will show the AstroGlobal.request description)
|
// We duplicate the description here because editors won't show the JSDoc comment from the imported type (but will for its properties, ex: Astro.request will show the AstroGlobal.request description)
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "astro",
|
"name": "astro",
|
||||||
"version": "0.25.3",
|
"version": "0.25.4",
|
||||||
"description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
|
"description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"author": "withastro",
|
"author": "withastro",
|
||||||
|
@ -85,6 +85,7 @@
|
||||||
"@proload/core": "^0.2.2",
|
"@proload/core": "^0.2.2",
|
||||||
"@proload/plugin-tsm": "^0.1.1",
|
"@proload/plugin-tsm": "^0.1.1",
|
||||||
"@web/parse5-utils": "^1.3.0",
|
"@web/parse5-utils": "^1.3.0",
|
||||||
|
"ast-types": "^0.14.2",
|
||||||
"boxen": "^6.2.1",
|
"boxen": "^6.2.1",
|
||||||
"ci-info": "^3.3.0",
|
"ci-info": "^3.3.0",
|
||||||
"common-ancestor-path": "^1.0.1",
|
"common-ancestor-path": "^1.0.1",
|
||||||
|
@ -97,7 +98,7 @@
|
||||||
"execa": "^6.1.0",
|
"execa": "^6.1.0",
|
||||||
"fast-glob": "^3.2.11",
|
"fast-glob": "^3.2.11",
|
||||||
"fast-xml-parser": "^4.0.7",
|
"fast-xml-parser": "^4.0.7",
|
||||||
"html-entities": "^2.3.2",
|
"html-entities": "^2.3.3",
|
||||||
"html-escaper": "^3.0.3",
|
"html-escaper": "^3.0.3",
|
||||||
"htmlparser2": "^7.2.0",
|
"htmlparser2": "^7.2.0",
|
||||||
"kleur": "^4.1.4",
|
"kleur": "^4.1.4",
|
||||||
|
@ -109,10 +110,11 @@
|
||||||
"parse5": "^6.0.1",
|
"parse5": "^6.0.1",
|
||||||
"path-to-regexp": "^6.2.0",
|
"path-to-regexp": "^6.2.0",
|
||||||
"postcss": "^8.4.12",
|
"postcss": "^8.4.12",
|
||||||
"postcss-load-config": "^3.1.3",
|
"postcss-load-config": "^3.1.4",
|
||||||
"preferred-pm": "^3.0.3",
|
"preferred-pm": "^3.0.3",
|
||||||
"prismjs": "^1.27.0",
|
"prismjs": "^1.27.0",
|
||||||
"prompts": "^2.4.2",
|
"prompts": "^2.4.2",
|
||||||
|
"recast": "^0.20.5",
|
||||||
"rehype-slug": "^5.0.1",
|
"rehype-slug": "^5.0.1",
|
||||||
"resolve": "^1.22.0",
|
"resolve": "^1.22.0",
|
||||||
"rollup": "^2.70.1",
|
"rollup": "^2.70.1",
|
||||||
|
@ -130,7 +132,7 @@
|
||||||
"tsconfig-resolver": "^3.0.1",
|
"tsconfig-resolver": "^3.0.1",
|
||||||
"vite": "^2.8.6",
|
"vite": "^2.8.6",
|
||||||
"yargs-parser": "^21.0.1",
|
"yargs-parser": "^21.0.1",
|
||||||
"zod": "^3.14.2"
|
"zod": "^3.14.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/types": "^7.17.0",
|
"@babel/types": "^7.17.0",
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import type { AddressInfo } from 'net';
|
import type { AddressInfo } from 'net';
|
||||||
import type * as babel from '@babel/core';
|
import type * as babel from '@babel/core';
|
||||||
import type * as vite from 'vite';
|
import type * as vite from 'vite';
|
||||||
import type { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import type { AstroConfigSchema } from '../core/config';
|
import type { AstroConfigSchema } from '../core/config';
|
||||||
import type { AstroComponentFactory, Metadata } from '../runtime/server';
|
import type { AstroComponentFactory, Metadata } from '../runtime/server';
|
||||||
import type { AstroRequest } from '../core/render/request';
|
|
||||||
export type { SSRManifest } from '../core/app/types';
|
export type { SSRManifest } from '../core/app/types';
|
||||||
|
|
||||||
export interface AstroBuiltinProps {
|
export interface AstroBuiltinProps {
|
||||||
|
@ -51,20 +50,28 @@ export interface BuildConfig {
|
||||||
* Docs: https://docs.astro.build/reference/api-reference/#astro-global
|
* Docs: https://docs.astro.build/reference/api-reference/#astro-global
|
||||||
*/
|
*/
|
||||||
export interface AstroGlobal extends AstroGlobalPartial {
|
export interface AstroGlobal extends AstroGlobalPartial {
|
||||||
|
/** get the current canonical URL */
|
||||||
|
canonicalURL: URL;
|
||||||
|
/** get page params (dynamic pages only) */
|
||||||
|
params: Params;
|
||||||
/** set props for this astro component (along with default values) */
|
/** set props for this astro component (along with default values) */
|
||||||
props: Record<string, number | string | any>;
|
props: Record<string, number | string | any>;
|
||||||
/** get information about this page */
|
/** get information about this page */
|
||||||
request: AstroRequest;
|
request: Request;
|
||||||
/** see if slots are used */
|
/** see if slots are used */
|
||||||
slots: Record<string, true | undefined> & { has(slotName: string): boolean; render(slotName: string): Promise<string> };
|
slots: Record<string, true | undefined> & { has(slotName: string): boolean; render(slotName: string): Promise<string> };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AstroGlobalPartial {
|
export interface AstroGlobalPartial {
|
||||||
fetchContent<T = any>(globStr: string): Promise<FetchContentResult<T>[]>;
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since version 0.24. See the {@link https://astro.build/deprecated/resolve upgrade guide} for more details.
|
* @deprecated since version 0.24. See the {@link https://astro.build/deprecated/resolve upgrade guide} for more details.
|
||||||
*/
|
*/
|
||||||
resolve: (path: string) => string;
|
resolve: (path: string) => string;
|
||||||
|
/** @deprecated Use `Astro.glob()` instead. */
|
||||||
|
fetchContent(globStr: string): Promise<any[]>;
|
||||||
|
glob(globStr: `${any}.astro`): Promise<ComponentInstance[]>;
|
||||||
|
glob<T extends Record<string, any>>(globStr: `${any}.md`): Promise<MarkdownInstance<T>[]>;
|
||||||
|
glob<T extends Record<string, any>>(globStr: string): Promise<T[]>;
|
||||||
site: URL;
|
site: URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -509,20 +516,13 @@ export interface ComponentInstance {
|
||||||
getStaticPaths?: (options: GetStaticPathsOptions) => GetStaticPathsResult;
|
getStaticPaths?: (options: GetStaticPathsOptions) => GetStaticPathsResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export interface MarkdownInstance<T extends Record<string, any>> {
|
||||||
* Astro.fetchContent() result
|
frontmatter: T;
|
||||||
* Docs: https://docs.astro.build/reference/api-reference/#astrofetchcontent
|
file: string;
|
||||||
*/
|
url: string | undefined;
|
||||||
export type FetchContentResult<T> = FetchContentResultBase & T;
|
Content: AstroComponentFactory;
|
||||||
|
getHeaders(): Promise<{ depth: number; slug: string; text: string }[]>;
|
||||||
export type FetchContentResultBase = {
|
}
|
||||||
astro: {
|
|
||||||
headers: string[];
|
|
||||||
source: string;
|
|
||||||
html: string;
|
|
||||||
};
|
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type GetHydrateCallback = () => Promise<(element: Element, innerHTML: string | null) => void>;
|
export type GetHydrateCallback = () => Promise<(element: Element, innerHTML: string | null) => void>;
|
||||||
|
|
||||||
|
@ -636,7 +636,7 @@ export interface EndpointOutput<Output extends Body = Body> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EndpointHandler {
|
export interface EndpointHandler {
|
||||||
[method: string]: (params: any, request: AstroRequest) => EndpointOutput | Response;
|
[method: string]: (params: any, request: Request) => EndpointOutput | Response;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AstroRenderer {
|
export interface AstroRenderer {
|
||||||
|
|
|
@ -57,7 +57,7 @@ function offsetAt({ line, character }: { line: number; character: number }, text
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pad(str: string, len: number) {
|
function generateString(str: string, len: number) {
|
||||||
return Array.from({ length: len }, () => str).join('');
|
return Array.from({ length: len }, () => str).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,8 +86,8 @@ export async function check(astroConfig: AstroConfig) {
|
||||||
const lineNumStr = d.range.start.line.toString();
|
const lineNumStr = d.range.start.line.toString();
|
||||||
const lineNumLen = lineNumStr.length;
|
const lineNumLen = lineNumStr.length;
|
||||||
console.error(`${bgWhite(black(lineNumStr))} ${str}`);
|
console.error(`${bgWhite(black(lineNumStr))} ${str}`);
|
||||||
let tildes = pad('~', d.range.end.character - d.range.start.character);
|
let tildes = generateString('~', d.range.end.character - d.range.start.character);
|
||||||
let spaces = pad(' ', d.range.start.character + lineNumLen - 1);
|
let spaces = generateString(' ', d.range.start.character + lineNumLen - 1);
|
||||||
console.error(` ${spaces}${bold(red(tildes))}\n`);
|
console.error(` ${spaces}${bold(red(tildes))}\n`);
|
||||||
result.errors++;
|
result.errors++;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -12,8 +12,9 @@ import add from '../core/add/index.js';
|
||||||
import devServer from '../core/dev/index.js';
|
import devServer from '../core/dev/index.js';
|
||||||
import preview from '../core/preview/index.js';
|
import preview from '../core/preview/index.js';
|
||||||
import { check } from './check.js';
|
import { check } from './check.js';
|
||||||
import { formatConfigError, loadConfig } from '../core/config.js';
|
import { loadConfig } from '../core/config.js';
|
||||||
import { printHelp } from '../core/messages.js';
|
import { printHelp, formatErrorMessage, formatConfigErrorMessage } from '../core/messages.js';
|
||||||
|
import { createSafeError } from '../core/util.js';
|
||||||
|
|
||||||
type Arguments = yargs.Arguments;
|
type Arguments = yargs.Arguments;
|
||||||
type CLICommand = 'help' | 'version' | 'add' | 'dev' | 'build' | 'preview' | 'reload' | 'check';
|
type CLICommand = 'help' | 'version' | 'add' | 'dev' | 'build' | 'preview' | 'reload' | 'check';
|
||||||
|
@ -102,40 +103,33 @@ export async function cli(args: string[]) {
|
||||||
// For now, `add` has to resolve the config again internally
|
// For now, `add` has to resolve the config again internally
|
||||||
config = await loadConfig({ cwd: projectRoot, flags });
|
config = await loadConfig({ cwd: projectRoot, flags });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throwAndExit(err);
|
return throwAndExit(err);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case 'add': {
|
case 'add': {
|
||||||
try {
|
try {
|
||||||
const packages = flags._.slice(3) as string[];
|
const packages = flags._.slice(3) as string[];
|
||||||
await add(packages, { cwd: projectRoot, flags, logging });
|
return await add(packages, { cwd: projectRoot, flags, logging });
|
||||||
process.exit(0);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throwAndExit(err);
|
return throwAndExit(err);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case 'dev': {
|
case 'dev': {
|
||||||
try {
|
try {
|
||||||
await devServer(config, { logging });
|
await devServer(config, { logging });
|
||||||
|
return await new Promise(() => {}); // lives forever
|
||||||
await new Promise(() => {}); // don’t close dev server
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throwAndExit(err);
|
return throwAndExit(err);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'build': {
|
case 'build': {
|
||||||
try {
|
try {
|
||||||
await build(config, { logging });
|
return await build(config, { logging });
|
||||||
process.exit(0);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throwAndExit(err);
|
return throwAndExit(err);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'check': {
|
case 'check': {
|
||||||
|
@ -145,11 +139,10 @@ export async function cli(args: string[]) {
|
||||||
|
|
||||||
case 'preview': {
|
case 'preview': {
|
||||||
try {
|
try {
|
||||||
await preview(config, { logging }); // this will keep running
|
return await preview(config, { logging }); // this will keep running
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throwAndExit(err);
|
return throwAndExit(err);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
|
@ -159,14 +152,11 @@ export async function cli(args: string[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Display error and exit */
|
/** Display error and exit */
|
||||||
function throwAndExit(err: any) {
|
function throwAndExit(err: unknown) {
|
||||||
if (err instanceof z.ZodError) {
|
if (err instanceof z.ZodError) {
|
||||||
console.error(formatConfigError(err));
|
console.error(formatConfigErrorMessage(err));
|
||||||
} else if (err.stack) {
|
|
||||||
const [mainMsg, ...stackMsg] = err.stack.split('\n');
|
|
||||||
console.error(colors.red(mainMsg) + '\n' + colors.dim(stackMsg.join('\n')));
|
|
||||||
} else {
|
} else {
|
||||||
console.error(colors.red(err.toString() || err));
|
console.error(formatErrorMessage(createSafeError(err)));
|
||||||
}
|
}
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,7 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO
|
||||||
}
|
}
|
||||||
applyPolyfill();
|
applyPolyfill();
|
||||||
|
|
||||||
|
// If no integrations were given, prompt the user for some popular ones.
|
||||||
if (names.length === 0) {
|
if (names.length === 0) {
|
||||||
const response = await prompts([
|
const response = await prompts([
|
||||||
{
|
{
|
||||||
|
@ -72,16 +73,13 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!response.frameworks && !response.addons) {
|
names = [...(response.frameworks ?? []), ...(response.addons ?? [])];
|
||||||
info(logging, null, msg.cancelled(`Integrations skipped.`, `You can always run ${cyan('astro add')} later!`));
|
}
|
||||||
return;
|
|
||||||
}
|
// If still empty after prompting, exit gracefully.
|
||||||
const selected = [response.frameworks ?? [], response.addons ?? []].flat(1);
|
if (names.length === 0) {
|
||||||
if (selected.length === 0) {
|
error(logging, null, `No integrations specified.`);
|
||||||
error(logging, null, `\n${red('No integrations specified!')}\n${dim('Try running')} astro add again.`);
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
names = selected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some packages might have a common alias! We normalize those here.
|
// Some packages might have a common alias! We normalize those here.
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { call as callEndpoint } from '../endpoint/index.js';
|
||||||
import { RouteCache } from '../render/route-cache.js';
|
import { RouteCache } from '../render/route-cache.js';
|
||||||
import { createLinkStylesheetElementSet, createModuleScriptElementWithSrcSet } from '../render/ssr-element.js';
|
import { createLinkStylesheetElementSet, createModuleScriptElementWithSrcSet } from '../render/ssr-element.js';
|
||||||
import { prependForwardSlash } from '../path.js';
|
import { prependForwardSlash } from '../path.js';
|
||||||
|
import { createRequest } from '../request.js';
|
||||||
|
|
||||||
export class App {
|
export class App {
|
||||||
#manifest: Manifest;
|
#manifest: Manifest;
|
||||||
|
@ -17,6 +18,7 @@ export class App {
|
||||||
#routeDataToRouteInfo: Map<RouteData, RouteInfo>;
|
#routeDataToRouteInfo: Map<RouteData, RouteInfo>;
|
||||||
#routeCache: RouteCache;
|
#routeCache: RouteCache;
|
||||||
#encoder = new TextEncoder();
|
#encoder = new TextEncoder();
|
||||||
|
#logging = defaultLogOptions;
|
||||||
|
|
||||||
constructor(manifest: Manifest) {
|
constructor(manifest: Manifest) {
|
||||||
this.#manifest = manifest;
|
this.#manifest = manifest;
|
||||||
|
@ -63,7 +65,7 @@ export class App {
|
||||||
const result = await render({
|
const result = await render({
|
||||||
legacyBuild: false,
|
legacyBuild: false,
|
||||||
links,
|
links,
|
||||||
logging: defaultLogOptions,
|
logging: this.#logging,
|
||||||
markdownRender: manifest.markdown.render,
|
markdownRender: manifest.markdown.render,
|
||||||
mod,
|
mod,
|
||||||
origin: url.origin,
|
origin: url.origin,
|
||||||
|
@ -81,8 +83,7 @@ export class App {
|
||||||
routeCache: this.#routeCache,
|
routeCache: this.#routeCache,
|
||||||
site: this.#manifest.site,
|
site: this.#manifest.site,
|
||||||
ssr: true,
|
ssr: true,
|
||||||
method: info.routeData.type === 'endpoint' ? '' : 'GET',
|
request,
|
||||||
headers: request.headers,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.type === 'response') {
|
if (result.type === 'response') {
|
||||||
|
@ -100,15 +101,14 @@ export class App {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async #callEndpoint(request: Request, routeData: RouteData, mod: ComponentInstance): Promise<Response> {
|
async #callEndpoint(request: Request, _routeData: RouteData, mod: ComponentInstance): Promise<Response> {
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
const handler = mod as unknown as EndpointHandler;
|
const handler = mod as unknown as EndpointHandler;
|
||||||
const result = await callEndpoint(handler, {
|
const result = await callEndpoint(handler, {
|
||||||
headers: request.headers,
|
|
||||||
logging: defaultLogOptions,
|
logging: defaultLogOptions,
|
||||||
method: request.method,
|
|
||||||
origin: url.origin,
|
origin: url.origin,
|
||||||
pathname: url.pathname,
|
pathname: url.pathname,
|
||||||
|
request,
|
||||||
routeCache: this.#routeCache,
|
routeCache: this.#routeCache,
|
||||||
ssr: true,
|
ssr: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { appendForwardSlash } from '../../core/path.js';
|
||||||
|
|
||||||
const STATUS_CODE_PAGES = new Set(['/404', '/500']);
|
const STATUS_CODE_PAGES = new Set(['/404', '/500']);
|
||||||
|
|
||||||
export function getOutRoot(astroConfig: AstroConfig): URL {
|
function getOutRoot(astroConfig: AstroConfig): URL {
|
||||||
return new URL('./', astroConfig.dist);
|
return new URL('./', astroConfig.dist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
import type { OutputAsset, OutputChunk, RollupOutput } from 'rollup';
|
|
||||||
import type { AstroConfig, ComponentInstance, EndpointHandler, SSRLoadedRenderer } from '../../@types/astro';
|
|
||||||
import type { PageBuildData, StaticBuildOptions, SingleFileBuiltModule } from './types';
|
|
||||||
import type { BuildInternals } from '../../core/build/internal.js';
|
|
||||||
import type { RenderOptions } from '../../core/render/core';
|
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { bgGreen, bgMagenta, black, cyan, dim, green, magenta } from 'kleur/colors';
|
||||||
import npath from 'path';
|
import npath from 'path';
|
||||||
|
import type { OutputAsset, OutputChunk, RollupOutput } from 'rollup';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { debug, error, info } from '../../core/logger.js';
|
import type { AstroConfig, ComponentInstance, EndpointHandler, SSRLoadedRenderer } from '../../@types/astro';
|
||||||
import { prependForwardSlash } from '../../core/path.js';
|
import type { BuildInternals } from '../../core/build/internal.js';
|
||||||
|
import { debug, info } from '../../core/logger.js';
|
||||||
|
import { appendForwardSlash, prependForwardSlash } from '../../core/path.js';
|
||||||
|
import type { RenderOptions } from '../../core/render/core';
|
||||||
import { BEFORE_HYDRATION_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
|
import { BEFORE_HYDRATION_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
|
||||||
import { call as callEndpoint } from '../endpoint/index.js';
|
import { call as callEndpoint } from '../endpoint/index.js';
|
||||||
import { render } from '../render/core.js';
|
import { render } from '../render/core.js';
|
||||||
import { createLinkStylesheetElementSet, createModuleScriptElementWithSrcSet } from '../render/ssr-element.js';
|
import { createLinkStylesheetElementSet, createModuleScriptElementWithSrcSet } from '../render/ssr-element.js';
|
||||||
import { getOutFile, getOutRoot, getOutFolder } from './common.js';
|
import { getOutputFilename } from '../util.js';
|
||||||
import { getPageDataByComponent, eachPageData } from './internal.js';
|
import { getOutFile, getOutFolder } from './common.js';
|
||||||
import { bgMagenta, black, cyan, dim, magenta } from 'kleur/colors';
|
import { eachPageData, getPageDataByComponent } from './internal.js';
|
||||||
|
import type { PageBuildData, SingleFileBuiltModule, StaticBuildOptions } from './types';
|
||||||
import { getTimeStat } from './util.js';
|
import { getTimeStat } from './util.js';
|
||||||
|
import { createRequest } from '../request.js';
|
||||||
|
|
||||||
// Render is usually compute, which Node.js can't parallelize well.
|
// Render is usually compute, which Node.js can't parallelize well.
|
||||||
// In real world testing, dropping from 10->1 showed a notiable perf
|
// In real world testing, dropping from 10->1 showed a notiable perf
|
||||||
|
@ -67,7 +68,8 @@ export function chunkIsPage(astroConfig: AstroConfig, output: OutputAsset | Outp
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generatePages(result: RollupOutput, opts: StaticBuildOptions, internals: BuildInternals, facadeIdToPageDataMap: Map<string, PageBuildData>) {
|
export async function generatePages(result: RollupOutput, opts: StaticBuildOptions, internals: BuildInternals, facadeIdToPageDataMap: Map<string, PageBuildData>) {
|
||||||
info(opts.logging, null, `\n${bgMagenta(black(' generating static routes '))}\n`);
|
const timer = performance.now();
|
||||||
|
info(opts.logging, null, `\n${bgGreen(black(' generating static routes '))}`);
|
||||||
|
|
||||||
const ssr = !!opts.astroConfig._ctx.adapter?.serverEntrypoint;
|
const ssr = !!opts.astroConfig._ctx.adapter?.serverEntrypoint;
|
||||||
const serverEntry = opts.buildConfig.serverEntry;
|
const serverEntry = opts.buildConfig.serverEntry;
|
||||||
|
@ -78,6 +80,7 @@ export async function generatePages(result: RollupOutput, opts: StaticBuildOptio
|
||||||
for (const pageData of eachPageData(internals)) {
|
for (const pageData of eachPageData(internals)) {
|
||||||
await generatePage(opts, internals, pageData, ssrEntry);
|
await generatePage(opts, internals, pageData, ssrEntry);
|
||||||
}
|
}
|
||||||
|
info(opts.logging, null, dim(`Completed in ${getTimeStat(timer, performance.now())}.\n`));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generatePage(
|
async function generatePage(
|
||||||
|
@ -109,31 +112,18 @@ async function generatePage(
|
||||||
renderers,
|
renderers,
|
||||||
};
|
};
|
||||||
|
|
||||||
const icon = pageData.route.type === 'page' ? cyan('</>') : magenta('{-}');
|
const icon = pageData.route.type === 'page' ? green('▶') : magenta('λ');
|
||||||
info(opts.logging, null, `${icon} ${pageData.route.component}`);
|
info(opts.logging, null, `${icon} ${pageData.route.component}`);
|
||||||
|
|
||||||
// Throttle the paths to avoid overloading the CPU with too many tasks.
|
for (let i = 0; i < pageData.paths.length; i++) {
|
||||||
const renderPromises = [];
|
const path = pageData.paths[i];
|
||||||
for (const paths of throttle(MAX_CONCURRENT_RENDERS, pageData.paths)) {
|
await generatePath(path, opts, generationOptions);
|
||||||
for (const path of paths) {
|
|
||||||
renderPromises.push(generatePath(path, opts, generationOptions));
|
|
||||||
}
|
|
||||||
// This blocks generating more paths until these 10 complete.
|
|
||||||
await Promise.all(renderPromises);
|
|
||||||
const timeEnd = performance.now();
|
const timeEnd = performance.now();
|
||||||
const timeChange = getTimeStat(timeStart, timeEnd);
|
const timeChange = getTimeStat(timeStart, timeEnd);
|
||||||
let shouldLogTimeChange = !getTimeStat(timeStart, timeEnd).startsWith('0');
|
const timeIncrease = `(+${timeChange})`;
|
||||||
for (const path of paths) {
|
const filePath = getOutputFilename(opts.astroConfig, path);
|
||||||
const timeIncrease = shouldLogTimeChange ? ` ${dim(`+${timeChange}`)}` : '';
|
const lineIcon = i === pageData.paths.length - 1 ? '└─' : '├─';
|
||||||
info(opts.logging, null, ` ${dim('┃')} ${path}${timeIncrease}`);
|
info(opts.logging, null, ` ${cyan(lineIcon)} ${dim(filePath)} ${dim(timeIncrease)}`);
|
||||||
// Should only log build time on the first generated path
|
|
||||||
// Logging for all generated paths adds extra noise
|
|
||||||
shouldLogTimeChange = false;
|
|
||||||
}
|
|
||||||
// Reset timeStart for the next batch of rendered paths
|
|
||||||
timeStart = performance.now();
|
|
||||||
// This empties the array without allocating a new one.
|
|
||||||
renderPromises.length = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,65 +165,61 @@ async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: G
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const url = new URL(origin + pathname);
|
||||||
const options: RenderOptions = {
|
const options: RenderOptions = {
|
||||||
legacyBuild: false,
|
legacyBuild: false,
|
||||||
links,
|
links,
|
||||||
logging,
|
logging,
|
||||||
markdownRender: astroConfig.markdownOptions.render,
|
markdownRender: astroConfig.markdownOptions.render,
|
||||||
mod,
|
mod,
|
||||||
origin,
|
origin,
|
||||||
pathname,
|
pathname,
|
||||||
scripts,
|
scripts,
|
||||||
renderers,
|
renderers,
|
||||||
async resolve(specifier: string) {
|
async resolve(specifier: string) {
|
||||||
const hashedFilePath = internals.entrySpecifierToBundleMap.get(specifier);
|
const hashedFilePath = internals.entrySpecifierToBundleMap.get(specifier);
|
||||||
if (typeof hashedFilePath !== 'string') {
|
if (typeof hashedFilePath !== 'string') {
|
||||||
// If no "astro:scripts/before-hydration.js" script exists in the build,
|
// If no "astro:scripts/before-hydration.js" script exists in the build,
|
||||||
// then we can assume that no before-hydration scripts are needed.
|
// then we can assume that no before-hydration scripts are needed.
|
||||||
// Return this as placeholder, which will be ignored by the browser.
|
// Return this as placeholder, which will be ignored by the browser.
|
||||||
// TODO: In the future, we hope to run this entire script through Vite,
|
// TODO: In the future, we hope to run this entire script through Vite,
|
||||||
// removing the need to maintain our own custom Vite-mimic resolve logic.
|
// removing the need to maintain our own custom Vite-mimic resolve logic.
|
||||||
if (specifier === BEFORE_HYDRATION_SCRIPT_ID) {
|
if (specifier === BEFORE_HYDRATION_SCRIPT_ID) {
|
||||||
return 'data:text/javascript;charset=utf-8,//[no before-hydration script]';
|
return 'data:text/javascript;charset=utf-8,//[no before-hydration script]';
|
||||||
}
|
|
||||||
throw new Error(`Cannot find the built path for ${specifier}`);
|
|
||||||
}
|
}
|
||||||
const relPath = npath.posix.relative(pathname, '/' + hashedFilePath);
|
throw new Error(`Cannot find the built path for ${specifier}`);
|
||||||
const fullyRelativePath = relPath[0] === '.' ? relPath : './' + relPath;
|
|
||||||
return fullyRelativePath;
|
|
||||||
},
|
|
||||||
method: 'GET',
|
|
||||||
headers: new Headers(),
|
|
||||||
route: pageData.route,
|
|
||||||
routeCache,
|
|
||||||
site: astroConfig.buildOptions.site,
|
|
||||||
ssr: opts.astroConfig.buildOptions.experimentalSsr,
|
|
||||||
};
|
|
||||||
|
|
||||||
let body: string;
|
|
||||||
if (pageData.route.type === 'endpoint') {
|
|
||||||
const result = await callEndpoint(mod as unknown as EndpointHandler, options);
|
|
||||||
|
|
||||||
if (result.type === 'response') {
|
|
||||||
throw new Error(`Returning a Response from an endpoint is not supported in SSG mode.`);
|
|
||||||
}
|
}
|
||||||
body = result.body;
|
const relPath = npath.posix.relative(pathname, '/' + hashedFilePath);
|
||||||
} else {
|
const fullyRelativePath = relPath[0] === '.' ? relPath : './' + relPath;
|
||||||
const result = await render(options);
|
return fullyRelativePath;
|
||||||
|
},
|
||||||
|
request: createRequest({ url, headers: new Headers(), logging }),
|
||||||
|
route: pageData.route,
|
||||||
|
routeCache,
|
||||||
|
site: astroConfig.buildOptions.site,
|
||||||
|
ssr: opts.astroConfig.buildOptions.experimentalSsr,
|
||||||
|
};
|
||||||
|
|
||||||
// If there's a redirect or something, just do nothing.
|
let body: string;
|
||||||
if (result.type !== 'html') {
|
if (pageData.route.type === 'endpoint') {
|
||||||
return;
|
const result = await callEndpoint(mod as unknown as EndpointHandler, options);
|
||||||
}
|
|
||||||
body = result.html;
|
if (result.type === 'response') {
|
||||||
|
throw new Error(`Returning a Response from an endpoint is not supported in SSG mode.`);
|
||||||
}
|
}
|
||||||
|
body = result.body;
|
||||||
|
} else {
|
||||||
|
const result = await render(options);
|
||||||
|
|
||||||
const outFolder = getOutFolder(astroConfig, pathname, pageData.route.type);
|
// If there's a redirect or something, just do nothing.
|
||||||
const outFile = getOutFile(astroConfig, outFolder, pathname, pageData.route.type);
|
if (result.type !== 'html') {
|
||||||
await fs.promises.mkdir(outFolder, { recursive: true });
|
return;
|
||||||
await fs.promises.writeFile(outFile, body, 'utf-8');
|
}
|
||||||
} catch (err) {
|
body = result.html;
|
||||||
error(opts.logging, 'build', `Error rendering:`, err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const outFolder = getOutFolder(astroConfig, pathname, pageData.route.type);
|
||||||
|
const outFile = getOutFile(astroConfig, outFolder, pathname, pageData.route.type);
|
||||||
|
await fs.promises.mkdir(outFolder, { recursive: true });
|
||||||
|
await fs.promises.writeFile(outFile, body, 'utf-8');
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ import { staticBuild } from './static-build.js';
|
||||||
import { RouteCache } from '../render/route-cache.js';
|
import { RouteCache } from '../render/route-cache.js';
|
||||||
import { runHookBuildDone, runHookBuildStart, runHookConfigDone, runHookConfigSetup } from '../../integrations/index.js';
|
import { runHookBuildDone, runHookBuildStart, runHookConfigDone, runHookConfigSetup } from '../../integrations/index.js';
|
||||||
import { getTimeStat } from './util.js';
|
import { getTimeStat } from './util.js';
|
||||||
|
import { createSafeError } from '../util.js';
|
||||||
|
import { fixViteErrorMessage } from '../errors.js';
|
||||||
|
|
||||||
export interface BuildOptions {
|
export interface BuildOptions {
|
||||||
mode?: string;
|
mode?: string;
|
||||||
|
@ -26,7 +28,7 @@ export interface BuildOptions {
|
||||||
export default async function build(config: AstroConfig, options: BuildOptions = { logging: defaultLogOptions }): Promise<void> {
|
export default async function build(config: AstroConfig, options: BuildOptions = { logging: defaultLogOptions }): Promise<void> {
|
||||||
config = await runHookConfigSetup({ config, command: 'build' });
|
config = await runHookConfigSetup({ config, command: 'build' });
|
||||||
const builder = new AstroBuilder(config, options);
|
const builder = new AstroBuilder(config, options);
|
||||||
await builder.build();
|
await builder.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
class AstroBuilder {
|
class AstroBuilder {
|
||||||
|
@ -36,32 +38,30 @@ class AstroBuilder {
|
||||||
private origin: string;
|
private origin: string;
|
||||||
private routeCache: RouteCache;
|
private routeCache: RouteCache;
|
||||||
private manifest: ManifestData;
|
private manifest: ManifestData;
|
||||||
private viteServer?: vite.ViteDevServer;
|
private timer: Record<string, number>;
|
||||||
private viteConfig?: ViteConfigWithSSR;
|
|
||||||
|
|
||||||
constructor(config: AstroConfig, options: BuildOptions) {
|
constructor(config: AstroConfig, options: BuildOptions) {
|
||||||
applyPolyfill();
|
|
||||||
|
|
||||||
if (!config.buildOptions.site && config.buildOptions.sitemap !== false) {
|
if (!config.buildOptions.site && config.buildOptions.sitemap !== false) {
|
||||||
warn(options.logging, 'config', `Set "buildOptions.site" to generate correct canonical URLs and sitemap`);
|
warn(options.logging, 'config', `Set "buildOptions.site" to generate correct canonical URLs and sitemap`);
|
||||||
}
|
}
|
||||||
|
if (options.mode) {
|
||||||
if (options.mode) this.mode = options.mode;
|
this.mode = options.mode;
|
||||||
|
}
|
||||||
this.config = config;
|
this.config = config;
|
||||||
const port = config.devOptions.port; // no need to save this (don’t rely on port in builder)
|
const port = config.devOptions.port; // no need to save this (don’t rely on port in builder)
|
||||||
this.logging = options.logging;
|
this.logging = options.logging;
|
||||||
this.routeCache = new RouteCache(this.logging);
|
this.routeCache = new RouteCache(this.logging);
|
||||||
this.origin = config.buildOptions.site ? new URL(config.buildOptions.site).origin : `http://localhost:${port}`;
|
this.origin = config.buildOptions.site ? new URL(config.buildOptions.site).origin : `http://localhost:${port}`;
|
||||||
this.manifest = createRouteManifest({ config }, this.logging);
|
this.manifest = createRouteManifest({ config }, this.logging);
|
||||||
|
this.timer = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
async build() {
|
/** Setup Vite and run any async setup logic that couldn't run inside of the constructor. */
|
||||||
info(this.logging, 'build', 'Initial setup...');
|
private async setup() {
|
||||||
|
debug('build', 'Initial setup...');
|
||||||
const { logging, origin } = this;
|
const { logging } = this;
|
||||||
const timer: Record<string, number> = {};
|
this.timer.init = performance.now();
|
||||||
timer.init = performance.now();
|
this.timer.viteStart = performance.now();
|
||||||
timer.viteStart = performance.now();
|
|
||||||
const viteConfig = await createVite(
|
const viteConfig = await createVite(
|
||||||
{
|
{
|
||||||
mode: this.mode,
|
mode: this.mode,
|
||||||
|
@ -74,10 +74,14 @@ class AstroBuilder {
|
||||||
);
|
);
|
||||||
await runHookConfigDone({ config: this.config });
|
await runHookConfigDone({ config: this.config });
|
||||||
warnIfUsingExperimentalSSR(logging, this.config);
|
warnIfUsingExperimentalSSR(logging, this.config);
|
||||||
this.viteConfig = viteConfig;
|
|
||||||
const viteServer = await vite.createServer(viteConfig);
|
const viteServer = await vite.createServer(viteConfig);
|
||||||
this.viteServer = viteServer;
|
debug('build', timerMessage('Vite started', this.timer.viteStart));
|
||||||
debug('build', timerMessage('Vite started', timer.viteStart));
|
return { viteConfig, viteServer };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Run the build logic. build() is marked private because usage should go through ".run()" */
|
||||||
|
private async build({ viteConfig, viteServer }: { viteConfig: ViteConfigWithSSR; viteServer: vite.ViteDevServer }) {
|
||||||
|
const { origin } = this;
|
||||||
const buildConfig: BuildConfig = {
|
const buildConfig: BuildConfig = {
|
||||||
client: new URL('./client/', this.config.dist),
|
client: new URL('./client/', this.config.dist),
|
||||||
server: new URL('./server/', this.config.dist),
|
server: new URL('./server/', this.config.dist),
|
||||||
|
@ -86,15 +90,15 @@ class AstroBuilder {
|
||||||
};
|
};
|
||||||
await runHookBuildStart({ config: this.config, buildConfig });
|
await runHookBuildStart({ config: this.config, buildConfig });
|
||||||
|
|
||||||
info(this.logging, 'build', 'Collecting page data...');
|
info(this.logging, 'build', 'Collecting build information...');
|
||||||
timer.loadStart = performance.now();
|
this.timer.loadStart = performance.now();
|
||||||
const { assets, allPages } = await collectPagesData({
|
const { assets, allPages } = await collectPagesData({
|
||||||
astroConfig: this.config,
|
astroConfig: this.config,
|
||||||
logging: this.logging,
|
logging: this.logging,
|
||||||
manifest: this.manifest,
|
manifest: this.manifest,
|
||||||
origin,
|
origin,
|
||||||
routeCache: this.routeCache,
|
routeCache: this.routeCache,
|
||||||
viteServer: this.viteServer,
|
viteServer,
|
||||||
ssr: this.config.buildOptions.experimentalSsr,
|
ssr: this.config.buildOptions.experimentalSsr,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -104,21 +108,21 @@ class AstroBuilder {
|
||||||
// TODO: add better type inference to data.preload[1]
|
// TODO: add better type inference to data.preload[1]
|
||||||
const frontmatter = (data.preload[1] as any).frontmatter;
|
const frontmatter = (data.preload[1] as any).frontmatter;
|
||||||
if (Boolean(frontmatter.draft) && !this.config.buildOptions.drafts) {
|
if (Boolean(frontmatter.draft) && !this.config.buildOptions.drafts) {
|
||||||
debug('build', timerMessage(`Skipping draft page ${page}`, timer.loadStart));
|
debug('build', timerMessage(`Skipping draft page ${page}`, this.timer.loadStart));
|
||||||
delete allPages[page];
|
delete allPages[page];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
debug('build', timerMessage('All pages loaded', timer.loadStart));
|
debug('build', timerMessage('All pages loaded', this.timer.loadStart));
|
||||||
|
|
||||||
// The names of each pages
|
// The names of each pages
|
||||||
const pageNames: string[] = [];
|
const pageNames: string[] = [];
|
||||||
|
|
||||||
// Bundle the assets in your final build: This currently takes the HTML output
|
// Bundle the assets in your final build: This currently takes the HTML output
|
||||||
// of every page (stored in memory) and bundles the assets pointed to on those pages.
|
// of every page (stored in memory) and bundles the assets pointed to on those pages.
|
||||||
timer.buildStart = performance.now();
|
this.timer.buildStart = performance.now();
|
||||||
info(this.logging, 'build', colors.dim(`Completed in ${getTimeStat(timer.init, performance.now())}`));
|
info(this.logging, 'build', colors.dim(`Completed in ${getTimeStat(this.timer.init, performance.now())}.`));
|
||||||
|
|
||||||
// Use the new faster static based build.
|
// Use the new faster static based build.
|
||||||
if (!this.config.buildOptions.legacyBuild) {
|
if (!this.config.buildOptions.legacyBuild) {
|
||||||
|
@ -130,7 +134,7 @@ class AstroBuilder {
|
||||||
origin: this.origin,
|
origin: this.origin,
|
||||||
pageNames,
|
pageNames,
|
||||||
routeCache: this.routeCache,
|
routeCache: this.routeCache,
|
||||||
viteConfig: this.viteConfig,
|
viteConfig,
|
||||||
buildConfig,
|
buildConfig,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -141,13 +145,13 @@ class AstroBuilder {
|
||||||
origin: this.origin,
|
origin: this.origin,
|
||||||
pageNames,
|
pageNames,
|
||||||
routeCache: this.routeCache,
|
routeCache: this.routeCache,
|
||||||
viteConfig: this.viteConfig,
|
viteConfig,
|
||||||
viteServer: this.viteServer,
|
viteServer,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write any additionally generated assets to disk.
|
// Write any additionally generated assets to disk.
|
||||||
timer.assetsStart = performance.now();
|
this.timer.assetsStart = performance.now();
|
||||||
Object.keys(assets).map((k) => {
|
Object.keys(assets).map((k) => {
|
||||||
if (!assets[k]) return;
|
if (!assets[k]) return;
|
||||||
const filePath = new URL(`file://${k}`);
|
const filePath = new URL(`file://${k}`);
|
||||||
|
@ -155,11 +159,11 @@ class AstroBuilder {
|
||||||
fs.writeFileSync(filePath, assets[k], 'utf8');
|
fs.writeFileSync(filePath, assets[k], 'utf8');
|
||||||
delete assets[k]; // free up memory
|
delete assets[k]; // free up memory
|
||||||
});
|
});
|
||||||
debug('build', timerMessage('Additional assets copied', timer.assetsStart));
|
debug('build', timerMessage('Additional assets copied', this.timer.assetsStart));
|
||||||
|
|
||||||
// Build your final sitemap.
|
// Build your final sitemap.
|
||||||
if (this.config.buildOptions.sitemap && this.config.buildOptions.site) {
|
if (this.config.buildOptions.sitemap && this.config.buildOptions.site) {
|
||||||
timer.sitemapStart = performance.now();
|
this.timer.sitemapStart = performance.now();
|
||||||
const sitemapFilter = this.config.buildOptions.sitemapFilter ? (this.config.buildOptions.sitemapFilter as (page: string) => boolean) : undefined;
|
const sitemapFilter = this.config.buildOptions.sitemapFilter ? (this.config.buildOptions.sitemapFilter as (page: string) => boolean) : undefined;
|
||||||
const sitemap = generateSitemap(
|
const sitemap = generateSitemap(
|
||||||
pageNames.map((pageName) => new URL(pageName, this.config.buildOptions.site).href),
|
pageNames.map((pageName) => new URL(pageName, this.config.buildOptions.site).href),
|
||||||
|
@ -168,16 +172,27 @@ class AstroBuilder {
|
||||||
const sitemapPath = new URL('./sitemap.xml', this.config.dist);
|
const sitemapPath = new URL('./sitemap.xml', this.config.dist);
|
||||||
await fs.promises.mkdir(new URL('./', sitemapPath), { recursive: true });
|
await fs.promises.mkdir(new URL('./', sitemapPath), { recursive: true });
|
||||||
await fs.promises.writeFile(sitemapPath, sitemap, 'utf8');
|
await fs.promises.writeFile(sitemapPath, sitemap, 'utf8');
|
||||||
debug('build', timerMessage('Sitemap built', timer.sitemapStart));
|
debug('build', timerMessage('Sitemap built', this.timer.sitemapStart));
|
||||||
}
|
}
|
||||||
|
|
||||||
// You're done! Time to clean up.
|
// You're done! Time to clean up.
|
||||||
await viteServer.close();
|
await viteServer.close();
|
||||||
await runHookBuildDone({ config: this.config, pages: pageNames, routes: Object.values(allPages).map((pd) => pd.route) });
|
await runHookBuildDone({ config: this.config, pages: pageNames, routes: Object.values(allPages).map((pd) => pd.route) });
|
||||||
|
|
||||||
if (logging.level && levels[logging.level] <= levels['info']) {
|
if (this.logging.level && levels[this.logging.level] <= levels['info']) {
|
||||||
const buildMode = this.config.buildOptions.experimentalSsr ? 'ssr' : 'static';
|
const buildMode = this.config.buildOptions.experimentalSsr ? 'ssr' : 'static';
|
||||||
await this.printStats({ logging, timeStart: timer.init, pageCount: pageNames.length, buildMode });
|
await this.printStats({ logging: this.logging, timeStart: this.timer.init, pageCount: pageNames.length, buildMode });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Build the given Astro project. */
|
||||||
|
async run() {
|
||||||
|
const setupData = await this.setup();
|
||||||
|
try {
|
||||||
|
await this.build(setupData);
|
||||||
|
} catch (_err) {
|
||||||
|
debugger;
|
||||||
|
throw fixViteErrorMessage(createSafeError(_err), setupData.viteServer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,14 +203,12 @@ class AstroBuilder {
|
||||||
|
|
||||||
let messages: string[] = [];
|
let messages: string[] = [];
|
||||||
if (buildMode === 'static') {
|
if (buildMode === 'static') {
|
||||||
const timePerPage = Math.round(buildTime / pageCount);
|
messages = [`${pageCount} page(s) built in`, colors.bold(total)];
|
||||||
const perPageMsg = colors.dim(`(${colors.bold(`${timePerPage}ms`)} avg per page + resources)`);
|
|
||||||
messages = [`${pageCount} pages built in`, colors.bold(total), perPageMsg];
|
|
||||||
} else {
|
} else {
|
||||||
messages = ['Server built in', colors.bold(total)];
|
messages = ['Server built in', colors.bold(total)];
|
||||||
}
|
}
|
||||||
|
|
||||||
info(logging, 'build', messages.join(' '));
|
info(logging, 'build', messages.join(' '));
|
||||||
info(logging, 'build', `🚀 ${colors.cyan(colors.bold('Done'))}`);
|
info(logging, 'build', `${colors.bold('Complete!')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,6 @@ export async function staticBuild(opts: StaticBuildOptions) {
|
||||||
const timer: Record<string, number> = {};
|
const timer: Record<string, number> = {};
|
||||||
|
|
||||||
timer.buildStart = performance.now();
|
timer.buildStart = performance.now();
|
||||||
info(opts.logging, 'build', 'Discovering entrypoints...');
|
|
||||||
|
|
||||||
for (const [component, pageData] of Object.entries(allPages)) {
|
for (const [component, pageData] of Object.entries(allPages)) {
|
||||||
const astroModuleURL = new URL('./' + component, astroConfig.projectRoot);
|
const astroModuleURL = new URL('./' + component, astroConfig.projectRoot);
|
||||||
|
@ -97,7 +96,7 @@ export async function staticBuild(opts: StaticBuildOptions) {
|
||||||
timer.ssr = performance.now();
|
timer.ssr = performance.now();
|
||||||
info(opts.logging, 'build', 'Building for SSR...');
|
info(opts.logging, 'build', 'Building for SSR...');
|
||||||
const ssrResult = (await ssrBuild(opts, internals, pageInput)) as RollupOutput;
|
const ssrResult = (await ssrBuild(opts, internals, pageInput)) as RollupOutput;
|
||||||
info(opts.logging, 'build', dim(`Completed in ${getTimeStat(timer.ssr, performance.now())}`));
|
info(opts.logging, 'build', dim(`Completed in ${getTimeStat(timer.ssr, performance.now())}.`));
|
||||||
|
|
||||||
timer.generate = performance.now();
|
timer.generate = performance.now();
|
||||||
if (opts.buildConfig.staticMode) {
|
if (opts.buildConfig.staticMode) {
|
||||||
|
@ -107,7 +106,6 @@ export async function staticBuild(opts: StaticBuildOptions) {
|
||||||
info(opts.logging, null, `\n${bgMagenta(black(' finalizing server assets '))}\n`);
|
info(opts.logging, null, `\n${bgMagenta(black(' finalizing server assets '))}\n`);
|
||||||
await ssrMoveAssets(opts);
|
await ssrMoveAssets(opts);
|
||||||
}
|
}
|
||||||
info(opts.logging, null, dim(`Completed in ${getTimeStat(timer.generate, performance.now())}\n`));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, input: Set<string>) {
|
async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, input: Set<string>) {
|
||||||
|
@ -171,7 +169,7 @@ async function clientBuild(opts: StaticBuildOptions, internals: BuildInternals,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use vite.mergeConfig() here?
|
// TODO: use vite.mergeConfig() here?
|
||||||
info(opts.logging, null, `\n${bgGreen(black(' building resources '))}\n`);
|
info(opts.logging, null, `\n${bgGreen(black(' building client '))}`);
|
||||||
|
|
||||||
const out = isBuildingToSSR(astroConfig) ? opts.buildConfig.client : astroConfig.dist;
|
const out = isBuildingToSSR(astroConfig) ? opts.buildConfig.client : astroConfig.dist;
|
||||||
|
|
||||||
|
@ -210,7 +208,7 @@ async function clientBuild(opts: StaticBuildOptions, internals: BuildInternals,
|
||||||
server: viteConfig.server,
|
server: viteConfig.server,
|
||||||
base: appendForwardSlash(astroConfig.buildOptions.site ? new URL(astroConfig.buildOptions.site).pathname : '/'),
|
base: appendForwardSlash(astroConfig.buildOptions.site ? new URL(astroConfig.buildOptions.site).pathname : '/'),
|
||||||
});
|
});
|
||||||
info(opts.logging, null, dim(`Completed in ${getTimeStat(timer, performance.now())}\n`));
|
info(opts.logging, null, dim(`Completed in ${getTimeStat(timer, performance.now())}.\n`));
|
||||||
return buildResult;
|
return buildResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ const manifestReplace = '@@ASTRO_MANIFEST_REPLACE@@';
|
||||||
export function vitePluginSSR(buildOpts: StaticBuildOptions, internals: BuildInternals, adapter: AstroAdapter): VitePlugin {
|
export function vitePluginSSR(buildOpts: StaticBuildOptions, internals: BuildInternals, adapter: AstroAdapter): VitePlugin {
|
||||||
return {
|
return {
|
||||||
name: '@astrojs/vite-plugin-astro-ssr',
|
name: '@astrojs/vite-plugin-astro-ssr',
|
||||||
|
enforce: 'post',
|
||||||
options(opts) {
|
options(opts) {
|
||||||
return addRollupInput(opts, [virtualModuleId]);
|
return addRollupInput(opts, [virtualModuleId]);
|
||||||
},
|
},
|
||||||
|
|
|
@ -327,11 +327,6 @@ export async function resolveConfig(userConfig: AstroUserConfig, root: string, f
|
||||||
return validatedConfig;
|
return validatedConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatConfigError(err: z.ZodError) {
|
|
||||||
const errorList = err.issues.map((issue) => ` ! ${colors.bold(issue.path.join('.'))} ${colors.red(issue.message + '.')}`);
|
|
||||||
return `${colors.red('[config]')} Astro found issue(s) with your configuration:\n${errorList.join('\n')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function mergeConfigRecursively(defaults: Record<string, any>, overrides: Record<string, any>, rootPath: string) {
|
function mergeConfigRecursively(defaults: Record<string, any>, overrides: Record<string, any>, rootPath: string) {
|
||||||
const merged: Record<string, any> = { ...defaults };
|
const merged: Record<string, any> = { ...defaults };
|
||||||
for (const key in overrides) {
|
for (const key in overrides) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { createVite } from '../create-vite.js';
|
||||||
import { defaultLogOptions, info, LogOptions, warn, warnIfUsingExperimentalSSR } from '../logger.js';
|
import { defaultLogOptions, info, LogOptions, warn, warnIfUsingExperimentalSSR } from '../logger.js';
|
||||||
import * as msg from '../messages.js';
|
import * as msg from '../messages.js';
|
||||||
import { apply as applyPolyfill } from '../polyfill.js';
|
import { apply as applyPolyfill } from '../polyfill.js';
|
||||||
import { getResolvedHostForVite } from './util.js';
|
import { getResolvedHostForVite } from '../util.js';
|
||||||
|
|
||||||
export interface DevOptions {
|
export interface DevOptions {
|
||||||
logging: LogOptions;
|
logging: LogOptions;
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
import type { AstroConfig } from '../../@types/astro';
|
|
||||||
|
|
||||||
export const localIps = new Set(['localhost', '127.0.0.1']);
|
|
||||||
|
|
||||||
/** Pad string () */
|
|
||||||
export function pad(input: string, minLength: number, dir?: 'left' | 'right'): string {
|
|
||||||
let output = input;
|
|
||||||
while (output.length < minLength) {
|
|
||||||
output = dir === 'left' ? ' ' + output : output + ' ';
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function emoji(char: string, fallback: string) {
|
|
||||||
return process.platform !== 'win32' ? char : fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove once --hostname is baselined
|
|
||||||
export function getResolvedHostForVite(config: AstroConfig) {
|
|
||||||
if (config.devOptions.host === false && config.devOptions.hostname !== 'localhost') {
|
|
||||||
return config.devOptions.hostname;
|
|
||||||
} else {
|
|
||||||
return config.devOptions.host;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getLocalAddress(serverAddress: string, config: AstroConfig): string {
|
|
||||||
// TODO: remove once --hostname is baselined
|
|
||||||
const host = getResolvedHostForVite(config);
|
|
||||||
if (typeof host === 'boolean' || host === 'localhost') {
|
|
||||||
return 'localhost';
|
|
||||||
} else {
|
|
||||||
return serverAddress;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NetworkLogging = 'none' | 'host-to-expose' | 'visible';
|
|
||||||
|
|
||||||
export function getNetworkLogging(config: AstroConfig): NetworkLogging {
|
|
||||||
// TODO: remove once --hostname is baselined
|
|
||||||
const host = getResolvedHostForVite(config);
|
|
||||||
|
|
||||||
if (host === false) {
|
|
||||||
return 'host-to-expose';
|
|
||||||
} else if (typeof host === 'string' && localIps.has(host)) {
|
|
||||||
return 'none';
|
|
||||||
} else {
|
|
||||||
return 'visible';
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +1,12 @@
|
||||||
import type { EndpointHandler } from '../../../@types/astro';
|
import type { EndpointHandler } from '../../../@types/astro';
|
||||||
import type { SSROptions } from '../../render/dev';
|
import type { SSROptions } from '../../render/dev';
|
||||||
|
|
||||||
import { preload } from '../../render/dev/index.js';
|
import { preload } from '../../render/dev/index.js';
|
||||||
import { errorHandler } from '../../render/dev/error.js';
|
|
||||||
import { call as callEndpoint } from '../index.js';
|
import { call as callEndpoint } from '../index.js';
|
||||||
import { getParamsAndProps, GetParamsAndPropsError } from '../../render/core.js';
|
|
||||||
import { createRequest } from '../../render/request.js';
|
|
||||||
|
|
||||||
export async function call(ssrOpts: SSROptions) {
|
export async function call(ssrOpts: SSROptions) {
|
||||||
try {
|
const [, mod] = await preload(ssrOpts);
|
||||||
const [, mod] = await preload(ssrOpts);
|
return await callEndpoint(mod as unknown as EndpointHandler, {
|
||||||
return await callEndpoint(mod as unknown as EndpointHandler, {
|
...ssrOpts,
|
||||||
...ssrOpts,
|
ssr: ssrOpts.astroConfig.buildOptions.experimentalSsr,
|
||||||
ssr: ssrOpts.astroConfig.buildOptions.experimentalSsr,
|
});
|
||||||
});
|
|
||||||
} catch (e: unknown) {
|
|
||||||
await errorHandler(e, { viteServer: ssrOpts.viteServer, filePath: ssrOpts.filePath });
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,8 @@ import type { EndpointHandler } from '../../@types/astro';
|
||||||
import type { RenderOptions } from '../render/core';
|
import type { RenderOptions } from '../render/core';
|
||||||
import { renderEndpoint } from '../../runtime/server/index.js';
|
import { renderEndpoint } from '../../runtime/server/index.js';
|
||||||
import { getParamsAndProps, GetParamsAndPropsError } from '../render/core.js';
|
import { getParamsAndProps, GetParamsAndPropsError } from '../render/core.js';
|
||||||
import { createRequest } from '../render/request.js';
|
|
||||||
|
|
||||||
export type EndpointOptions = Pick<RenderOptions, 'logging' | 'headers' | 'method' | 'origin' | 'route' | 'routeCache' | 'pathname' | 'route' | 'site' | 'ssr'>;
|
export type EndpointOptions = Pick<RenderOptions, 'logging' | 'origin' | 'request' | 'route' | 'routeCache' | 'pathname' | 'route' | 'site' | 'ssr'>;
|
||||||
|
|
||||||
type EndpointCallResult =
|
type EndpointCallResult =
|
||||||
| {
|
| {
|
||||||
|
@ -23,9 +22,8 @@ export async function call(mod: EndpointHandler, opts: EndpointOptions): Promise
|
||||||
throw new Error(`[getStaticPath] route pattern matched, but no matching static path found. (${opts.pathname})`);
|
throw new Error(`[getStaticPath] route pattern matched, but no matching static path found. (${opts.pathname})`);
|
||||||
}
|
}
|
||||||
const [params] = paramsAndPropsResp;
|
const [params] = paramsAndPropsResp;
|
||||||
const request = createRequest(opts.method, opts.pathname, opts.headers, opts.origin, opts.site, opts.ssr);
|
|
||||||
|
|
||||||
const response = await renderEndpoint(mod, request, params);
|
const response = await renderEndpoint(mod, opts.request, params);
|
||||||
|
|
||||||
if (response instanceof Response) {
|
if (response instanceof Response) {
|
||||||
return {
|
return {
|
||||||
|
|
78
packages/astro/src/core/errors.ts
Normal file
78
packages/astro/src/core/errors.ts
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import type { BuildResult } from 'esbuild';
|
||||||
|
import type { ViteDevServer } from 'vite';
|
||||||
|
import type { SSRError } from '../@types/astro';
|
||||||
|
import eol from 'eol';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { codeFrame, createSafeError } from './util.js';
|
||||||
|
|
||||||
|
export interface ErrorWithMetadata {
|
||||||
|
[name: string]: any;
|
||||||
|
message: string;
|
||||||
|
stack: string;
|
||||||
|
id?: string;
|
||||||
|
frame?: string;
|
||||||
|
plugin?: string;
|
||||||
|
pluginCode?: string;
|
||||||
|
loc?: {
|
||||||
|
file?: string;
|
||||||
|
line: number;
|
||||||
|
column: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cleanErrorStack(stack: string) {
|
||||||
|
return stack
|
||||||
|
.split(/\n/g)
|
||||||
|
.filter((l) => /^\s*at/.test(l))
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update the error message to correct any vite-isms that we don't want to expose to the user. */
|
||||||
|
export function fixViteErrorMessage(_err: unknown, server: ViteDevServer) {
|
||||||
|
const err = createSafeError(_err);
|
||||||
|
// Vite will give you better stacktraces, using sourcemaps.
|
||||||
|
server.ssrFixStacktrace(err);
|
||||||
|
// Fix: Astro.glob() compiles to import.meta.glob() by the time Vite sees it,
|
||||||
|
// so we need to update this error message in case it originally came from Astro.glob().
|
||||||
|
if (err.message === 'import.meta.glob() can only accept string literals.') {
|
||||||
|
err.message = 'Astro.glob() and import.meta.glob() can only accept string literals.';
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes any error-like object and returns a standardized Error + metadata object.
|
||||||
|
* Useful for consistent reporting regardless of where the error surfaced from.
|
||||||
|
*/
|
||||||
|
export function collectErrorMetadata(e: any): ErrorWithMetadata {
|
||||||
|
// normalize error stack line-endings to \n
|
||||||
|
if ((e as any).stack) {
|
||||||
|
(e as any).stack = eol.lf((e as any).stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Astro error (thrown by esbuild so it needs to be formatted for Vite)
|
||||||
|
if (Array.isArray((e as any).errors)) {
|
||||||
|
const { location, pluginName, text } = (e as BuildResult).errors[0];
|
||||||
|
const err = e as SSRError;
|
||||||
|
if (location) {
|
||||||
|
err.loc = { file: location.file, line: location.line, column: location.column };
|
||||||
|
err.id = err.id || location?.file;
|
||||||
|
}
|
||||||
|
const possibleFilePath = err.pluginCode || err.id || location?.file;
|
||||||
|
if (possibleFilePath && !err.frame) {
|
||||||
|
try {
|
||||||
|
const fileContents = fs.readFileSync(possibleFilePath, 'utf8');
|
||||||
|
err.frame = codeFrame(fileContents, err.loc);
|
||||||
|
} catch {
|
||||||
|
// do nothing, code frame isn't that big a deal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pluginName) {
|
||||||
|
err.plugin = pluginName;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic error (probably from Vite, and already formatted)
|
||||||
|
return e;
|
||||||
|
}
|
|
@ -2,12 +2,13 @@
|
||||||
* Dev server messages (organized here to prevent clutter)
|
* Dev server messages (organized here to prevent clutter)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import stripAnsi from 'strip-ansi';
|
|
||||||
import { bold, dim, red, green, underline, yellow, bgYellow, cyan, bgGreen, black, bgRed, bgWhite } from 'kleur/colors';
|
import { bold, dim, red, green, underline, yellow, bgYellow, cyan, bgGreen, black, bgRed, bgWhite } from 'kleur/colors';
|
||||||
import { pad, emoji, getLocalAddress, getNetworkLogging } from './dev/util.js';
|
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import type { AddressInfo } from 'net';
|
import type { AddressInfo } from 'net';
|
||||||
import type { AstroConfig } from '../@types/astro';
|
import type { AstroConfig } from '../@types/astro';
|
||||||
|
import { collectErrorMetadata, cleanErrorStack } from './errors.js';
|
||||||
|
import { ZodError } from 'zod';
|
||||||
|
import { emoji, getLocalAddress, getResolvedHostForVite, padMultilineString } from './util.js';
|
||||||
|
|
||||||
const PREFIX_PADDING = 6;
|
const PREFIX_PADDING = 6;
|
||||||
|
|
||||||
|
@ -18,15 +19,15 @@ export function req({ url, statusCode, reqTime }: { url: string; statusCode: num
|
||||||
else if (statusCode >= 400) color = yellow;
|
else if (statusCode >= 400) color = yellow;
|
||||||
else if (statusCode >= 300) color = dim;
|
else if (statusCode >= 300) color = dim;
|
||||||
else if (statusCode >= 200) color = green;
|
else if (statusCode >= 200) color = green;
|
||||||
return `${bold(color(pad(`${statusCode}`, PREFIX_PADDING)))} ${pad(url, 40)} ${reqTime ? dim(Math.round(reqTime) + 'ms') : ''}`.trim();
|
return `${bold(color(`${statusCode}`.padStart(PREFIX_PADDING)))} ${url.padStart(40)} ${reqTime ? dim(Math.round(reqTime) + 'ms') : ''}`.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reload({ file }: { file: string }): string {
|
export function reload({ file }: { file: string }): string {
|
||||||
return `${green(pad('reload', PREFIX_PADDING))} ${file}`;
|
return `${green('reload'.padStart(PREFIX_PADDING))} ${file}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hmr({ file }: { file: string }): string {
|
export function hmr({ file }: { file: string }): string {
|
||||||
return `${green(pad('update', PREFIX_PADDING))} ${file}`;
|
return `${green('update'.padStart(PREFIX_PADDING))} ${file}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Display dev server host and startup time */
|
/** Display dev server host and startup time */
|
||||||
|
@ -91,7 +92,7 @@ export function success(message: string, tip?: string) {
|
||||||
const badge = bgGreen(black(` success `));
|
const badge = bgGreen(black(` success `));
|
||||||
const headline = green(message);
|
const headline = green(message);
|
||||||
const footer = tip ? `\n ▶ ${tip}` : undefined;
|
const footer = tip ? `\n ▶ ${tip}` : undefined;
|
||||||
return ['', badge, headline, footer]
|
return ['', `${badge} ${headline}`, footer]
|
||||||
.filter((v) => v !== undefined)
|
.filter((v) => v !== undefined)
|
||||||
.map((msg) => ` ${msg}`)
|
.map((msg) => ` ${msg}`)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
|
@ -101,7 +102,7 @@ export function failure(message: string, tip?: string) {
|
||||||
const badge = bgRed(black(` error `));
|
const badge = bgRed(black(` error `));
|
||||||
const headline = red(message);
|
const headline = red(message);
|
||||||
const footer = tip ? `\n ▶ ${tip}` : undefined;
|
const footer = tip ? `\n ▶ ${tip}` : undefined;
|
||||||
return ['', badge, headline, footer]
|
return ['', `${badge} ${headline}`, footer]
|
||||||
.filter((v) => v !== undefined)
|
.filter((v) => v !== undefined)
|
||||||
.map((msg) => ` ${msg}`)
|
.map((msg) => ` ${msg}`)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
|
@ -111,7 +112,7 @@ export function cancelled(message: string, tip?: string) {
|
||||||
const badge = bgYellow(black(` cancelled `));
|
const badge = bgYellow(black(` cancelled `));
|
||||||
const headline = yellow(message);
|
const headline = yellow(message);
|
||||||
const footer = tip ? `\n ▶ ${tip}` : undefined;
|
const footer = tip ? `\n ▶ ${tip}` : undefined;
|
||||||
return ['', badge, headline, footer]
|
return ['', `${badge} ${headline}`, footer]
|
||||||
.filter((v) => v !== undefined)
|
.filter((v) => v !== undefined)
|
||||||
.map((msg) => ` ${msg}`)
|
.map((msg) => ` ${msg}`)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
|
@ -122,15 +123,45 @@ export function portInUse({ port }: { port: number }): string {
|
||||||
return `Port ${port} in use. Trying a new one…`;
|
return `Port ${port} in use. Trying a new one…`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Pretty-print errors */
|
const LOCAL_IP_HOSTS = new Set(['localhost', '127.0.0.1']);
|
||||||
export function err(error: Error): string {
|
|
||||||
if (!error.stack) return stripAnsi(error.message);
|
export function getNetworkLogging(config: AstroConfig): 'none' | 'host-to-expose' | 'visible' {
|
||||||
let message = stripAnsi(error.message);
|
// TODO: remove once --hostname is baselined
|
||||||
let stack = stripAnsi(error.stack);
|
const host = getResolvedHostForVite(config);
|
||||||
const split = stack.indexOf(message) + message.length;
|
|
||||||
message = stack.slice(0, split);
|
if (host === false) {
|
||||||
stack = stack.slice(split).replace(/^\n+/, '');
|
return 'host-to-expose';
|
||||||
return `${message}\n${dim(stack)}`;
|
} else if (typeof host === 'string' && LOCAL_IP_HOSTS.has(host)) {
|
||||||
|
return 'none';
|
||||||
|
} else {
|
||||||
|
return 'visible';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatConfigErrorMessage(err: ZodError) {
|
||||||
|
const errorList = err.issues.map((issue) => ` ! ${bold(issue.path.join('.'))} ${red(issue.message + '.')}`);
|
||||||
|
return `${red('[config]')} Astro found issue(s) with your configuration:\n${errorList.join('\n')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatErrorMessage(_err: Error, args: string[] = []): string {
|
||||||
|
const err = collectErrorMetadata(_err);
|
||||||
|
args.push(`${bgRed(black(` error `))}${red(bold(padMultilineString(err.message)))}`);
|
||||||
|
if (err.id) {
|
||||||
|
args.push(` ${bold('File:')}`);
|
||||||
|
args.push(red(` ${err.id}`));
|
||||||
|
}
|
||||||
|
if (err.frame) {
|
||||||
|
args.push(` ${bold('Code:')}`);
|
||||||
|
args.push(red(padMultilineString(err.frame, 4)));
|
||||||
|
}
|
||||||
|
if (args.length === 1 && err.stack) {
|
||||||
|
args.push(dim(cleanErrorStack(err.stack)));
|
||||||
|
} else if (err.stack) {
|
||||||
|
args.push(` ${bold('Stacktrace:')}`);
|
||||||
|
args.push(dim(cleanErrorStack(err.stack)));
|
||||||
|
args.push(``);
|
||||||
|
}
|
||||||
|
return args.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function printHelp({
|
export function printHelp({
|
||||||
|
@ -157,7 +188,7 @@ export function printHelp({
|
||||||
let raw = '';
|
let raw = '';
|
||||||
|
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
raw += `${opts.prefix}${bold(pad(`${row[0]}`, opts.padding - opts.prefix.length))}`;
|
raw += `${opts.prefix}${bold(`${row[0]}`.padStart(opts.padding - opts.prefix.length))}`;
|
||||||
if (split) raw += '\n ';
|
if (split) raw += '\n ';
|
||||||
raw += dim(row[1]) + '\n';
|
raw += dim(row[1]) + '\n';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import type { ComponentInstance, EndpointHandler, MarkdownRenderOptions, Params, Props, SSRLoadedRenderer, RouteData, SSRElement } from '../../@types/astro';
|
import type { ComponentInstance, EndpointHandler, MarkdownRenderOptions, Params, Props, SSRLoadedRenderer, RouteData, SSRElement } from '../../@types/astro';
|
||||||
import type { LogOptions } from '../logger.js';
|
import type { LogOptions } from '../logger.js';
|
||||||
import type { AstroRequest } from './request';
|
|
||||||
|
|
||||||
import { renderHead, renderPage } from '../../runtime/server/index.js';
|
import { renderHead, renderPage } from '../../runtime/server/index.js';
|
||||||
import { getParams } from '../routing/index.js';
|
import { getParams } from '../routing/index.js';
|
||||||
|
@ -71,12 +70,11 @@ export interface RenderOptions {
|
||||||
routeCache: RouteCache;
|
routeCache: RouteCache;
|
||||||
site?: string;
|
site?: string;
|
||||||
ssr: boolean;
|
ssr: boolean;
|
||||||
method: string;
|
request: Request;
|
||||||
headers: Headers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function render(opts: RenderOptions): Promise<{ type: 'html'; html: string } | { type: 'response'; response: Response }> {
|
export async function render(opts: RenderOptions): Promise<{ type: 'html'; html: string } | { type: 'response'; response: Response }> {
|
||||||
const { headers, legacyBuild, links, logging, origin, markdownRender, method, mod, pathname, scripts, renderers, resolve, route, routeCache, site, ssr } = opts;
|
const { legacyBuild, links, logging, origin, markdownRender, mod, pathname, scripts, renderers, request, resolve, route, routeCache, site, ssr } = opts;
|
||||||
|
|
||||||
const paramsAndPropsRes = await getParamsAndProps({
|
const paramsAndPropsRes = await getParamsAndProps({
|
||||||
logging,
|
logging,
|
||||||
|
@ -107,11 +105,10 @@ export async function render(opts: RenderOptions): Promise<{ type: 'html'; html:
|
||||||
pathname,
|
pathname,
|
||||||
resolve,
|
resolve,
|
||||||
renderers,
|
renderers,
|
||||||
|
request,
|
||||||
site,
|
site,
|
||||||
scripts,
|
scripts,
|
||||||
ssr,
|
ssr,
|
||||||
method,
|
|
||||||
headers,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let page = await renderPage(result, Component, pageProps, null);
|
let page = await renderPage(result, Component, pageProps, null);
|
||||||
|
|
|
@ -29,10 +29,15 @@ export function getStylesForURL(filePath: URL, viteServer: vite.ViteDevServer):
|
||||||
: // Otherwise, you are following an import in the module import tree.
|
: // Otherwise, you are following an import in the module import tree.
|
||||||
// You are safe to use getModuleById() here because Vite has already
|
// You are safe to use getModuleById() here because Vite has already
|
||||||
// resolved the correct `id` for you, by creating the import you followed here.
|
// resolved the correct `id` for you, by creating the import you followed here.
|
||||||
new Set([viteServer.moduleGraph.getModuleById(id)!]);
|
new Set([viteServer.moduleGraph.getModuleById(id)]);
|
||||||
|
|
||||||
// Collect all imported modules for the module(s).
|
// Collect all imported modules for the module(s).
|
||||||
for (const entry of moduleEntriesForId) {
|
for (const entry of moduleEntriesForId) {
|
||||||
|
// Handle this in case an module entries weren't found for ID
|
||||||
|
// This seems possible with some virtual IDs (ex: `astro:markdown/*.md`)
|
||||||
|
if (!entry) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (id === entry.id) {
|
if (id === entry.id) {
|
||||||
scanned.add(id);
|
scanned.add(id);
|
||||||
for (const importedModule of entry.importedModules) {
|
for (const importedModule of entry.importedModules) {
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
import type { BuildResult } from 'esbuild';
|
|
||||||
import type * as vite from 'vite';
|
|
||||||
import type { SSRError } from '../../../@types/astro';
|
|
||||||
|
|
||||||
import eol from 'eol';
|
|
||||||
import fs from 'fs';
|
|
||||||
import { codeFrame } from '../../util.js';
|
|
||||||
|
|
||||||
interface ErrorHandlerOptions {
|
|
||||||
filePath: URL;
|
|
||||||
viteServer: vite.ViteDevServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function errorHandler(e: unknown, { viteServer, filePath }: ErrorHandlerOptions) {
|
|
||||||
// normalize error stack line-endings to \n
|
|
||||||
if ((e as any).stack) {
|
|
||||||
(e as any).stack = eol.lf((e as any).stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
// fix stack trace with Vite (this searches its module graph for matches)
|
|
||||||
if (e instanceof Error) {
|
|
||||||
viteServer.ssrFixStacktrace(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Astro error (thrown by esbuild so it needs to be formatted for Vite)
|
|
||||||
if (Array.isArray((e as any).errors)) {
|
|
||||||
const { location, pluginName, text } = (e as BuildResult).errors[0];
|
|
||||||
const err = e as SSRError;
|
|
||||||
if (location) err.loc = { file: location.file, line: location.line, column: location.column };
|
|
||||||
let src = err.pluginCode;
|
|
||||||
if (!src && err.id && fs.existsSync(err.id)) src = await fs.promises.readFile(err.id, 'utf8');
|
|
||||||
if (!src) src = await fs.promises.readFile(filePath, 'utf8');
|
|
||||||
err.frame = codeFrame(src, err.loc);
|
|
||||||
err.id = location?.file;
|
|
||||||
err.message = `${location?.file}: ${text}
|
|
||||||
${err.frame}
|
|
||||||
`;
|
|
||||||
if (pluginName) err.plugin = pluginName;
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic error (probably from Vite, and already formatted)
|
|
||||||
throw e;
|
|
||||||
}
|
|
|
@ -7,7 +7,6 @@ import { prependForwardSlash } from '../../../core/path.js';
|
||||||
import { RouteCache } from '../route-cache.js';
|
import { RouteCache } from '../route-cache.js';
|
||||||
import { createModuleScriptElementWithSrcSet } from '../ssr-element.js';
|
import { createModuleScriptElementWithSrcSet } from '../ssr-element.js';
|
||||||
import { getStylesForURL } from './css.js';
|
import { getStylesForURL } from './css.js';
|
||||||
import { errorHandler } from './error.js';
|
|
||||||
import { getHmrScript } from './hmr.js';
|
import { getHmrScript } from './hmr.js';
|
||||||
import { injectTags } from './html.js';
|
import { injectTags } from './html.js';
|
||||||
export interface SSROptions {
|
export interface SSROptions {
|
||||||
|
@ -29,10 +28,8 @@ export interface SSROptions {
|
||||||
routeCache: RouteCache;
|
routeCache: RouteCache;
|
||||||
/** Vite instance */
|
/** Vite instance */
|
||||||
viteServer: vite.ViteDevServer;
|
viteServer: vite.ViteDevServer;
|
||||||
/** Method */
|
/** Request */
|
||||||
method: string;
|
request: Request;
|
||||||
/** Headers */
|
|
||||||
headers: Headers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ComponentPreload = [SSRLoadedRenderer[], ComponentInstance];
|
export type ComponentPreload = [SSRLoadedRenderer[], ComponentInstance];
|
||||||
|
@ -65,7 +62,7 @@ export async function preload({ astroConfig, filePath, viteServer }: Pick<SSROpt
|
||||||
|
|
||||||
/** use Vite to SSR */
|
/** use Vite to SSR */
|
||||||
export async function render(renderers: SSRLoadedRenderer[], mod: ComponentInstance, ssrOpts: SSROptions): Promise<RenderResponse> {
|
export async function render(renderers: SSRLoadedRenderer[], mod: ComponentInstance, ssrOpts: SSROptions): Promise<RenderResponse> {
|
||||||
const { astroConfig, filePath, logging, mode, origin, pathname, method, headers, route, routeCache, viteServer } = ssrOpts;
|
const { astroConfig, filePath, logging, mode, origin, pathname, request, route, routeCache, viteServer } = ssrOpts;
|
||||||
const legacy = astroConfig.buildOptions.legacyBuild;
|
const legacy = astroConfig.buildOptions.legacyBuild;
|
||||||
|
|
||||||
// Add hoisted script tags
|
// Add hoisted script tags
|
||||||
|
@ -145,12 +142,11 @@ export async function render(renderers: SSRLoadedRenderer[], mod: ComponentInsta
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
renderers,
|
renderers,
|
||||||
|
request,
|
||||||
route,
|
route,
|
||||||
routeCache,
|
routeCache,
|
||||||
site: astroConfig.buildOptions.site,
|
site: astroConfig.buildOptions.site,
|
||||||
ssr: astroConfig.buildOptions.experimentalSsr,
|
ssr: astroConfig.buildOptions.experimentalSsr,
|
||||||
method,
|
|
||||||
headers,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (route?.type === 'endpoint' || content.type === 'response') {
|
if (route?.type === 'endpoint' || content.type === 'response') {
|
||||||
|
@ -216,11 +212,6 @@ export async function render(renderers: SSRLoadedRenderer[], mod: ComponentInsta
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function ssr(preloadedComponent: ComponentPreload, ssrOpts: SSROptions): Promise<RenderResponse> {
|
export async function ssr(preloadedComponent: ComponentPreload, ssrOpts: SSROptions): Promise<RenderResponse> {
|
||||||
try {
|
const [renderers, mod] = preloadedComponent;
|
||||||
const [renderers, mod] = preloadedComponent;
|
return await render(renderers, mod, ssrOpts); // NOTE: without "await", errors won’t get caught below
|
||||||
return await render(renderers, mod, ssrOpts); // note(drew): without "await", errors won’t get caught by errorHandler()
|
|
||||||
} catch (e: unknown) {
|
|
||||||
await errorHandler(e, { viteServer: ssrOpts.viteServer, filePath: ssrOpts.filePath });
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
import type { Params } from '../../@types/astro';
|
|
||||||
import { canonicalURL as utilCanonicalURL } from '../util.js';
|
|
||||||
|
|
||||||
type Site = string | undefined;
|
|
||||||
|
|
||||||
export interface AstroRequest {
|
|
||||||
/** get the current page URL */
|
|
||||||
url: URL;
|
|
||||||
|
|
||||||
/** get the current canonical URL */
|
|
||||||
canonicalURL: URL;
|
|
||||||
|
|
||||||
/** get page params (dynamic pages only) */
|
|
||||||
params: Params;
|
|
||||||
|
|
||||||
headers: Headers;
|
|
||||||
|
|
||||||
method: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AstroRequestSSR = AstroRequest;
|
|
||||||
|
|
||||||
export function createRequest(method: string, pathname: string, headers: Headers, origin: string, site: Site, ssr: boolean): AstroRequest {
|
|
||||||
const url = new URL('.' + pathname, new URL(origin));
|
|
||||||
|
|
||||||
const canonicalURL = utilCanonicalURL('.' + pathname, site ?? url.origin);
|
|
||||||
|
|
||||||
const request: AstroRequest = {
|
|
||||||
url,
|
|
||||||
canonicalURL,
|
|
||||||
params: {},
|
|
||||||
headers,
|
|
||||||
method,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!ssr) {
|
|
||||||
// Headers are only readable if using SSR-mode. If not, make it an empty headers
|
|
||||||
// object, so you can't do something bad.
|
|
||||||
request.headers = new Headers();
|
|
||||||
|
|
||||||
// Disallow using query params.
|
|
||||||
request.url = new URL(request.url);
|
|
||||||
|
|
||||||
for (const [key] of request.url.searchParams) {
|
|
||||||
request.url.searchParams.delete(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return request;
|
|
||||||
}
|
|
|
@ -3,7 +3,7 @@ import type { AstroGlobal, AstroGlobalPartial, MarkdownParser, MarkdownRenderOpt
|
||||||
import { renderSlot } from '../../runtime/server/index.js';
|
import { renderSlot } from '../../runtime/server/index.js';
|
||||||
import { LogOptions, warn } from '../logger.js';
|
import { LogOptions, warn } from '../logger.js';
|
||||||
import { isCSSRequest } from './dev/css.js';
|
import { isCSSRequest } from './dev/css.js';
|
||||||
import { createRequest } from './request.js';
|
import { canonicalURL as utilCanonicalURL } from '../util.js';
|
||||||
import { isScriptRequest } from './script.js';
|
import { isScriptRequest } from './script.js';
|
||||||
|
|
||||||
function onlyAvailableInSSR(name: string) {
|
function onlyAvailableInSSR(name: string) {
|
||||||
|
@ -26,8 +26,7 @@ export interface CreateResultArgs {
|
||||||
site: string | undefined;
|
site: string | undefined;
|
||||||
links?: Set<SSRElement>;
|
links?: Set<SSRElement>;
|
||||||
scripts?: Set<SSRElement>;
|
scripts?: Set<SSRElement>;
|
||||||
headers: Headers;
|
request: Request;
|
||||||
method: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Slots {
|
class Slots {
|
||||||
|
@ -72,10 +71,10 @@ class Slots {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createResult(args: CreateResultArgs): SSRResult {
|
export function createResult(args: CreateResultArgs): SSRResult {
|
||||||
const { legacyBuild, markdownRender, method, origin, headers, params, pathname, renderers, resolve, site } = args;
|
const { legacyBuild, markdownRender, origin, params, pathname, renderers, request, resolve, site } = args;
|
||||||
|
|
||||||
const request = createRequest(method, pathname, headers, origin, site, args.ssr);
|
const url = new URL(request.url);
|
||||||
request.params = params;
|
const canonicalURL = utilCanonicalURL('.' + pathname, site ?? url.origin);
|
||||||
|
|
||||||
// Create the result object that will be passed into the render function.
|
// Create the result object that will be passed into the render function.
|
||||||
// This object starts here as an empty shell (not yet the result) but then
|
// This object starts here as an empty shell (not yet the result) but then
|
||||||
|
@ -90,6 +89,8 @@ export function createResult(args: CreateResultArgs): SSRResult {
|
||||||
|
|
||||||
const Astro = {
|
const Astro = {
|
||||||
__proto__: astroGlobal,
|
__proto__: astroGlobal,
|
||||||
|
canonicalURL,
|
||||||
|
params,
|
||||||
props,
|
props,
|
||||||
request,
|
request,
|
||||||
redirect: args.ssr
|
redirect: args.ssr
|
||||||
|
|
41
packages/astro/src/core/request.ts
Normal file
41
packages/astro/src/core/request.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import type { IncomingHttpHeaders } from 'http';
|
||||||
|
import type { LogOptions } from './logger';
|
||||||
|
import { warn } from './logger.js';
|
||||||
|
|
||||||
|
type HeaderType = Headers | Record<string, any> | IncomingHttpHeaders;
|
||||||
|
type RequestBody = ArrayBuffer | Blob | ReadableStream | URLSearchParams | FormData;
|
||||||
|
|
||||||
|
export interface CreateRequestOptions {
|
||||||
|
url: URL | string;
|
||||||
|
headers: HeaderType;
|
||||||
|
method?: string;
|
||||||
|
body?: RequestBody | undefined;
|
||||||
|
logging: LogOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRequest({ url, headers, method = 'GET', body = undefined, logging }: CreateRequestOptions): Request {
|
||||||
|
let headersObj = headers instanceof Headers ? headers : new Headers(Object.entries(headers as Record<string, any>));
|
||||||
|
|
||||||
|
const request = new Request(url.toString(), {
|
||||||
|
method: method,
|
||||||
|
headers: headersObj,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperties(request, {
|
||||||
|
canonicalURL: {
|
||||||
|
get() {
|
||||||
|
warn(logging, 'deprecation', `Astro.request.canonicalURL has been moved to Astro.canonicalURL`);
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
get() {
|
||||||
|
warn(logging, 'deprecation', `Astro.request.params has been moved to Astro.params`);
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
|
@ -1,11 +1,12 @@
|
||||||
import type { AstroConfig } from '../@types/astro';
|
|
||||||
import type { ErrorPayload } from 'vite';
|
|
||||||
import eol from 'eol';
|
import eol from 'eol';
|
||||||
import path from 'path';
|
|
||||||
import slash from 'slash';
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { fileURLToPath, pathToFileURL } from 'url';
|
import path from 'path';
|
||||||
import resolve from 'resolve';
|
import resolve from 'resolve';
|
||||||
|
import slash from 'slash';
|
||||||
|
import { fileURLToPath, pathToFileURL } from 'url';
|
||||||
|
import type { ErrorPayload } from 'vite';
|
||||||
|
import type { AstroConfig } from '../@types/astro';
|
||||||
|
import { removeEndingForwardSlash } from './path.js';
|
||||||
|
|
||||||
/** Normalize URL to its canonical form */
|
/** Normalize URL to its canonical form */
|
||||||
export function canonicalURL(url: string, base?: string): URL {
|
export function canonicalURL(url: string, base?: string): URL {
|
||||||
|
@ -35,6 +36,28 @@ export function arraify<T>(target: T | T[]): T[] {
|
||||||
return Array.isArray(target) ? target : [target];
|
return Array.isArray(target) ? target : [target];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function padMultilineString(source: string, n = 2) {
|
||||||
|
const lines = source.split(/\r?\n/);
|
||||||
|
return lines.map((l) => ` `.repeat(n) + l).join(`\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const STATUS_CODE_REGEXP = /^\/?[0-9]{3}$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the correct output filename for a route, based on your config.
|
||||||
|
* Handles both "/foo" and "foo" `name` formats.
|
||||||
|
* Handles `/404` and `/` correctly.
|
||||||
|
*/
|
||||||
|
export function getOutputFilename(astroConfig: AstroConfig, name: string) {
|
||||||
|
if (name === '/' || name === '') {
|
||||||
|
return path.posix.join(name, 'index.html');
|
||||||
|
}
|
||||||
|
if (astroConfig.buildOptions.pageUrlFormat === 'directory' && !STATUS_CODE_REGEXP.test(name)) {
|
||||||
|
return path.posix.join(name, 'index.html');
|
||||||
|
}
|
||||||
|
return `${removeEndingForwardSlash(name || 'index')}.html`;
|
||||||
|
}
|
||||||
|
|
||||||
/** is a specifier an npm package? */
|
/** is a specifier an npm package? */
|
||||||
export function parseNpmName(spec: string): { scope?: string; name: string; subpath?: string } | undefined {
|
export function parseNpmName(spec: string): { scope?: string; name: string; subpath?: string } | undefined {
|
||||||
// not an npm package
|
// not an npm package
|
||||||
|
@ -137,6 +160,28 @@ export function isBuildingToSSR(config: AstroConfig): boolean {
|
||||||
return !!config._ctx.adapter?.serverEntrypoint;
|
return !!config._ctx.adapter?.serverEntrypoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function emoji(char: string, fallback: string) {
|
||||||
|
return process.platform !== 'win32' ? char : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove once --hostname is baselined
|
||||||
|
export function getResolvedHostForVite(config: AstroConfig) {
|
||||||
|
if (config.devOptions.host === false && config.devOptions.hostname !== 'localhost') {
|
||||||
|
return config.devOptions.hostname;
|
||||||
|
} else {
|
||||||
|
return config.devOptions.host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLocalAddress(serverAddress: string, config: AstroConfig): string {
|
||||||
|
const host = getResolvedHostForVite(config);
|
||||||
|
if (typeof host === 'boolean' || host === 'localhost') {
|
||||||
|
return 'localhost';
|
||||||
|
} else {
|
||||||
|
return serverAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Vendored from https://github.com/genmon/aboutfeeds/blob/main/tools/pretty-feed-v3.xsl
|
// Vendored from https://github.com/genmon/aboutfeeds/blob/main/tools/pretty-feed-v3.xsl
|
||||||
/** Basic stylesheet for RSS feeds */
|
/** Basic stylesheet for RSS feeds */
|
||||||
export const PRETTY_FEED_V3 = `<?xml version="1.0" encoding="utf-8"?>
|
export const PRETTY_FEED_V3 = `<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import shorthash from 'shorthash';
|
import shorthash from 'shorthash';
|
||||||
import type { AstroComponentMetadata, AstroGlobalPartial, EndpointHandler, Params, SSRElement, SSRLoadedRenderer, SSRResult } from '../../@types/astro';
|
import type { AstroComponentMetadata, AstroGlobalPartial, EndpointHandler, Params, SSRElement, SSRLoadedRenderer, SSRResult } from '../../@types/astro';
|
||||||
import type { AstroRequest } from '../../core/render/request';
|
|
||||||
import { escapeHTML, HTMLString, markHTMLString } from './escape.js';
|
import { escapeHTML, HTMLString, markHTMLString } from './escape.js';
|
||||||
import { extractDirectives, generateHydrateScript, serializeProps } from './hydration.js';
|
import { extractDirectives, generateHydrateScript, serializeProps } from './hydration.js';
|
||||||
import { serializeListValue } from './util.js';
|
import { serializeListValue } from './util.js';
|
||||||
|
@ -277,34 +276,25 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create the Astro.fetchContent() runtime function. */
|
/** Create the Astro.fetchContent() runtime function. */
|
||||||
function createFetchContentFn(url: URL, site: URL) {
|
function createDeprecatedFetchContentFn() {
|
||||||
let sitePathname = site.pathname;
|
return () => {
|
||||||
const fetchContent = (importMetaGlobResult: Record<string, any>) => {
|
throw new Error('Deprecated: Astro.fetchContent() has been replaced with Astro.glob().');
|
||||||
let allEntries = [...Object.entries(importMetaGlobResult)];
|
|
||||||
if (allEntries.length === 0) {
|
|
||||||
throw new Error(`[${url.pathname}] Astro.fetchContent() no matches found.`);
|
|
||||||
}
|
|
||||||
return allEntries
|
|
||||||
.map(([spec, mod]) => {
|
|
||||||
// Only return Markdown files for now.
|
|
||||||
if (!mod.frontmatter) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const urlSpec = new URL(spec, url).pathname;
|
|
||||||
return {
|
|
||||||
...mod.frontmatter,
|
|
||||||
Content: mod.default,
|
|
||||||
content: mod.metadata,
|
|
||||||
file: new URL(spec, url),
|
|
||||||
url: urlSpec.includes('/pages/') ? urlSpec.replace(/^.*\/pages\//, sitePathname).replace(/(\/index)?\.md$/, '') : undefined,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter(Boolean);
|
|
||||||
};
|
};
|
||||||
// This has to be cast because the type of fetchContent is the type of the function
|
}
|
||||||
// that receives the import.meta.glob result, but the user is using it as
|
|
||||||
// another type.
|
/** Create the Astro.glob() runtime function. */
|
||||||
return fetchContent as unknown as AstroGlobalPartial['fetchContent'];
|
function createAstroGlobFn() {
|
||||||
|
const globHandler = (importMetaGlobResult: Record<string, any>, globValue: () => any) => {
|
||||||
|
let allEntries = [...Object.values(importMetaGlobResult)];
|
||||||
|
if (allEntries.length === 0) {
|
||||||
|
throw new Error(`Astro.glob(${JSON.stringify(globValue())}) - no matches found.`);
|
||||||
|
}
|
||||||
|
// Map over the `import()` promises, calling to load them.
|
||||||
|
return Promise.all(allEntries.map((fn) => fn()));
|
||||||
|
};
|
||||||
|
// Cast the return type because the argument that the user sees (string) is different from the argument
|
||||||
|
// that the runtime sees post-compiler (Record<string, Module>).
|
||||||
|
return globHandler as unknown as AstroGlobalPartial['glob'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is used to create the top-level Astro global; the one that you can use
|
// This is used to create the top-level Astro global; the one that you can use
|
||||||
|
@ -313,10 +303,10 @@ export function createAstro(filePathname: string, _site: string, projectRootStr:
|
||||||
const site = new URL(_site);
|
const site = new URL(_site);
|
||||||
const url = new URL(filePathname, site);
|
const url = new URL(filePathname, site);
|
||||||
const projectRoot = new URL(projectRootStr);
|
const projectRoot = new URL(projectRootStr);
|
||||||
const fetchContent = createFetchContentFn(url, site);
|
|
||||||
return {
|
return {
|
||||||
site,
|
site,
|
||||||
fetchContent,
|
fetchContent: createDeprecatedFetchContentFn(),
|
||||||
|
glob: createAstroGlobFn(),
|
||||||
// INVESTIGATE is there a use-case for multi args?
|
// INVESTIGATE is there a use-case for multi args?
|
||||||
resolve(...segments: string[]) {
|
resolve(...segments: string[]) {
|
||||||
let resolved = segments.reduce((u, segment) => new URL(segment, u), url).pathname;
|
let resolved = segments.reduce((u, segment) => new URL(segment, u), url).pathname;
|
||||||
|
@ -397,7 +387,7 @@ export function defineScriptVars(vars: Record<any, any>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renders an endpoint request to completion, returning the body.
|
// Renders an endpoint request to completion, returning the body.
|
||||||
export async function renderEndpoint(mod: EndpointHandler, request: AstroRequest, params: Params) {
|
export async function renderEndpoint(mod: EndpointHandler, request: Request, params: Params) {
|
||||||
const chosenMethod = request.method?.toLowerCase() ?? 'get';
|
const chosenMethod = request.method?.toLowerCase() ?? 'get';
|
||||||
const handler = mod[chosenMethod];
|
const handler = mod[chosenMethod];
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import type * as t from '@babel/types';
|
import { parse as babelParser } from '@babel/parser';
|
||||||
|
import type { ArrowFunctionExpressionKind, CallExpressionKind, StringLiteralKind } from 'ast-types/gen/kinds';
|
||||||
|
import type { NodePath } from 'ast-types/lib/node-path';
|
||||||
|
import { parse, print, types, visit } from 'recast';
|
||||||
import type { Plugin } from 'vite';
|
import type { Plugin } from 'vite';
|
||||||
import type { AstroConfig } from '../@types/astro';
|
import type { AstroConfig } from '../@types/astro';
|
||||||
|
|
||||||
import * as babelTraverse from '@babel/traverse';
|
// Check for `Astro.glob()`. Be very forgiving of whitespace. False positives are okay.
|
||||||
import * as babel from '@babel/core';
|
const ASTRO_GLOB_REGEX = /Astro2?\s*\.\s*glob\s*\(/;
|
||||||
|
|
||||||
interface AstroPluginOptions {
|
interface AstroPluginOptions {
|
||||||
config: AstroConfig;
|
config: AstroConfig;
|
||||||
}
|
}
|
||||||
|
@ -21,55 +23,58 @@ export default function astro({ config }: AstroPluginOptions): Plugin {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimization: only run on a probably match
|
// Optimization: Detect usage with a quick string match.
|
||||||
// Open this up if need for post-pass extends past fetchContent
|
// Only perform the transform if this function is found
|
||||||
if (!code.includes('fetchContent')) {
|
if (!ASTRO_GLOB_REGEX.test(code)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the second-pass JS AST Traversal
|
const ast = parse(code, {
|
||||||
const result = await babel.transformAsync(code, {
|
// We need to use the babel parser because `import.meta.hot` is not
|
||||||
sourceType: 'module',
|
// supported by esprima (default parser). In the future, we should
|
||||||
sourceMaps: true,
|
// experiment with other parsers if Babel is too slow or heavy.
|
||||||
plugins: [
|
parser: { parse: babelParser },
|
||||||
() => {
|
|
||||||
return {
|
|
||||||
visitor: {
|
|
||||||
StringLiteral(path: babelTraverse.NodePath<t.StringLiteral>) {
|
|
||||||
if (
|
|
||||||
path.parent.type !== 'CallExpression' ||
|
|
||||||
path.parent.callee.type !== 'MemberExpression' ||
|
|
||||||
!validAstroGlobalNames.has((path.parent.callee.object as any).name) ||
|
|
||||||
(path.parent.callee.property as any).name !== 'fetchContent'
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { value } = path.node;
|
|
||||||
if (/[a-z]\:\/\//.test(value)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
path.replaceWith({
|
|
||||||
type: 'CallExpression',
|
|
||||||
callee: {
|
|
||||||
type: 'MemberExpression',
|
|
||||||
object: { type: 'MetaProperty', meta: { type: 'Identifier', name: 'import' }, property: { type: 'Identifier', name: 'meta' } },
|
|
||||||
property: { type: 'Identifier', name: 'globEager' },
|
|
||||||
computed: false,
|
|
||||||
},
|
|
||||||
arguments: [path.node],
|
|
||||||
} as any);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Undocumented baby behavior, but possible according to Babel types.
|
visit(ast, {
|
||||||
if (!result || !result.code) {
|
visitCallExpression: function (path) {
|
||||||
return null;
|
// Filter out anything that isn't `Astro.glob()` or `Astro2.glob()`
|
||||||
}
|
if (
|
||||||
|
!types.namedTypes.MemberExpression.check(path.node.callee) ||
|
||||||
|
!types.namedTypes.Identifier.check(path.node.callee.property) ||
|
||||||
|
!(path.node.callee.property.name === 'glob') ||
|
||||||
|
!types.namedTypes.Identifier.check(path.node.callee.object) ||
|
||||||
|
!(path.node.callee.object.name === 'Astro' || path.node.callee.object.name === 'Astro2')
|
||||||
|
) {
|
||||||
|
this.traverse(path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the `Astro.glob()` argument with `import.meta.glob`.
|
||||||
|
const argsPath = path.get('arguments', 0) as NodePath;
|
||||||
|
const args = argsPath.value;
|
||||||
|
argsPath.replace(
|
||||||
|
{
|
||||||
|
type: 'CallExpression',
|
||||||
|
callee: {
|
||||||
|
type: 'MemberExpression',
|
||||||
|
object: { type: 'MetaProperty', meta: { type: 'Identifier', name: 'import' }, property: { type: 'Identifier', name: 'meta' } },
|
||||||
|
property: { type: 'Identifier', name: 'glob' },
|
||||||
|
computed: false,
|
||||||
|
},
|
||||||
|
arguments: [args],
|
||||||
|
} as CallExpressionKind,
|
||||||
|
{
|
||||||
|
type: 'ArrowFunctionExpression',
|
||||||
|
body: args,
|
||||||
|
params: [],
|
||||||
|
} as ArrowFunctionExpressionKind
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = print(ast);
|
||||||
return { code: result.code, map: result.map };
|
return { code: result.code, map: result.map };
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type * as vite from 'vite';
|
||||||
import type http from 'http';
|
import type http from 'http';
|
||||||
import type { AstroConfig, ManifestData } from '../@types/astro';
|
import type { AstroConfig, ManifestData } from '../@types/astro';
|
||||||
import type { RenderResponse, SSROptions } from '../core/render/dev/index';
|
import type { RenderResponse, SSROptions } from '../core/render/dev/index';
|
||||||
import { info, warn, error, LogOptions } from '../core/logger.js';
|
import { debug, info, warn, error, LogOptions } from '../core/logger.js';
|
||||||
import { getParamsAndProps, GetParamsAndPropsError } from '../core/render/core.js';
|
import { getParamsAndProps, GetParamsAndPropsError } from '../core/render/core.js';
|
||||||
import { createRouteManifest, matchRoute } from '../core/routing/index.js';
|
import { createRouteManifest, matchRoute } from '../core/routing/index.js';
|
||||||
import stripAnsi from 'strip-ansi';
|
import stripAnsi from 'strip-ansi';
|
||||||
|
@ -10,11 +10,11 @@ import { createSafeError } from '../core/util.js';
|
||||||
import { ssr, preload } from '../core/render/dev/index.js';
|
import { ssr, preload } from '../core/render/dev/index.js';
|
||||||
import { call as callEndpoint } from '../core/endpoint/dev/index.js';
|
import { call as callEndpoint } from '../core/endpoint/dev/index.js';
|
||||||
import * as msg from '../core/messages.js';
|
import * as msg from '../core/messages.js';
|
||||||
|
|
||||||
import notFoundTemplate, { subpathNotUsedTemplate } from '../template/4xx.js';
|
import notFoundTemplate, { subpathNotUsedTemplate } from '../template/4xx.js';
|
||||||
import serverErrorTemplate from '../template/5xx.js';
|
import serverErrorTemplate from '../template/5xx.js';
|
||||||
import { RouteCache } from '../core/render/route-cache.js';
|
import { RouteCache } from '../core/render/route-cache.js';
|
||||||
import { AstroRequest } from '../core/render/request.js';
|
import { fixViteErrorMessage } from '../core/errors.js';
|
||||||
|
import { createRequest } from '../core/request.js';
|
||||||
|
|
||||||
interface AstroPluginOptions {
|
interface AstroPluginOptions {
|
||||||
config: AstroConfig;
|
config: AstroConfig;
|
||||||
|
@ -117,9 +117,36 @@ async function handleRequest(
|
||||||
const site = config.buildOptions.site ? new URL(config.buildOptions.site) : undefined;
|
const site = config.buildOptions.site ? new URL(config.buildOptions.site) : undefined;
|
||||||
const devRoot = site ? site.pathname : '/';
|
const devRoot = site ? site.pathname : '/';
|
||||||
const origin = `${viteServer.config.server.https ? 'https' : 'http'}://${req.headers.host}`;
|
const origin = `${viteServer.config.server.https ? 'https' : 'http'}://${req.headers.host}`;
|
||||||
|
const buildingToSSR = !!config._ctx.adapter?.serverEntrypoint;
|
||||||
const url = new URL(origin + req.url);
|
const url = new URL(origin + req.url);
|
||||||
const pathname = decodeURI(url.pathname);
|
const pathname = decodeURI(url.pathname);
|
||||||
const rootRelativeUrl = pathname.substring(devRoot.length - 1);
|
const rootRelativeUrl = pathname.substring(devRoot.length - 1);
|
||||||
|
if (!buildingToSSR) {
|
||||||
|
// Prevent user from depending on search params when not doing SSR.
|
||||||
|
for (const [key] of url.searchParams) {
|
||||||
|
url.searchParams.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let body: ArrayBuffer | undefined = undefined;
|
||||||
|
if (!(req.method === 'GET' || req.method === 'HEAD')) {
|
||||||
|
let bytes: string[] = [];
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
req.setEncoding('utf-8');
|
||||||
|
req.on('data', (bts) => bytes.push(bts));
|
||||||
|
req.on('close', resolve);
|
||||||
|
});
|
||||||
|
body = new TextEncoder().encode(bytes.join('')).buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers are only available when using SSR.
|
||||||
|
const request = createRequest({
|
||||||
|
url,
|
||||||
|
headers: buildingToSSR ? req.headers : new Headers(),
|
||||||
|
method: req.method,
|
||||||
|
body,
|
||||||
|
logging,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!pathname.startsWith(devRoot)) {
|
if (!pathname.startsWith(devRoot)) {
|
||||||
|
@ -166,10 +193,9 @@ async function handleRequest(
|
||||||
filePath: filePathCustom404,
|
filePath: filePathCustom404,
|
||||||
logging,
|
logging,
|
||||||
mode: 'development',
|
mode: 'development',
|
||||||
method: 'GET',
|
|
||||||
headers: new Headers(Object.entries(req.headers as Record<string, any>)),
|
|
||||||
origin,
|
origin,
|
||||||
pathname: rootRelativeUrl,
|
pathname: rootRelativeUrl,
|
||||||
|
request,
|
||||||
route: routeCustom404,
|
route: routeCustom404,
|
||||||
routeCache,
|
routeCache,
|
||||||
viteServer,
|
viteServer,
|
||||||
|
@ -190,8 +216,7 @@ async function handleRequest(
|
||||||
route,
|
route,
|
||||||
routeCache,
|
routeCache,
|
||||||
viteServer,
|
viteServer,
|
||||||
method: req.method || 'GET',
|
request,
|
||||||
headers: new Headers(Object.entries(req.headers as Record<string, any>)),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Route successfully matched! Render it.
|
// Route successfully matched! Render it.
|
||||||
|
@ -207,11 +232,10 @@ async function handleRequest(
|
||||||
const result = await ssr(preloadedComponent, options);
|
const result = await ssr(preloadedComponent, options);
|
||||||
return await writeSSRResult(result, res, statusCode);
|
return await writeSSRResult(result, res, statusCode);
|
||||||
}
|
}
|
||||||
} catch (_err: any) {
|
} catch (_err) {
|
||||||
debugger;
|
debugger;
|
||||||
info(logging, 'serve', msg.req({ url: pathname, statusCode: 500 }));
|
const err = fixViteErrorMessage(createSafeError(_err), viteServer);
|
||||||
const err = createSafeError(_err);
|
error(logging, null, msg.formatErrorMessage(err));
|
||||||
error(logging, 'error', msg.err(err));
|
|
||||||
handle500Response(viteServer, origin, req, res, err);
|
handle500Response(viteServer, origin, req, res, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
import type { AstroConfig } from '../@types/astro';
|
import { createElement, createScript, getAttribute, hasAttribute, insertBefore, remove, setAttribute } from '@web/parse5-utils';
|
||||||
import type { LogOptions } from '../core/logger.js';
|
|
||||||
import type { ViteDevServer, Plugin as VitePlugin } from 'vite';
|
|
||||||
import type { OutputChunk, PreRenderedChunk, PluginContext } from 'rollup';
|
|
||||||
import type { AllPagesData } from '../core/build/types';
|
|
||||||
import type { BuildInternals } from '../core/build/internal';
|
|
||||||
import parse5 from 'parse5';
|
|
||||||
import srcsetParse from 'srcset-parse';
|
|
||||||
import * as npath from 'path';
|
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { getAttribute, hasAttribute, insertBefore, remove, createScript, createElement, setAttribute } from '@web/parse5-utils';
|
import parse5 from 'parse5';
|
||||||
import { addRollupInput } from './add-rollup-input.js';
|
import * as npath from 'path';
|
||||||
import { findAssets, findExternalScripts, findInlineScripts, findInlineStyles, getTextContent, getAttributes } from './extract-assets.js';
|
import type { OutputChunk, PluginContext, PreRenderedChunk } from 'rollup';
|
||||||
import { isBuildableImage, isBuildableLink, isHoistedScript, isInSrcDirectory, hasSrcSet } from './util.js';
|
import srcsetParse from 'srcset-parse';
|
||||||
|
import type { Plugin as VitePlugin, ViteDevServer } from 'vite';
|
||||||
|
import type { AstroConfig } from '../@types/astro';
|
||||||
|
import type { BuildInternals } from '../core/build/internal';
|
||||||
|
import type { AllPagesData } from '../core/build/types';
|
||||||
|
import type { LogOptions } from '../core/logger.js';
|
||||||
|
import { prependDotSlash } from '../core/path.js';
|
||||||
import { render as ssrRender } from '../core/render/dev/index.js';
|
import { render as ssrRender } from '../core/render/dev/index.js';
|
||||||
import { getAstroStyleId, getAstroPageStyleId } from '../vite-plugin-build-css/index.js';
|
|
||||||
import { prependDotSlash, removeEndingForwardSlash } from '../core/path.js';
|
|
||||||
import { RouteCache } from '../core/render/route-cache.js';
|
import { RouteCache } from '../core/render/route-cache.js';
|
||||||
|
import { getOutputFilename } from '../core/util.js';
|
||||||
|
import { getAstroPageStyleId, getAstroStyleId } from '../vite-plugin-build-css/index.js';
|
||||||
|
import { addRollupInput } from './add-rollup-input.js';
|
||||||
|
import { findAssets, findExternalScripts, findInlineScripts, findInlineStyles, getAttributes, getTextContent } from './extract-assets.js';
|
||||||
|
import { hasSrcSet, isBuildableImage, isBuildableLink, isHoistedScript, isInSrcDirectory } from './util.js';
|
||||||
|
import { createRequest } from '../core/request.js';
|
||||||
|
|
||||||
// This package isn't real ESM, so have to coerce it
|
// This package isn't real ESM, so have to coerce it
|
||||||
const matchSrcset: typeof srcsetParse = (srcsetParse as any).default;
|
const matchSrcset: typeof srcsetParse = (srcsetParse as any).default;
|
||||||
|
@ -25,7 +27,6 @@ const ASTRO_PAGE_PREFIX = '@astro-page';
|
||||||
const ASTRO_SCRIPT_PREFIX = '@astro-script';
|
const ASTRO_SCRIPT_PREFIX = '@astro-script';
|
||||||
|
|
||||||
const ASTRO_EMPTY = '@astro-empty';
|
const ASTRO_EMPTY = '@astro-empty';
|
||||||
const STATUS_CODE_REGEXP = /^[0-9]{3}$/;
|
|
||||||
|
|
||||||
interface PluginOptions {
|
interface PluginOptions {
|
||||||
astroConfig: AstroConfig;
|
astroConfig: AstroConfig;
|
||||||
|
@ -87,8 +88,11 @@ export function rollupPluginAstroScanHTML(options: PluginOptions): VitePlugin {
|
||||||
astroConfig,
|
astroConfig,
|
||||||
filePath: new URL(`./${component}`, astroConfig.projectRoot),
|
filePath: new URL(`./${component}`, astroConfig.projectRoot),
|
||||||
logging,
|
logging,
|
||||||
headers: new Headers(),
|
request: createRequest({
|
||||||
method: 'GET',
|
url: new URL(origin + pathname),
|
||||||
|
headers: new Headers(),
|
||||||
|
logging,
|
||||||
|
}),
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
origin,
|
origin,
|
||||||
pathname,
|
pathname,
|
||||||
|
@ -487,14 +491,7 @@ export function rollupPluginAstroScanHTML(options: PluginOptions): VitePlugin {
|
||||||
|
|
||||||
const outHTML = parse5.serialize(document);
|
const outHTML = parse5.serialize(document);
|
||||||
const name = pathname.substr(1);
|
const name = pathname.substr(1);
|
||||||
let outPath: string;
|
const outPath = getOutputFilename(astroConfig, name);
|
||||||
|
|
||||||
// Output directly to 404.html rather than 404/index.html
|
|
||||||
if (astroConfig.buildOptions.pageUrlFormat === 'file' || STATUS_CODE_REGEXP.test(name)) {
|
|
||||||
outPath = `${removeEndingForwardSlash(name || 'index')}.html`;
|
|
||||||
} else {
|
|
||||||
outPath = npath.posix.join(name, 'index.html');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emitFile({
|
this.emitFile({
|
||||||
fileName: outPath,
|
fileName: outPath,
|
||||||
|
|
|
@ -2,14 +2,20 @@ import { transform } from '@astrojs/compiler';
|
||||||
import ancestor from 'common-ancestor-path';
|
import ancestor from 'common-ancestor-path';
|
||||||
import esbuild from 'esbuild';
|
import esbuild from 'esbuild';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import matter from 'gray-matter';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
import type { Plugin } from 'vite';
|
import type { Plugin } from 'vite';
|
||||||
import type { AstroConfig } from '../@types/astro';
|
import type { AstroConfig } from '../@types/astro';
|
||||||
import { PAGE_SSR_SCRIPT_ID } from '../vite-plugin-scripts/index.js';
|
import { PAGE_SSR_SCRIPT_ID } from '../vite-plugin-scripts/index.js';
|
||||||
|
import { virtualModuleId as pagesVirtualModuleId } from '../core/build/vite-plugin-pages.js';
|
||||||
|
|
||||||
interface AstroPluginOptions {
|
interface AstroPluginOptions {
|
||||||
config: AstroConfig;
|
config: AstroConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const VIRTUAL_MODULE_ID_PREFIX = 'astro:markdown';
|
||||||
|
const VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID_PREFIX;
|
||||||
|
|
||||||
// TODO: Clean up some of the shared logic between this Markdown plugin and the Astro plugin.
|
// TODO: Clean up some of the shared logic between this Markdown plugin and the Astro plugin.
|
||||||
// Both end up connecting a `load()` hook to the Astro compiler, and share some copy-paste
|
// Both end up connecting a `load()` hook to the Astro compiler, and share some copy-paste
|
||||||
// logic in how that is done.
|
// logic in how that is done.
|
||||||
|
@ -23,14 +29,88 @@ export default function markdown({ config }: AstroPluginOptions): Plugin {
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Weird Vite behavior: Vite seems to use a fake "index.html" importer when you
|
||||||
|
// have `enforce: pre`. This can probably be removed once the vite issue is fixed.
|
||||||
|
// see: https://github.com/vitejs/vite/issues/5981
|
||||||
|
const fakeRootImporter = fileURLToPath(new URL('index.html', config.projectRoot));
|
||||||
|
function isRootImport(importer: string | undefined) {
|
||||||
|
if (!importer) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (importer === fakeRootImporter) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (importer === '\0' + pagesVirtualModuleId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'astro:markdown',
|
name: 'astro:markdown',
|
||||||
enforce: 'pre', // run transforms before other plugins can
|
enforce: 'pre',
|
||||||
|
async resolveId(id, importer, options) {
|
||||||
|
// Resolve virtual modules as-is.
|
||||||
|
if (id.startsWith(VIRTUAL_MODULE_ID)) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
// Resolve any .md files with the `?content` cache buster. This should only come from
|
||||||
|
// an already-resolved JS module wrapper. Needed to prevent infinite loops in Vite.
|
||||||
|
// Unclear if this is expected or if cache busting is just working around a Vite bug.
|
||||||
|
if (id.endsWith('.md?content')) {
|
||||||
|
const resolvedId = await this.resolve(id, importer, { skipSelf: true, ...options });
|
||||||
|
return resolvedId?.id.replace('?content', '');
|
||||||
|
}
|
||||||
|
// If the markdown file is imported from another file via ESM, resolve a JS representation
|
||||||
|
// that defers the markdown -> HTML rendering until it is needed. This is especially useful
|
||||||
|
// when fetching and then filtering many markdown files, like with import.meta.glob() or Astro.glob().
|
||||||
|
// Otherwise, resolve directly to the actual component.
|
||||||
|
if (id.endsWith('.md') && !isRootImport(importer)) {
|
||||||
|
const resolvedId = await this.resolve(id, importer, { skipSelf: true, ...options });
|
||||||
|
if (resolvedId) {
|
||||||
|
return VIRTUAL_MODULE_ID + resolvedId.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// In all other cases, we do nothing and rely on normal Vite resolution.
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
async load(id) {
|
async load(id) {
|
||||||
|
// A markdown file has been imported via ESM!
|
||||||
|
// Return the file's JS representation, including all Markdown
|
||||||
|
// frontmatter and a deferred `import() of the compiled markdown content.
|
||||||
|
if (id.startsWith(VIRTUAL_MODULE_ID)) {
|
||||||
|
const sitePathname = config.buildOptions.site ? new URL(config.buildOptions.site).pathname : '/';
|
||||||
|
const fileId = id.substring(VIRTUAL_MODULE_ID.length);
|
||||||
|
const fileUrl = fileId.includes('/pages/') ? fileId.replace(/^.*\/pages\//, sitePathname).replace(/(\/index)?\.md$/, '') : undefined;
|
||||||
|
const source = await fs.promises.readFile(fileId, 'utf8');
|
||||||
|
const { data: frontmatter } = matter(source);
|
||||||
|
return {
|
||||||
|
code: `
|
||||||
|
// Static
|
||||||
|
export const frontmatter = ${JSON.stringify(frontmatter)};
|
||||||
|
export const file = ${JSON.stringify(fileId)};
|
||||||
|
export const url = ${JSON.stringify(fileUrl)};
|
||||||
|
|
||||||
|
// Deferred
|
||||||
|
export default async function load() {
|
||||||
|
return (await import(${JSON.stringify(fileId + '?content')}));
|
||||||
|
};
|
||||||
|
export function Content(...args) {
|
||||||
|
return load().then((m) => m.default(...args))
|
||||||
|
}
|
||||||
|
Content.isAstroComponentFactory = true;
|
||||||
|
export function getHeaders() {
|
||||||
|
return load().then((m) => m.metadata.headers)
|
||||||
|
};`,
|
||||||
|
map: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// A markdown file is being rendered! This markdown file was either imported
|
||||||
|
// directly as a page in Vite, or it was a deferred render from a JS module.
|
||||||
|
// This returns the compiled markdown -> astro component that renders to HTML.
|
||||||
if (id.endsWith('.md')) {
|
if (id.endsWith('.md')) {
|
||||||
const source = await fs.promises.readFile(id, 'utf8');
|
const source = await fs.promises.readFile(id, 'utf8');
|
||||||
|
|
||||||
// Transform from `.md` to valid `.astro`
|
|
||||||
let render = config.markdownOptions.render;
|
let render = config.markdownOptions.render;
|
||||||
let renderOpts = {};
|
let renderOpts = {};
|
||||||
if (Array.isArray(render)) {
|
if (Array.isArray(render)) {
|
||||||
|
@ -40,8 +120,6 @@ export default function markdown({ config }: AstroPluginOptions): Plugin {
|
||||||
if (typeof render === 'string') {
|
if (typeof render === 'string') {
|
||||||
({ default: render } = await import(render));
|
({ default: render } = await import(render));
|
||||||
}
|
}
|
||||||
let renderResult = await render(source, renderOpts);
|
|
||||||
let { frontmatter, metadata, code: astroResult } = renderResult;
|
|
||||||
|
|
||||||
const filename = normalizeFilename(id);
|
const filename = normalizeFilename(id);
|
||||||
const fileUrl = new URL(`file://${filename}`);
|
const fileUrl = new URL(`file://${filename}`);
|
||||||
|
@ -49,6 +127,9 @@ export default function markdown({ config }: AstroPluginOptions): Plugin {
|
||||||
const hasInjectedScript = isPage && config._ctx.scripts.some((s) => s.stage === 'page-ssr');
|
const hasInjectedScript = isPage && config._ctx.scripts.some((s) => s.stage === 'page-ssr');
|
||||||
|
|
||||||
// Extract special frontmatter keys
|
// Extract special frontmatter keys
|
||||||
|
const { data: frontmatter, content: markdownContent } = matter(source);
|
||||||
|
let renderResult = await render(markdownContent, renderOpts);
|
||||||
|
let { code: astroResult, metadata } = renderResult;
|
||||||
const { layout = '', components = '', setup = '', ...content } = frontmatter;
|
const { layout = '', components = '', setup = '', ...content } = frontmatter;
|
||||||
content.astro = metadata;
|
content.astro = metadata;
|
||||||
const prelude = `---
|
const prelude = `---
|
||||||
|
@ -83,8 +164,7 @@ export const frontmatter = ${JSON.stringify(content)};
|
||||||
${tsResult}`;
|
${tsResult}`;
|
||||||
|
|
||||||
// Compile from `.ts` to `.js`
|
// Compile from `.ts` to `.js`
|
||||||
const { code, map } = await esbuild.transform(tsResult, { loader: 'ts', sourcemap: 'inline', sourcefile: id });
|
const { code } = await esbuild.transform(tsResult, { loader: 'ts', sourcemap: false, sourcefile: id });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code,
|
code,
|
||||||
map: null,
|
map: null,
|
||||||
|
|
|
@ -25,7 +25,7 @@ describe('Astro.*', () => {
|
||||||
expect($('#nested-child-pathname').text()).to.equal('/');
|
expect($('#nested-child-pathname').text()).to.equal('/');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Astro.request.canonicalURL', async () => {
|
it('Astro.canonicalURL', async () => {
|
||||||
// given a URL, expect the following canonical URL
|
// given a URL, expect the following canonical URL
|
||||||
const canonicalURLs = {
|
const canonicalURLs = {
|
||||||
'/index.html': 'https://mysite.dev/blog/',
|
'/index.html': 'https://mysite.dev/blog/',
|
||||||
|
@ -48,7 +48,7 @@ describe('Astro.*', () => {
|
||||||
expect($('#site').attr('href')).to.equal('https://mysite.dev/blog/');
|
expect($('#site').attr('href')).to.equal('https://mysite.dev/blog/');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Astro.fetchContent() returns the correct "url" property, including buildOptions.site subpath', async () => {
|
it('Astro.glob() correctly returns an array of all posts', async () => {
|
||||||
const html = await fixture.readFile('/posts/1/index.html');
|
const html = await fixture.readFile('/posts/1/index.html');
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
expect($('.post-url').attr('href')).to.equal('/blog/post/post-2');
|
expect($('.post-url').attr('href')).to.equal('/blog/post/post-2');
|
||||||
|
|
|
@ -28,7 +28,7 @@ describe('astro cli', () => {
|
||||||
it('astro build', async () => {
|
it('astro build', async () => {
|
||||||
const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url);
|
const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url);
|
||||||
const proc = await cli('build', '--project-root', fileURLToPath(projectRootURL));
|
const proc = await cli('build', '--project-root', fileURLToPath(projectRootURL));
|
||||||
expect(proc.stdout).to.include('Done');
|
expect(proc.stdout).to.include('Complete');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('astro dev welcome', async () => {
|
it('astro dev welcome', async () => {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import stripAnsi from 'strip-ansi';
|
import stripAnsi from 'strip-ansi';
|
||||||
import { formatConfigError, validateConfig } from '../dist/core/config.js';
|
import { formatConfigErrorMessage } from '../dist/core/messages.js';
|
||||||
|
import { validateConfig } from '../dist/core/config.js';
|
||||||
|
|
||||||
describe('Config Validation', () => {
|
describe('Config Validation', () => {
|
||||||
it('empty user config is valid', async () => {
|
it('empty user config is valid', async () => {
|
||||||
|
@ -22,7 +23,7 @@ describe('Config Validation', () => {
|
||||||
it('A validation error can be formatted correctly', async () => {
|
it('A validation error can be formatted correctly', async () => {
|
||||||
const configError = await validateConfig({ buildOptions: { sitemap: 42 } }, process.cwd()).catch((err) => err);
|
const configError = await validateConfig({ buildOptions: { sitemap: 42 } }, process.cwd()).catch((err) => err);
|
||||||
expect(configError instanceof z.ZodError).to.equal(true);
|
expect(configError instanceof z.ZodError).to.equal(true);
|
||||||
const formattedError = stripAnsi(formatConfigError(configError));
|
const formattedError = stripAnsi(formatConfigErrorMessage(configError));
|
||||||
expect(formattedError).to.equal(
|
expect(formattedError).to.equal(
|
||||||
`[config] Astro found issue(s) with your configuration:
|
`[config] Astro found issue(s) with your configuration:
|
||||||
! buildOptions.sitemap Expected boolean, received number.`
|
! buildOptions.sitemap Expected boolean, received number.`
|
||||||
|
@ -37,7 +38,7 @@ describe('Config Validation', () => {
|
||||||
};
|
};
|
||||||
const configError = await validateConfig(veryBadConfig, process.cwd()).catch((err) => err);
|
const configError = await validateConfig(veryBadConfig, process.cwd()).catch((err) => err);
|
||||||
expect(configError instanceof z.ZodError).to.equal(true);
|
expect(configError instanceof z.ZodError).to.equal(true);
|
||||||
const formattedError = stripAnsi(formatConfigError(configError));
|
const formattedError = stripAnsi(formatConfigErrorMessage(configError));
|
||||||
expect(formattedError).to.equal(
|
expect(formattedError).to.equal(
|
||||||
`[config] Astro found issue(s) with your configuration:
|
`[config] Astro found issue(s) with your configuration:
|
||||||
! pages Expected string, received object.
|
! pages Expected string, received object.
|
||||||
|
|
|
@ -10,7 +10,7 @@ export function getStaticPaths({ paginate }) {
|
||||||
{params: {calledTwiceTest: 'c'}},
|
{params: {calledTwiceTest: 'c'}},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
const { params } = Astro.request;
|
const { params } = Astro;
|
||||||
---
|
---
|
||||||
|
|
||||||
<html>
|
<html>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue