Skip to content
This repository has been archived by the owner on Feb 10, 2025. It is now read-only.

Commit

Permalink
Merge branch 'main' into sessions
Browse files Browse the repository at this point in the history
  • Loading branch information
ascorbic committed Feb 5, 2025
2 parents 093a380 + 991eea7 commit ad6ded0
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 103 deletions.
5 changes: 0 additions & 5 deletions .changeset/grumpy-phones-explode.md

This file was deleted.

8 changes: 8 additions & 0 deletions packages/vercel/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @astrojs/vercel

## 8.0.5

### Patch Changes

- [#519](https://github.com/withastro/adapters/pull/519) [`641d7d5`](https://github.com/withastro/adapters/commit/641d7d588d2d77f519201e583f0275db4260575c) Thanks [@ascorbic](https://github.com/ascorbic)! - Updates edge middleware to support esnext syntax

- [#525](https://github.com/withastro/adapters/pull/525) [`6ef9a6f`](https://github.com/withastro/adapters/commit/6ef9a6f958b089ac2890f94a015c978e0b254bda) Thanks [@ascorbic](https://github.com/ascorbic)! - Fixes a bug that caused redirect loops when trailingSlash was set

## 8.0.4

### Patch Changes
Expand Down
3 changes: 2 additions & 1 deletion packages/vercel/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@astrojs/vercel",
"description": "Deploy your site to Vercel",
"version": "8.0.4",
"version": "8.0.5",
"type": "module",
"author": "withastro",
"license": "MIT",
Expand Down Expand Up @@ -50,6 +50,7 @@
"@vercel/analytics": "^1.4.1",
"@vercel/edge": "^1.2.1",
"@vercel/nft": "^0.29.0",
"@vercel/routing-utils": "^5.0.0",
"esbuild": "^0.24.0",
"fast-glob": "^3.3.3"
},
Expand Down
65 changes: 53 additions & 12 deletions packages/vercel/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { cpSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
import { basename } from 'node:path';
import { pathToFileURL } from 'node:url';
import { emptyDir, removeDir, writeJson } from '@astrojs/internal-helpers/fs';
import { type Route, getTransformedRoutes, normalizeRoutes } from '@vercel/routing-utils';
import type {
AstroAdapter,
AstroConfig,
Expand All @@ -10,6 +11,7 @@ import type {
HookParameters,
IntegrationResolvedRoute,
} from 'astro';
import { AstroError } from 'astro/errors';
import glob from 'fast-glob';
import {
type DevImageService,
Expand Down Expand Up @@ -261,16 +263,23 @@ export default function vercelAdapter({
);
}
const vercelConfigPath = new URL('vercel.json', config.root);
if (existsSync(vercelConfigPath)) {
if (
config.trailingSlash &&
config.trailingSlash !== 'ignore' &&
existsSync(vercelConfigPath)
) {
try {
const vercelConfig = JSON.parse(readFileSync(vercelConfigPath, 'utf-8'));
if (vercelConfig.trailingSlash === true && config.trailingSlash === 'always') {
logger.warn(
'\n' +
`\tYour "vercel.json" \`trailingSlash\` configuration (set to \`true\`) will conflict with your Astro \`trailinglSlash\` configuration (set to \`"always"\`).\n` +
// biome-ignore lint/style/noUnusedTemplateLiteral: <explanation>
`\tThis would cause infinite redirects under certain conditions and throw an \`ERR_TOO_MANY_REDIRECTS\` error.\n` +
`\tTo prevent this, change your Astro configuration and update \`"trailingSlash"\` to \`"ignore"\`.\n`
if (
(vercelConfig.trailingSlash === true && config.trailingSlash === 'never') ||
(vercelConfig.trailingSlash === false && config.trailingSlash === 'always')
) {
logger.error(
`
Your "vercel.json" \`trailingSlash\` configuration (set to \`${vercelConfig.trailingSlash}\`) will conflict with your Astro \`trailingSlash\` configuration (set to \`${JSON.stringify(config.trailingSlash)}\`).
This would cause infinite redirects or duplicate content issues.
Please remove the \`trailingSlash\` configuration from your \`vercel.json\` file or Astro config.
`
);
}
} catch (_err) {
Expand Down Expand Up @@ -435,14 +444,12 @@ export default function vercelAdapter({
}
const fourOhFourRoute = routes.find((route) => route.pathname === '/404');
const destination = new URL('./.vercel/output/config.json', _config.root);
const finalRoutes = [
...getRedirects(routes, _config),
const finalRoutes: Route[] = [
{
src: `^/${_config.build.assets}/(.*)$`,
headers: { 'cache-control': 'public, max-age=31536000, immutable' },
continue: true,
},
{ handle: 'filesystem' },
];
if (_buildOutput === 'server') {
finalRoutes.push(...routeDefinitions);
Expand All @@ -467,6 +474,30 @@ export default function vercelAdapter({
});
}
}
// The Vercel `trailingSlash` option
let trailingSlash: boolean | undefined;
// Vercel's `trailingSlash` option maps to Astro's like so:
// - `true` -> `"always"`
// - `false` -> `"never"`
// - `undefined` -> `"ignore"`
// If config is set to "ignore", we leave it as undefined.
if (_config.trailingSlash && _config.trailingSlash !== 'ignore') {
// Otherwise, map it accordingly.
trailingSlash = _config.trailingSlash === 'always';
}

const { routes: redirects = [], error } = getTransformedRoutes({
trailingSlash,
rewrites: [],
redirects: getRedirects(routes, _config),
headers: [],
});
if (error) {
throw new AstroError(
`Error generating redirects: ${error.message}`,
error.link ? `${error.action ?? 'More info'}: ${error.link}` : undefined
);
}

let images: VercelImageConfig | undefined;
if (imageService || imagesConfig) {
Expand All @@ -487,11 +518,21 @@ export default function vercelAdapter({
}
}

const normalized = normalizeRoutes([...(redirects ?? []), ...finalRoutes]);
if (normalized.error) {
throw new AstroError(
`Error generating routes: ${normalized.error.message}`,
normalized.error.link
? `${normalized.error.action ?? 'More info'}: ${normalized.error.link}`
: undefined
);
}

// Output configuration
// https://vercel.com/docs/build-output-api/v3#build-output-configuration
await writeJson(destination, {
version: 3,
routes: finalRoutes,
routes: normalized.routes,
images,
});

Expand Down
93 changes: 38 additions & 55 deletions packages/vercel/src/lib/redirects.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import nodePath from 'node:path';
import { appendForwardSlash, removeLeadingForwardSlash } from '@astrojs/internal-helpers/path';
import { removeLeadingForwardSlash } from '@astrojs/internal-helpers/path';
import type { AstroConfig, IntegrationResolvedRoute, RoutePart } from 'astro';

import type { Redirect } from '@vercel/routing-utils';

const pathJoin = nodePath.posix.join;

// https://vercel.com/docs/project-configuration#legacy/routes
Expand Down Expand Up @@ -40,10 +42,32 @@ function getParts(part: string, file: string) {

return result;
}
/**
* Convert Astro routes into Vercel path-to-regexp syntax, which are the input for getTransformedRoutes
*/
function getMatchPattern(segments: RoutePart[][]) {
return segments
.map((segment) => {
return segment
.map((part) => {
if (part.spread) {
// Extract parameter name from spread syntax (e.g., "...slug" -> "slug")
const paramName = part.content.startsWith('...') ? part.content.slice(3) : part.content;
return `:${paramName}*`;
}
if (part.dynamic) {
return `:${part.content}`;
}
return part.content;
})
.join('');
})
.join('/');
}

// Copied from /home/juanm04/dev/misc/astro/packages/astro/src/core/routing/manifest/create.ts
// 2022-04-26
function getMatchPattern(segments: RoutePart[][]) {
function getMatchRegex(segments: RoutePart[][]) {
return segments
.map((segment, segmentIndex) => {
return segment.length === 1 && segment[0].spread
Expand Down Expand Up @@ -72,37 +96,16 @@ function getMatchPattern(segments: RoutePart[][]) {
.join('');
}

function getReplacePattern(segments: RoutePart[][]) {
let n = 0;
let result = '';

for (const segment of segments) {
for (const part of segment) {
// biome-ignore lint/style/useTemplate: <explanation>
if (part.dynamic) result += '$' + ++n;
else result += part.content;
}
result += '/';
}

// Remove trailing slash
result = result.slice(0, -1);

return result;
}

function getRedirectLocation(route: IntegrationResolvedRoute, config: AstroConfig): string {
if (route.redirectRoute) {
const pattern = getReplacePattern(route.redirectRoute.segments);
const path = config.trailingSlash === 'always' ? appendForwardSlash(pattern) : pattern;
return pathJoin(config.base, path);
// biome-ignore lint/style/noUselessElse: <explanation>
} else if (typeof route.redirect === 'object') {
const pattern = getMatchPattern(route.redirectRoute.segments);
return pathJoin(config.base, pattern);
}

if (typeof route.redirect === 'object') {
return pathJoin(config.base, route.redirect.destination);
// biome-ignore lint/style/noUselessElse: <explanation>
} else {
return pathJoin(config.base, route.redirect || '');
}
return pathJoin(config.base, route.redirect || '');
}

function getRedirectStatus(route: IntegrationResolvedRoute): number {
Expand All @@ -119,40 +122,20 @@ export function escapeRegex(content: string) {
.map((s: string) => {
return getParts(s, content);
});
return `^/${getMatchPattern(segments)}$`;
return `^/${getMatchRegex(segments)}$`;
}

export function getRedirects(
routes: IntegrationResolvedRoute[],
config: AstroConfig
): VercelRoute[] {
const redirects: VercelRoute[] = [];
export function getRedirects(routes: IntegrationResolvedRoute[], config: AstroConfig): Redirect[] {
const redirects: Redirect[] = [];

for (const route of routes) {
if (route.type === 'redirect') {
redirects.push({
src: config.base + getMatchPattern(route.segments),
headers: { Location: getRedirectLocation(route, config) },
status: getRedirectStatus(route),
source: config.base + getMatchPattern(route.segments),
destination: getRedirectLocation(route, config),
statusCode: getRedirectStatus(route),
});
} else if (route.type === 'page' && route.pattern !== '/') {
if (config.trailingSlash === 'always') {
redirects.push({
src: config.base + getMatchPattern(route.segments),
// biome-ignore lint/style/useTemplate: <explanation>
headers: { Location: config.base + getReplacePattern(route.segments) + '/' },
status: 308,
});
} else if (config.trailingSlash === 'never') {
redirects.push({
// biome-ignore lint/style/useTemplate: <explanation>
src: config.base + getMatchPattern(route.segments) + '/',
headers: { Location: config.base + getReplacePattern(route.segments) },
status: 308,
});
}
}
}

return redirects;
}
14 changes: 7 additions & 7 deletions packages/vercel/test/isr.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,31 +38,31 @@ describe('ISR', () => {
dest: '_render',
},
{
src: '^/excluded(?:\\/(.*?))?$',
src: '^/excluded(?:/(.*?))?$',
dest: '_render',
},
{
src: '^\\/_server-islands\\/([^/]+?)\\/?$',
src: '^/_server-islands/([^/]+?)/?$',
dest: '_render',
},
{
src: '^\\/_image\\/?$',
src: '^/_image/?$',
dest: '_render',
},
{
src: '^\\/excluded\\/([^/]+?)\\/?$',
src: '^/excluded/([^/]+?)/?$',
dest: '/_isr?x_astro_path=$0',
},
{
src: '^\\/excluded(?:\\/(.*?))?\\/?$',
src: '^/excluded(?:/(.*?))?/?$',
dest: '/_isr?x_astro_path=$0',
},
{
src: '^\\/one\\/?$',
src: '^/one/?$',
dest: '/_isr?x_astro_path=$0',
},
{
src: '^\\/two\\/?$',
src: '^/two/?$',
dest: '/_isr?x_astro_path=$0',
},
]);
Expand Down
2 changes: 1 addition & 1 deletion packages/vercel/test/prerendered-error-pages.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('prerendered error pages routing', () => {
assert.deepEqual(
deploymentConfig.routes.find((r) => r.status === 404),
{
src: '/.*',
src: '^/.*$',
dest: '/404.html',
status: 404,
}
Expand Down
Loading

0 comments on commit ad6ded0

Please sign in to comment.