Skip to content

Commit

Permalink
fix(vercel): now works with monorepos (#5033)
Browse files Browse the repository at this point in the history
* Upgraded nft

* Handle monorepo better

* Changeset

* Fixed common ancestor

* Fixed outdir
  • Loading branch information
JuanM04 authored Oct 10, 2022
1 parent 2d9d422 commit c1f9914
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 248 deletions.
6 changes: 6 additions & 0 deletions .changeset/famous-dancers-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@astrojs/vercel': patch
---

- Upgraded @vercel/nft to 0.22.1
- Fix monorepos (#5020)
2 changes: 1 addition & 1 deletion packages/integrations/vercel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
},
"dependencies": {
"@astrojs/webapi": "^1.1.0",
"@vercel/nft": "^0.18.2"
"@vercel/nft": "^0.22.1"
},
"devDependencies": {
"astro": "workspace:*",
Expand Down
6 changes: 5 additions & 1 deletion packages/integrations/vercel/src/lib/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}

Expand Down
84 changes: 68 additions & 16 deletions packages/integrations/vercel/src/lib/nft.ts
Original file line number Diff line number Diff line change
@@ -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),
};
}
16 changes: 12 additions & 4 deletions packages/integrations/vercel/src/serverless/adapter.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -16,6 +16,7 @@ function getAdapter(): AstroAdapter {

export default function vercelEdge(): AstroIntegration {
let _config: AstroConfig;
let buildTempFolder: URL;
let functionFolder: URL;
let serverEntry: string;

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

Expand Down
Loading

0 comments on commit c1f9914

Please sign in to comment.