diff options
author | 2024-09-17 07:52:50 +0200 | |
---|---|---|
committer | 2024-09-17 07:52:50 +0200 | |
commit | 7fc8dae5c529088cf4b98f544a7a2f4a86d8c33b (patch) | |
tree | 43afe63c9a907ff417ea0ddcad0b542d3f4d53e9 /packages/integrations/vercel/src | |
parent | bea4ad266c4f9ea44f311f4d433a697a91e9ed0d (diff) | |
parent | 0afc80bb7f6711b37189079d3d86f3651a64c672 (diff) | |
download | astro-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.ts | 3 | ||||
-rw-r--r-- | packages/integrations/vercel/src/lib/nft.ts | 16 | ||||
-rw-r--r-- | packages/integrations/vercel/src/lib/redirects.ts | 29 | ||||
-rw-r--r-- | packages/integrations/vercel/src/lib/searchRoot.ts | 101 | ||||
-rw-r--r-- | packages/integrations/vercel/src/serverless/adapter.ts | 85 |
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`; } |