1
- import { existsSync , readFileSync } from 'node:fs' ;
1
+ import { cpSync , existsSync , mkdirSync , readFileSync } from 'node:fs' ;
2
2
import { basename } from 'node:path' ;
3
3
import { pathToFileURL } from 'node:url' ;
4
4
import { emptyDir , removeDir , writeJson } from '@astrojs/internal-helpers/fs' ;
@@ -7,6 +7,7 @@ import type {
7
7
AstroConfig ,
8
8
AstroIntegration ,
9
9
AstroIntegrationLogger ,
10
+ HookParameters ,
10
11
IntegrationRouteData ,
11
12
} from 'astro' ;
12
13
import glob from 'fast-glob' ;
@@ -188,13 +189,14 @@ export default function vercelAdapter({
188
189
// Secret used to verify that the caller is the astro-generated edge middleware and not a third-party
189
190
const middlewareSecret = crypto . randomUUID ( ) ;
190
191
191
- let buildOutput : 'server' | 'static' ;
192
+ let _buildOutput : 'server' | 'static' ;
193
+
194
+ let staticDir : URL | undefined ;
192
195
193
196
return {
194
197
name : PACKAGE_NAME ,
195
198
hooks : {
196
199
'astro:config:setup' : async ( { command, config, updateConfig, injectScript, logger } ) => {
197
- buildOutput = config . output ;
198
200
if ( webAnalytics ?. enabled ) {
199
201
injectScript (
200
202
'head-inline' ,
@@ -204,7 +206,30 @@ export default function vercelAdapter({
204
206
) ;
205
207
}
206
208
207
- if ( buildOutput === 'server' ) {
209
+ staticDir = new URL ( './.vercel/output/static' , config . root ) ;
210
+ updateConfig ( {
211
+ build : {
212
+ format : 'directory' ,
213
+ redirects : false ,
214
+ } ,
215
+ vite : {
216
+ ssr : {
217
+ external : [ '@vercel/nft' ] ,
218
+ } ,
219
+ } ,
220
+ ...getAstroImageConfig (
221
+ imageService ,
222
+ imagesConfig ,
223
+ command ,
224
+ devImageService ,
225
+ config . image
226
+ ) ,
227
+ } ) ;
228
+ } ,
229
+ 'astro:config:done' : ( { setAdapter, config, logger, buildOutput } ) => {
230
+ _buildOutput = buildOutput ;
231
+
232
+ if ( _buildOutput === 'server' ) {
208
233
if ( maxDuration && maxDuration > 900 ) {
209
234
logger . warn (
210
235
`maxDuration is set to ${ maxDuration } seconds, which is longer than the maximum allowed duration of 900 seconds.`
@@ -214,7 +239,6 @@ export default function vercelAdapter({
214
239
`Please make sure that your plan allows for this duration. See https://vercel.com/docs/functions/serverless-functions/runtimes#maxduration for more information.`
215
240
) ;
216
241
}
217
-
218
242
const vercelConfigPath = new URL ( 'vercel.json' , config . root ) ;
219
243
if ( existsSync ( vercelConfigPath ) ) {
220
244
try {
@@ -225,65 +249,21 @@ export default function vercelAdapter({
225
249
`\tYour "vercel.json" \`trailingSlash\` configuration (set to \`true\`) will conflict with your Astro \`trailinglSlash\` configuration (set to \`"always"\`).\n` +
226
250
// biome-ignore lint/style/noUnusedTemplateLiteral: <explanation>
227
251
`\tThis would cause infinite redirects under certain conditions and throw an \`ERR_TOO_MANY_REDIRECTS\` error.\n` +
228
- `\tTo prevent this, your Astro configuration is updated to \`"ignore"\` during builds .\n`
252
+ `\tTo prevent this, change your Astro configuration and update \`"trailingSlash"\` to \`"ignore"\`.\n`
229
253
) ;
230
- updateConfig ( {
231
- trailingSlash : 'ignore' ,
232
- } ) ;
233
254
}
234
255
} catch ( _err ) {
235
256
logger . warn ( `Your "vercel.json" config is not a valid json file.` ) ;
236
257
}
237
258
}
238
-
239
- updateConfig ( {
240
- outDir : new URL ( './.vercel/output/' , config . root ) ,
241
- build : {
242
- client : new URL ( './.vercel/output/static/' , config . root ) ,
243
- server : new URL ( './.vercel/output/_functions/' , config . root ) ,
244
- redirects : false ,
245
- } ,
246
- vite : {
247
- ssr : {
248
- external : [ '@vercel/nft' ] ,
249
- } ,
250
- } ,
251
- ...getAstroImageConfig (
252
- imageService ,
253
- imagesConfig ,
254
- command ,
255
- devImageService ,
256
- config . image
257
- ) ,
258
- } ) ;
259
- } else {
260
- const outDir = new URL ( './.vercel/output/static/' , config . root ) ;
261
- updateConfig ( {
262
- outDir,
263
- build : {
264
- format : 'directory' ,
265
- redirects : false ,
266
- } ,
267
- ...getAstroImageConfig (
268
- imageService ,
269
- imagesConfig ,
270
- command ,
271
- devImageService ,
272
- config . image
273
- ) ,
274
- } ) ;
275
- }
276
- } ,
277
- 'astro:config:done' : ( { setAdapter, config } ) => {
278
- if ( buildOutput === 'server' ) {
279
- setAdapter ( getAdapter ( { buildOutput, edgeMiddleware, middlewareSecret, skewProtection } ) ) ;
259
+ setAdapter ( getAdapter ( { buildOutput : _buildOutput , edgeMiddleware, middlewareSecret, skewProtection } ) ) ;
280
260
} else {
281
261
setAdapter (
282
262
getAdapter ( {
283
263
edgeMiddleware : false ,
284
264
middlewareSecret : '' ,
285
265
skewProtection,
286
- buildOutput,
266
+ buildOutput : _buildOutput ,
287
267
} )
288
268
) ;
289
269
}
@@ -292,27 +272,44 @@ export default function vercelAdapter({
292
272
_serverEntry = config . build . serverEntry ;
293
273
} ,
294
274
'astro:build:start' : async ( ) => {
295
- if ( buildOutput !== 'static' ) {
296
- // Ensure to have `.vercel/output` empty.
297
- // This is because, when building to static, outDir = .vercel/output/static/,
298
- // so .vercel/output itself won't get cleaned.
299
- await emptyDir ( new URL ( './.vercel/output/' , _config . root ) ) ;
300
- }
275
+ // Ensure to have `.vercel/output` empty.
276
+ await emptyDir ( new URL ( './.vercel/output/' , _config . root ) ) ;
301
277
} ,
302
278
'astro:build:ssr' : async ( { entryPoints, middlewareEntryPoint } ) => {
303
279
_entryPoints = new Map (
304
280
Array . from ( entryPoints ) . filter ( ( [ routeData ] ) => ! routeData . prerender )
305
281
) ;
306
282
_middlewareEntryPoint = middlewareEntryPoint ;
307
283
} ,
308
- 'astro:build:done' : async ( { routes, logger } ) => {
284
+ 'astro:build:done' : async ( { routes, logger } : HookParameters < 'astro:build:done' > ) => {
285
+ const outDir = new URL ( './.vercel/output/' , _config . root ) ;
286
+ if ( staticDir ) {
287
+ if ( existsSync ( staticDir ) ) {
288
+ emptyDir ( staticDir ) ;
289
+ }
290
+ mkdirSync ( new URL ( './.vercel/output/static/' , _config . root ) , { recursive : true } ) ;
291
+
292
+ if ( _buildOutput === 'static' && staticDir ) {
293
+ cpSync ( _config . outDir , new URL ( './.vercel/output/static/' , _config . root ) , {
294
+ recursive : true ,
295
+ } ) ;
296
+ } else {
297
+ cpSync ( _config . build . client , new URL ( './.vercel/output/static/' , _config . root ) , {
298
+ recursive : true ,
299
+ } ) ;
300
+ cpSync ( _config . build . server , new URL ( './.vercel/output/_functions/' , _config . root ) , {
301
+ recursive : true ,
302
+ } ) ;
303
+ }
304
+ }
305
+
309
306
const routeDefinitions : Array < {
310
307
src : string ;
311
308
dest : string ;
312
309
middlewarePath ?: string ;
313
310
} > = [ ] ;
314
311
315
- if ( buildOutput === 'server' ) {
312
+ if ( _buildOutput === 'server' ) {
316
313
// Merge any includes from `vite.assetsInclude
317
314
if ( _config . vite . assetsInclude ) {
318
315
const mergeGlobbedIncludes = ( globPattern : unknown ) => {
@@ -339,6 +336,7 @@ export default function vercelAdapter({
339
336
excludeFiles ,
340
337
includeFiles ,
341
338
logger ,
339
+ outDir ,
342
340
maxDuration
343
341
) ;
344
342
@@ -399,10 +397,7 @@ export default function vercelAdapter({
399
397
}
400
398
}
401
399
const fourOhFourRoute = routes . find ( ( route ) => route . pathname === '/404' ) ;
402
- const destination =
403
- buildOutput === 'server'
404
- ? new URL ( './config.json' , _config . outDir )
405
- : new URL ( './.vercel/output/config.json' , _config . root ) ;
400
+ const destination = new URL ( './.vercel/output/config.json' , _config . root ) ;
406
401
const finalRoutes = [
407
402
...getRedirects ( routes , _config ) ,
408
403
{
@@ -412,12 +407,12 @@ export default function vercelAdapter({
412
407
} ,
413
408
{ handle : 'filesystem' } ,
414
409
] ;
415
- if ( buildOutput === 'server' ) {
410
+ if ( _buildOutput === 'server' ) {
416
411
finalRoutes . push ( ...routeDefinitions ) ;
417
412
}
418
413
419
414
if ( fourOhFourRoute ) {
420
- if ( buildOutput === 'server' ) {
415
+ if ( _buildOutput === 'server' ) {
421
416
finalRoutes . push ( {
422
417
src : '/.*' ,
423
418
dest : fourOhFourRoute . prerender
@@ -464,7 +459,7 @@ export default function vercelAdapter({
464
459
} ) ;
465
460
466
461
// Remove temporary folder
467
- if ( buildOutput === 'server' ) {
462
+ if ( _buildOutput === 'server' ) {
468
463
await removeDir ( _buildTempFolder ) ;
469
464
}
470
465
} ,
@@ -495,16 +490,17 @@ class VercelBuilder {
495
490
readonly excludeFiles : URL [ ] ,
496
491
readonly includeFiles : URL [ ] ,
497
492
readonly logger : AstroIntegrationLogger ,
493
+ readonly outDir : URL ,
498
494
readonly maxDuration ?: number ,
499
495
readonly runtime = getRuntime ( process , logger )
500
496
) { }
501
497
502
498
async buildServerlessFolder ( entry : URL , functionName : string , root : URL ) {
503
499
const { config, includeFiles, excludeFiles, logger, NTF_CACHE , runtime, maxDuration } = this ;
504
500
// .vercel/output/functions/<name>.func/
505
- const functionFolder = new URL ( `./functions/${ functionName } .func/` , config . outDir ) ;
506
- const packageJson = new URL ( `./functions/${ functionName } .func/package.json` , config . outDir ) ;
507
- const vcConfig = new URL ( `./functions/${ functionName } .func/.vc-config.json` , config . outDir ) ;
501
+ const functionFolder = new URL ( `./functions/${ functionName } .func/` , this . outDir ) ;
502
+ const packageJson = new URL ( `./functions/${ functionName } .func/package.json` , this . outDir ) ;
503
+ const vcConfig = new URL ( `./functions/${ functionName } .func/.vc-config.json` , this . outDir ) ;
508
504
509
505
// Copy necessary files (e.g. node_modules/)
510
506
const { handler } = await copyDependenciesToFunction (
@@ -538,7 +534,7 @@ class VercelBuilder {
538
534
await this . buildServerlessFolder ( entry , functionName , root ) ;
539
535
const prerenderConfig = new URL (
540
536
`./functions/${ functionName } .prerender-config.json` ,
541
- this . config . outDir
537
+ this . outDir
542
538
) ;
543
539
// https://vercel.com/docs/build-output-api/v3/primitives#prerender-configuration-file
544
540
await writeJson ( prerenderConfig , {
@@ -550,7 +546,7 @@ class VercelBuilder {
550
546
}
551
547
552
548
async buildMiddlewareFolder ( entry : URL , functionName : string , middlewareSecret : string ) {
553
- const functionFolder = new URL ( `./functions/${ functionName } .func/` , this . config . outDir ) ;
549
+ const functionFolder = new URL ( `./functions/${ functionName } .func/` , this . outDir ) ;
554
550
555
551
await generateEdgeMiddleware (
556
552
entry ,
0 commit comments