import { realpath } from "node:fs/promises"; import path from "node:path"; import { nodeFileTrace } from "@vercel/nft"; import { copyPathMaterializingSymlinks, hoistIsolatedStoreDependencies, isPathWithin, } from "./artifact-stage.ts"; import { resolveSourceRoot } from "./config/source-root.ts"; /** * Configured artifacts copy an output directory's contents to the artifact * root. Runtime dependencies traced from that output need the same relocation: * app-local node_modules become artifact-root node_modules, while hoisted * workspace deps and workspace packages keep enough source-root layout for * bare imports and package symlinks to resolve after unpacking. */ export async function stageConfiguredArtifactDependencies(options: { appPath: string; artifactDir: string; outputDirectory: string; entrypoint: string; signal?: AbortSignal; }): Promise { const [appRoot, outputRoot] = await Promise.all([ realpath(path.resolve(options.appPath)), realpath(path.resolve(options.outputDirectory)), ]); const sourceRoot = await resolveSourceRoot(appRoot, options.signal); const entrypointPath = path.join(outputRoot, options.entrypoint); options.signal?.throwIfAborted(); const { fileList } = await nodeFileTrace([entrypointPath], { base: sourceRoot, }); for (const relativePath of fileList) { options.signal?.throwIfAborted(); const sourcePath = path.join(sourceRoot, relativePath); if (isPathWithin(outputRoot, sourcePath)) { continue; } await copyPathMaterializingSymlinks( sourcePath, path.join( options.artifactDir, artifactRelativePathForTrace(sourcePath, { appRoot, sourceRoot, }), ), { appRoot, sourceRoot, allowedSymlinkTargetRoots: [appRoot, sourceRoot], ignoreMissingSource: true, signal: options.signal, }, ); } await hoistIsolatedStoreDependencies( path.join(options.artifactDir, "node_modules"), options.signal, ); } function artifactRelativePathForTrace( sourcePath: string, options: { appRoot: string; sourceRoot: string }, ): string { const resolvedSource = path.resolve(sourcePath); const appNodeModules = path.join(options.appRoot, "node_modules"); const sourceNodeModules = path.join(options.sourceRoot, "node_modules"); if (isPathWithin(appNodeModules, resolvedSource)) { return path.join( "node_modules", path.relative(appNodeModules, resolvedSource), ); } if (isPathWithin(options.appRoot, resolvedSource)) { return path.relative(options.appRoot, resolvedSource); } if (isPathWithin(sourceNodeModules, resolvedSource)) { return path.join( "node_modules", path.relative(sourceNodeModules, resolvedSource), ); } return path.relative(options.sourceRoot, resolvedSource); }