0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-03-10 23:01:26 -05:00

fix(vercel): now works with monorepos (#5033)

* Upgraded nft

* Handle monorepo better

* Changeset

* Fixed common ancestor

* Fixed outdir
This commit is contained in:
Juan Martín Seery 2022-10-10 12:37:03 -03:00 committed by GitHub
parent a7d58b4663
commit bf4ba4aee1
4 changed files with 86 additions and 22 deletions

View file

@ -45,7 +45,7 @@
},
"dependencies": {
"@astrojs/webapi": "^1.1.0",
"@vercel/nft": "^0.18.2"
"@vercel/nft": "^0.22.1"
},
"devDependencies": {
"astro": "workspace:*",

View file

@ -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 });
}

View file

@ -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');
const meta = await fs.stat(origin);
const isSymlink = (await fs.lstat(origin)).isSymbolicLink();
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 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),
};
}

View file

@ -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',
});