diff options
Diffstat (limited to 'packages/integrations')
-rw-r--r-- | packages/integrations/vercel/package.json | 2 | ||||
-rw-r--r-- | packages/integrations/vercel/src/lib/fs.ts | 6 | ||||
-rw-r--r-- | packages/integrations/vercel/src/lib/nft.ts | 84 | ||||
-rw-r--r-- | packages/integrations/vercel/src/serverless/adapter.ts | 16 |
4 files changed, 86 insertions, 22 deletions
diff --git a/packages/integrations/vercel/package.json b/packages/integrations/vercel/package.json index 1d57806ba..37b16a388 100644 --- a/packages/integrations/vercel/package.json +++ b/packages/integrations/vercel/package.json @@ -45,7 +45,7 @@ }, "dependencies": { "@astrojs/webapi": "^1.1.0", - "@vercel/nft": "^0.18.2" + "@vercel/nft": "^0.22.1" }, "devDependencies": { "astro": "workspace:*", diff --git a/packages/integrations/vercel/src/lib/fs.ts b/packages/integrations/vercel/src/lib/fs.ts index d7a833cd0..64c4c69ba 100644 --- a/packages/integrations/vercel/src/lib/fs.ts +++ b/packages/integrations/vercel/src/lib/fs.ts @@ -5,8 +5,12 @@ export async function writeJson<T>(path: PathLike, data: T) { await fs.writeFile(path, JSON.stringify(data), { encoding: 'utf-8' }); } -export async function emptyDir(dir: PathLike): Promise<void> { +export async function removeDir(dir: PathLike) { await fs.rm(dir, { recursive: true, force: true, maxRetries: 3 }); +} + +export async function emptyDir(dir: PathLike): Promise<void> { + await removeDir(dir); await fs.mkdir(dir, { recursive: true }); } diff --git a/packages/integrations/vercel/src/lib/nft.ts b/packages/integrations/vercel/src/lib/nft.ts index b18cb73ef..ba3677583 100644 --- a/packages/integrations/vercel/src/lib/nft.ts +++ b/packages/integrations/vercel/src/lib/nft.ts @@ -1,38 +1,90 @@ import { nodeFileTrace } from '@vercel/nft'; import * as fs from 'node:fs/promises'; +import nodePath from 'node:path'; import { fileURLToPath } from 'node:url'; export async function copyDependenciesToFunction( - root: URL, - functionFolder: URL, - serverEntry: string -) { - const entryPath = fileURLToPath(new URL(`./${serverEntry}`, functionFolder)); + entry: URL, + outDir: URL +): Promise<{ handler: string }> { + const entryPath = fileURLToPath(entry); + + // Get root of folder of the system (like C:\ on Windows or / on Linux) + let base = entry; + while (fileURLToPath(base) !== fileURLToPath(new URL('../', base))) { + base = new URL('../', base); + } const result = await nodeFileTrace([entryPath], { - base: fileURLToPath(root), + base: fileURLToPath(base), }); - for (const file of result.fileList) { - if (file.startsWith('.vercel/')) continue; - const origin = new URL(file, root); - const dest = new URL(file, functionFolder); + if (result.fileList.size === 0) throw new Error('[@astrojs/vercel] No files found'); + + for (const error of result.warnings) { + if (error.message.startsWith('Failed to resolve dependency')) { + const [, module, file] = /Cannot find module '(.+?)' loaded from (.+)/.exec(error.message)!; + + // The import(astroRemark) sometimes fails to resolve, but it's not a problem + if (module === '@astrojs/') continue; + + if (entryPath === file) { + console.warn( + `[@astrojs/vercel] The module "${module}" couldn't be resolved. This may not be a problem, but it's worth checking.` + ); + } else { + console.warn( + `[@astrojs/vercel] The module "${module}" inside the file "${file}" couldn't be resolved. This may not be a problem, but it's worth checking.` + ); + } + } else { + throw error; + } + } - const meta = await fs.stat(origin); - const isSymlink = (await fs.lstat(origin)).isSymbolicLink(); + const fileList = [...result.fileList]; + + let commonAncestor = nodePath.dirname(fileList[0]); + for (const file of fileList.slice(1)) { + while (!file.startsWith(commonAncestor)) { + commonAncestor = nodePath.dirname(commonAncestor); + } + } + + for (const file of fileList) { + const origin = new URL(file, base); + const dest = new URL(nodePath.relative(commonAncestor, file), outDir); + + const realpath = await fs.realpath(origin); + const isSymlink = realpath !== fileURLToPath(origin); + const isDir = (await fs.stat(origin)).isDirectory(); // Create directories recursively - if (meta.isDirectory() && !isSymlink) { + if (isDir && !isSymlink) { await fs.mkdir(new URL('..', dest), { recursive: true }); } else { await fs.mkdir(new URL('.', dest), { recursive: true }); } if (isSymlink) { - const link = await fs.readlink(origin); - await fs.symlink(link, dest, meta.isDirectory() ? 'dir' : 'file'); - } else { + const realdest = fileURLToPath( + new URL( + nodePath.relative(nodePath.join(fileURLToPath(base), commonAncestor), realpath), + outDir + ) + ); + await fs.symlink( + nodePath.relative(fileURLToPath(new URL('.', dest)), realdest), + dest, + isDir ? 'dir' : 'file' + ); + } else if (!isDir) { await fs.copyFile(origin, dest); } } + + return { + // serverEntry location inside the outDir + handler: nodePath.relative(nodePath.join(fileURLToPath(base), commonAncestor), entryPath), + }; } diff --git a/packages/integrations/vercel/src/serverless/adapter.ts b/packages/integrations/vercel/src/serverless/adapter.ts index 970141cd0..f5ae4e8cb 100644 --- a/packages/integrations/vercel/src/serverless/adapter.ts +++ b/packages/integrations/vercel/src/serverless/adapter.ts @@ -1,6 +1,6 @@ import type { AstroAdapter, AstroConfig, AstroIntegration } from 'astro'; -import { getVercelOutput, writeJson } from '../lib/fs.js'; +import { getVercelOutput, removeDir, writeJson } from '../lib/fs.js'; import { copyDependenciesToFunction } from '../lib/nft.js'; import { getRedirects } from '../lib/redirects.js'; @@ -16,6 +16,7 @@ function getAdapter(): AstroAdapter { export default function vercelEdge(): AstroIntegration { let _config: AstroConfig; + let buildTempFolder: URL; let functionFolder: URL; let serverEntry: string; @@ -39,11 +40,18 @@ export default function vercelEdge(): AstroIntegration { 'astro:build:start': async ({ buildConfig }) => { buildConfig.serverEntry = serverEntry = 'entry.js'; buildConfig.client = new URL('./static/', _config.outDir); - buildConfig.server = functionFolder = new URL('./functions/render.func/', _config.outDir); + buildConfig.server = buildTempFolder = new URL('./dist/', _config.root); + functionFolder = new URL('./functions/render.func/', _config.outDir); }, 'astro:build:done': async ({ routes }) => { // Copy necessary files (e.g. node_modules/) - await copyDependenciesToFunction(_config.root, functionFolder, serverEntry); + const { handler } = await copyDependenciesToFunction( + new URL(serverEntry, buildTempFolder), + functionFolder + ); + + // Remove temporary folder + await removeDir(buildTempFolder); // Enable ESM // https://aws.amazon.com/blogs/compute/using-node-js-es-modules-and-top-level-await-in-aws-lambda/ @@ -55,7 +63,7 @@ export default function vercelEdge(): AstroIntegration { // https://vercel.com/docs/build-output-api/v3#vercel-primitives/serverless-functions/configuration await writeJson(new URL(`./.vc-config.json`, functionFolder), { runtime: getRuntime(), - handler: serverEntry, + handler, launcherType: 'Nodejs', }); |