1
1
import nodePath from 'node:path' ;
2
- import { appendForwardSlash , removeLeadingForwardSlash } from '@astrojs/internal-helpers/path' ;
2
+ import { removeLeadingForwardSlash } from '@astrojs/internal-helpers/path' ;
3
3
import type { AstroConfig , IntegrationResolvedRoute , RoutePart } from 'astro' ;
4
4
5
+ import type { Redirect } from '@vercel/routing-utils' ;
6
+
5
7
const pathJoin = nodePath . posix . join ;
6
8
7
9
// https://vercel.com/docs/project-configuration#legacy/routes
@@ -40,10 +42,32 @@ function getParts(part: string, file: string) {
40
42
41
43
return result ;
42
44
}
45
+ /**
46
+ * Convert Astro routes into Vercel path-to-regexp syntax, which are the input for getTransformedRoutes
47
+ */
48
+ function getMatchPattern ( segments : RoutePart [ ] [ ] ) {
49
+ return segments
50
+ . map ( ( segment ) => {
51
+ return segment
52
+ . map ( ( part ) => {
53
+ if ( part . spread ) {
54
+ // Extract parameter name from spread syntax (e.g., "...slug" -> "slug")
55
+ const paramName = part . content . startsWith ( '...' ) ? part . content . slice ( 3 ) : part . content ;
56
+ return `:${ paramName } *` ;
57
+ }
58
+ if ( part . dynamic ) {
59
+ return `:${ part . content } ` ;
60
+ }
61
+ return part . content ;
62
+ } )
63
+ . join ( '' ) ;
64
+ } )
65
+ . join ( '/' ) ;
66
+ }
43
67
44
68
// Copied from /home/juanm04/dev/misc/astro/packages/astro/src/core/routing/manifest/create.ts
45
69
// 2022-04-26
46
- function getMatchPattern ( segments : RoutePart [ ] [ ] ) {
70
+ function getMatchRegex ( segments : RoutePart [ ] [ ] ) {
47
71
return segments
48
72
. map ( ( segment , segmentIndex ) => {
49
73
return segment . length === 1 && segment [ 0 ] . spread
@@ -72,37 +96,16 @@ function getMatchPattern(segments: RoutePart[][]) {
72
96
. join ( '' ) ;
73
97
}
74
98
75
- function getReplacePattern ( segments : RoutePart [ ] [ ] ) {
76
- let n = 0 ;
77
- let result = '' ;
78
-
79
- for ( const segment of segments ) {
80
- for ( const part of segment ) {
81
- // biome-ignore lint/style/useTemplate: <explanation>
82
- if ( part . dynamic ) result += '$' + ++ n ;
83
- else result += part . content ;
84
- }
85
- result += '/' ;
86
- }
87
-
88
- // Remove trailing slash
89
- result = result . slice ( 0 , - 1 ) ;
90
-
91
- return result ;
92
- }
93
-
94
99
function getRedirectLocation ( route : IntegrationResolvedRoute , config : AstroConfig ) : string {
95
100
if ( route . redirectRoute ) {
96
- const pattern = getReplacePattern ( route . redirectRoute . segments ) ;
97
- const path = config . trailingSlash === 'always' ? appendForwardSlash ( pattern ) : pattern ;
98
- return pathJoin ( config . base , path ) ;
99
- // biome-ignore lint/style/noUselessElse: <explanation>
100
- } else if ( typeof route . redirect === 'object' ) {
101
+ const pattern = getMatchPattern ( route . redirectRoute . segments ) ;
102
+ return pathJoin ( config . base , pattern ) ;
103
+ }
104
+
105
+ if ( typeof route . redirect === 'object' ) {
101
106
return pathJoin ( config . base , route . redirect . destination ) ;
102
- // biome-ignore lint/style/noUselessElse: <explanation>
103
- } else {
104
- return pathJoin ( config . base , route . redirect || '' ) ;
105
107
}
108
+ return pathJoin ( config . base , route . redirect || '' ) ;
106
109
}
107
110
108
111
function getRedirectStatus ( route : IntegrationResolvedRoute ) : number {
@@ -119,40 +122,20 @@ export function escapeRegex(content: string) {
119
122
. map ( ( s : string ) => {
120
123
return getParts ( s , content ) ;
121
124
} ) ;
122
- return `^/${ getMatchPattern ( segments ) } $` ;
125
+ return `^/${ getMatchRegex ( segments ) } $` ;
123
126
}
124
127
125
- export function getRedirects (
126
- routes : IntegrationResolvedRoute [ ] ,
127
- config : AstroConfig
128
- ) : VercelRoute [ ] {
129
- const redirects : VercelRoute [ ] = [ ] ;
128
+ export function getRedirects ( routes : IntegrationResolvedRoute [ ] , config : AstroConfig ) : Redirect [ ] {
129
+ const redirects : Redirect [ ] = [ ] ;
130
130
131
131
for ( const route of routes ) {
132
132
if ( route . type === 'redirect' ) {
133
133
redirects . push ( {
134
- src : config . base + getMatchPattern ( route . segments ) ,
135
- headers : { Location : getRedirectLocation ( route , config ) } ,
136
- status : getRedirectStatus ( route ) ,
134
+ source : config . base + getMatchPattern ( route . segments ) ,
135
+ destination : getRedirectLocation ( route , config ) ,
136
+ statusCode : getRedirectStatus ( route ) ,
137
137
} ) ;
138
- } else if ( route . type === 'page' && route . pattern !== '/' ) {
139
- if ( config . trailingSlash === 'always' ) {
140
- redirects . push ( {
141
- src : config . base + getMatchPattern ( route . segments ) ,
142
- // biome-ignore lint/style/useTemplate: <explanation>
143
- headers : { Location : config . base + getReplacePattern ( route . segments ) + '/' } ,
144
- status : 308 ,
145
- } ) ;
146
- } else if ( config . trailingSlash === 'never' ) {
147
- redirects . push ( {
148
- // biome-ignore lint/style/useTemplate: <explanation>
149
- src : config . base + getMatchPattern ( route . segments ) + '/' ,
150
- headers : { Location : config . base + getReplacePattern ( route . segments ) } ,
151
- status : 308 ,
152
- } ) ;
153
- }
154
138
}
155
139
}
156
-
157
140
return redirects ;
158
141
}
0 commit comments