summaryrefslogtreecommitdiff
path: root/packages/integrations/vercel/src
diff options
context:
space:
mode:
authorGravatar Alexander Niebuhr <alexander@nbhr.io> 2024-09-17 07:52:50 +0200
committerGravatar Alexander Niebuhr <alexander@nbhr.io> 2024-09-17 07:52:50 +0200
commit7fc8dae5c529088cf4b98f544a7a2f4a86d8c33b (patch)
tree43afe63c9a907ff417ea0ddcad0b542d3f4d53e9 /packages/integrations/vercel/src
parentbea4ad266c4f9ea44f311f4d433a697a91e9ed0d (diff)
parent0afc80bb7f6711b37189079d3d86f3651a64c672 (diff)
downloadastro-7fc8dae5c529088cf4b98f544a7a2f4a86d8c33b.tar.gz
astro-7fc8dae5c529088cf4b98f544a7a2f4a86d8c33b.tar.zst
astro-7fc8dae5c529088cf4b98f544a7a2f4a86d8c33b.zip
Merge branch 'main' into next
Diffstat (limited to 'packages/integrations/vercel/src')
-rw-r--r--packages/integrations/vercel/src/image/shared-dev-service.ts3
-rw-r--r--packages/integrations/vercel/src/lib/nft.ts16
-rw-r--r--packages/integrations/vercel/src/lib/redirects.ts29
-rw-r--r--packages/integrations/vercel/src/lib/searchRoot.ts101
-rw-r--r--packages/integrations/vercel/src/serverless/adapter.ts85
5 files changed, 166 insertions, 68 deletions
diff --git a/packages/integrations/vercel/src/image/shared-dev-service.ts b/packages/integrations/vercel/src/image/shared-dev-service.ts
index 5cae60bcf..4edf11d3a 100644
--- a/packages/integrations/vercel/src/image/shared-dev-service.ts
+++ b/packages/integrations/vercel/src/image/shared-dev-service.ts
@@ -28,9 +28,8 @@ export const baseDevService: Omit<LocalImageService, 'transform'> = {
const transform = {
// biome-ignore lint/style/noNonNullAssertion: <explanation>
src: params.get('href')!,
- // biome-ignore lint/style/useNumberNamespace: <explanation>
// biome-ignore lint/style/noNonNullAssertion: <explanation>
- width: params.has('w') ? parseInt(params.get('w')!) : undefined,
+ width: params.has('w') ? Number.parseInt(params.get('w')!) : undefined,
quality: params.get('q'),
};
diff --git a/packages/integrations/vercel/src/lib/nft.ts b/packages/integrations/vercel/src/lib/nft.ts
index 61dcd8f9e..baf069b07 100644
--- a/packages/integrations/vercel/src/lib/nft.ts
+++ b/packages/integrations/vercel/src/lib/nft.ts
@@ -1,7 +1,9 @@
import { relative as relativePath } from 'node:path';
-import { fileURLToPath } from 'node:url';
+import { fileURLToPath, pathToFileURL } from 'node:url';
import { copyFilesToFolder } from '@astrojs/internal-helpers/fs';
+import { appendForwardSlash } from '@astrojs/internal-helpers/path';
import type { AstroIntegrationLogger } from 'astro';
+import { searchForWorkspaceRoot } from './searchRoot.js';
export async function copyDependenciesToFunction(
{
@@ -10,12 +12,14 @@ export async function copyDependenciesToFunction(
includeFiles,
excludeFiles,
logger,
+ root,
}: {
entry: URL;
outDir: URL;
includeFiles: URL[];
excludeFiles: URL[];
logger: AstroIntegrationLogger;
+ root: URL;
},
// we want to pass the caching by reference, and not by value
cache: object
@@ -23,11 +27,8 @@ export async function copyDependenciesToFunction(
const entryPath = fileURLToPath(entry);
logger.info(`Bundling function ${relativePath(fileURLToPath(outDir), entryPath)}`);
- // 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);
- }
+ // Set the base to the workspace root
+ const base = pathToFileURL(appendForwardSlash(searchForWorkspaceRoot(fileURLToPath(root))));
// The Vite bundle includes an import to `@vercel/nft` for some reason,
// and that trips up `@vercel/nft` itself during the adapter build. Using a
@@ -36,9 +37,6 @@ export async function copyDependenciesToFunction(
const { nodeFileTrace } = await import('@vercel/nft');
const result = await nodeFileTrace([entryPath], {
base: fileURLToPath(base),
- // If you have a route of /dev this appears in source and NFT will try to
- // scan your local /dev :8
- ignore: ['/dev/**'],
cache,
});
diff --git a/packages/integrations/vercel/src/lib/redirects.ts b/packages/integrations/vercel/src/lib/redirects.ts
index 1d7d4eca5..b11a5b2cc 100644
--- a/packages/integrations/vercel/src/lib/redirects.ts
+++ b/packages/integrations/vercel/src/lib/redirects.ts
@@ -49,19 +49,19 @@ function getMatchPattern(segments: RoutePart[][]) {
return segment[0].spread
? '(?:\\/(.*?))?'
: segment
- .map((part) => {
- if (part)
- return part.dynamic
- ? '([^/]+?)'
- : part.content
- .normalize()
- .replace(/\?/g, '%3F')
- .replace(/#/g, '%23')
- .replace(/%5B/g, '[')
- .replace(/%5D/g, ']')
- .replace(/[*+?^${}()|[\]\\]/g, '\\$&');
- })
- .join('');
+ .map((part) => {
+ if (part)
+ return part.dynamic
+ ? '([^/]+?)'
+ : part.content
+ .normalize()
+ .replace(/\?/g, '%3F')
+ .replace(/#/g, '%23')
+ .replace(/%5B/g, '[')
+ .replace(/%5D/g, ']')
+ .replace(/[*+?^${}()|[\]\\]/g, '\\$&');
+ })
+ .join('');
})
.join('/');
}
@@ -117,8 +117,7 @@ export function escapeRegex(content: string) {
}
export function getRedirects(routes: IntegrationRouteData[], config: AstroConfig): VercelRoute[] {
- // biome-ignore lint/style/useConst: <explanation>
- let redirects: VercelRoute[] = [];
+ const redirects: VercelRoute[] = [];
for (const route of routes) {
if (route.type === 'redirect') {
diff --git a/packages/integrations/vercel/src/lib/searchRoot.ts b/packages/integrations/vercel/src/lib/searchRoot.ts
new file mode 100644
index 000000000..832f6e498
--- /dev/null
+++ b/packages/integrations/vercel/src/lib/searchRoot.ts
@@ -0,0 +1,101 @@
+// Taken from: https://github.com/vitejs/vite/blob/1a76300cd16827f0640924fdc21747ce140c35fb/packages/vite/src/node/server/searchRoot.ts
+// MIT license
+// See https://github.com/vitejs/vite/blob/1a76300cd16827f0640924fdc21747ce140c35fb/LICENSE
+import fs from 'node:fs';
+import { dirname, join } from 'node:path';
+
+// https://github.com/vitejs/vite/issues/2820#issuecomment-812495079
+const ROOT_FILES = [
+ // '.git',
+
+ // https://pnpm.io/workspaces/
+ 'pnpm-workspace.yaml',
+
+ // https://rushjs.io/pages/advanced/config_files/
+ // 'rush.json',
+
+ // https://nx.dev/latest/react/getting-started/nx-setup
+ // 'workspace.json',
+ // 'nx.json',
+
+ // https://github.com/lerna/lerna#lernajson
+ 'lerna.json',
+];
+
+export function tryStatSync(file: string): fs.Stats | undefined {
+ try {
+ // The "throwIfNoEntry" is a performance optimization for cases where the file does not exist
+ return fs.statSync(file, { throwIfNoEntry: false });
+ } catch {
+ // Ignore errors
+ }
+}
+
+export function isFileReadable(filename: string): boolean {
+ if (!tryStatSync(filename)) {
+ return false;
+ }
+
+ try {
+ // Check if current process has read permission to the file
+ fs.accessSync(filename, fs.constants.R_OK);
+
+ return true;
+ } catch {
+ return false;
+ }
+}
+
+// npm: https://docs.npmjs.com/cli/v7/using-npm/workspaces#installing-workspaces
+// yarn: https://classic.yarnpkg.com/en/docs/workspaces/#toc-how-to-use-it
+function hasWorkspacePackageJSON(root: string): boolean {
+ const path = join(root, 'package.json');
+ if (!isFileReadable(path)) {
+ return false;
+ }
+ try {
+ const content = JSON.parse(fs.readFileSync(path, 'utf-8')) || {};
+ return !!content.workspaces;
+ } catch {
+ return false;
+ }
+}
+
+function hasRootFile(root: string): boolean {
+ return ROOT_FILES.some((file) => fs.existsSync(join(root, file)));
+}
+
+function hasPackageJSON(root: string) {
+ const path = join(root, 'package.json');
+ return fs.existsSync(path);
+}
+
+/**
+ * Search up for the nearest `package.json`
+ */
+export function searchForPackageRoot(current: string, root = current): string {
+ if (hasPackageJSON(current)) return current;
+
+ const dir = dirname(current);
+ // reach the fs root
+ if (!dir || dir === current) return root;
+
+ return searchForPackageRoot(dir, root);
+}
+
+/**
+ * Search up for the nearest workspace root
+ */
+export function searchForWorkspaceRoot(
+ current: string,
+ root = searchForPackageRoot(current)
+): string {
+ if (hasRootFile(current)) return current;
+ if (hasWorkspacePackageJSON(current)) return current;
+
+ const dir = dirname(current);
+ // reach the fs root
+ if (!dir || dir === current) return root;
+
+ return searchForWorkspaceRoot(dir, root);
+}
diff --git a/packages/integrations/vercel/src/serverless/adapter.ts b/packages/integrations/vercel/src/serverless/adapter.ts
index 3803cdf41..f45743b03 100644
--- a/packages/integrations/vercel/src/serverless/adapter.ts
+++ b/packages/integrations/vercel/src/serverless/adapter.ts
@@ -233,10 +233,10 @@ export default function vercelServerless({
if (vercelConfig.trailingSlash === true && config.trailingSlash === 'always') {
logger.warn(
'\n' +
- `\tYour "vercel.json" \`trailingSlash\` configuration (set to \`true\`) will conflict with your Astro \`trailinglSlash\` configuration (set to \`"always"\`).\n` +
- // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation>
- `\tThis would cause infinite redirects under certain conditions and throw an \`ERR_TOO_MANY_REDIRECTS\` error.\n` +
- `\tTo prevent this, your Astro configuration is updated to \`"ignore"\` during builds.\n`
+ `\tYour "vercel.json" \`trailingSlash\` configuration (set to \`true\`) will conflict with your Astro \`trailinglSlash\` configuration (set to \`"always"\`).\n` +
+ // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation>
+ `\tThis would cause infinite redirects under certain conditions and throw an \`ERR_TOO_MANY_REDIRECTS\` error.\n` +
+ `\tTo prevent this, your Astro configuration is updated to \`"ignore"\` during builds.\n`
);
updateConfig({
trailingSlash: 'ignore',
@@ -327,7 +327,7 @@ export default function vercelServerless({
? getRouteFuncName(route)
: getFallbackFuncName(entryFile);
- await builder.buildServerlessFolder(entryFile, func);
+ await builder.buildServerlessFolder(entryFile, func, _config.root);
routeDefinitions.push({
src: route.pattern.source,
@@ -338,7 +338,7 @@ export default function vercelServerless({
const entryFile = new URL(_serverEntry, _buildTempFolder);
if (isr) {
const isrConfig = typeof isr === 'object' ? isr : {};
- await builder.buildServerlessFolder(entryFile, NODE_PATH);
+ await builder.buildServerlessFolder(entryFile, NODE_PATH, _config.root);
if (isrConfig.exclude?.length) {
const dest = _middlewareEntryPoint ? MIDDLEWARE_PATH : NODE_PATH;
for (const route of isrConfig.exclude) {
@@ -346,14 +346,14 @@ export default function vercelServerless({
routeDefinitions.push({ src: escapeRegex(route), dest });
}
}
- await builder.buildISRFolder(entryFile, '_isr', isrConfig);
+ await builder.buildISRFolder(entryFile, '_isr', isrConfig, _config.root);
for (const route of routes) {
const src = route.pattern.source;
const dest = src.startsWith('^\\/_image') ? NODE_PATH : ISR_PATH;
if (!route.prerender) routeDefinitions.push({ src, dest });
}
} else {
- await builder.buildServerlessFolder(entryFile, NODE_PATH);
+ await builder.buildServerlessFolder(entryFile, NODE_PATH, _config.root);
const dest = _middlewareEntryPoint ? MIDDLEWARE_PATH : NODE_PATH;
for (const route of routes) {
if (!route.prerender) routeDefinitions.push({ src: route.pattern.source, dest });
@@ -384,31 +384,31 @@ export default function vercelServerless({
...routeDefinitions,
...(fourOhFourRoute
? [
- {
- src: '/.*',
- dest: fourOhFourRoute.prerender
- ? '/404.html'
- : _middlewareEntryPoint
- ? MIDDLEWARE_PATH
- : NODE_PATH,
- status: 404,
- },
- ]
+ {
+ src: '/.*',
+ dest: fourOhFourRoute.prerender
+ ? '/404.html'
+ : _middlewareEntryPoint
+ ? MIDDLEWARE_PATH
+ : NODE_PATH,
+ status: 404,
+ },
+ ]
: []),
],
...(imageService || imagesConfig
? {
- images: imagesConfig
- ? {
- ...imagesConfig,
- domains: [...imagesConfig.domains, ..._config.image.domains],
- remotePatterns: [
- ...(imagesConfig.remotePatterns ?? []),
- ..._config.image.remotePatterns,
- ],
- }
- : getDefaultImageConfig(_config.image),
- }
+ images: imagesConfig
+ ? {
+ ...imagesConfig,
+ domains: [...imagesConfig.domains, ..._config.image.domains],
+ remotePatterns: [
+ ...(imagesConfig.remotePatterns ?? []),
+ ..._config.image.remotePatterns,
+ ],
+ }
+ : getDefaultImageConfig(_config.image),
+ }
: {}),
});
@@ -431,9 +431,9 @@ class VercelBuilder {
readonly logger: AstroIntegrationLogger,
readonly maxDuration?: number,
readonly runtime = getRuntime(process, logger)
- ) {}
+ ) { }
- async buildServerlessFolder(entry: URL, functionName: string) {
+ async buildServerlessFolder(entry: URL, functionName: string, root: URL) {
const { config, includeFiles, excludeFiles, logger, NTF_CACHE, runtime, maxDuration } = this;
// .vercel/output/functions/<name>.func/
const functionFolder = new URL(`./functions/${functionName}.func/`, config.outDir);
@@ -448,6 +448,7 @@ class VercelBuilder {
includeFiles,
excludeFiles,
logger,
+ root,
},
NTF_CACHE
);
@@ -467,8 +468,8 @@ class VercelBuilder {
});
}
- async buildISRFolder(entry: URL, functionName: string, isr: VercelISRConfig) {
- await this.buildServerlessFolder(entry, functionName);
+ async buildISRFolder(entry: URL, functionName: string, isr: VercelISRConfig, root: URL) {
+ await this.buildServerlessFolder(entry, functionName, root);
const prerenderConfig = new URL(
`./functions/${functionName}.prerender-config.json`,
this.config.outDir
@@ -511,11 +512,11 @@ function getRuntime(process: NodeJS.Process, logger: AstroIntegrationLogger): Ru
// biome-ignore lint/style/useTemplate: <explanation>
// biome-ignore lint/style/noUnusedTemplateLiteral: <explanation>
`\n` +
- `\tThe local Node.js version (${major}) is not supported by Vercel Serverless Functions.\n` +
- // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation>
- `\tYour project will use Node.js 18 as the runtime instead.\n` +
- // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation>
- `\tConsider switching your local version to 18.\n`
+ `\tThe local Node.js version (${major}) is not supported by Vercel Serverless Functions.\n` +
+ // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation>
+ `\tYour project will use Node.js 18 as the runtime instead.\n` +
+ // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation>
+ `\tConsider switching your local version to 18.\n`
);
return 'nodejs18.x';
}
@@ -544,10 +545,10 @@ function getRuntime(process: NodeJS.Process, logger: AstroIntegrationLogger): Ru
// biome-ignore lint/style/useTemplate: <explanation>
// biome-ignore lint/style/noUnusedTemplateLiteral: <explanation>
`\n` +
- `\tYour project is being built for Node.js ${major} as the runtime.\n` +
- `\tThis version is deprecated by Vercel Serverless Functions, and scheduled to be disabled on ${removeDate}.\n` +
- // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation>
- `\tConsider upgrading your local version to 18.\n`
+ `\tYour project is being built for Node.js ${major} as the runtime.\n` +
+ `\tThis version is deprecated by Vercel Serverless Functions, and scheduled to be disabled on ${removeDate}.\n` +
+ // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation>
+ `\tConsider upgrading your local version to 18.\n`
);
return `nodejs${major}.x`;
}