diff --git a/.changeset/brave-radios-fetch.md b/.changeset/brave-radios-fetch.md new file mode 100644 index 0000000000..4b5e38dc52 --- /dev/null +++ b/.changeset/brave-radios-fetch.md @@ -0,0 +1,5 @@ +--- +"@pnpm/headless": patch +--- + +bin files of dependencies linked from the workspace, should be created. diff --git a/.changeset/sweet-drinks-wash.md b/.changeset/sweet-drinks-wash.md new file mode 100644 index 0000000000..0bf08f28f4 --- /dev/null +++ b/.changeset/sweet-drinks-wash.md @@ -0,0 +1,6 @@ +--- +"@pnpm/headless": patch +"supi": patch +--- + +Perform less filesystem operations during the creation of bin files of direct dependencies. diff --git a/packages/headless/src/index.ts b/packages/headless/src/index.ts index a6a5872002..c5af897a20 100644 --- a/packages/headless/src/index.ts +++ b/packages/headless/src/index.ts @@ -44,7 +44,7 @@ import { } from '@pnpm/modules-yaml' import pkgIdToFilename from '@pnpm/pkgid-to-filename' import { fromDir as readPackageFromDir } from '@pnpm/read-package-json' -import { readProjectManifestOnly } from '@pnpm/read-project-manifest' +import { readProjectManifestOnly, safeReadProjectManifestOnly } from '@pnpm/read-project-manifest' import { PackageFilesResponse, StoreController, @@ -328,7 +328,26 @@ export default async (opts: HeadlessOptions) => { } await linkAllBins(graph, { optional: opts.include.optionalDependencies, warn }) - await Promise.all(opts.projects.map(linkBinsOfImporter)) + await Promise.all(opts.projects.map(async (project) => { + if (opts.publicHoistPattern?.length && path.relative(opts.lockfileDir, project.rootDir) === '') { + await linkBinsOfImporter(project) + } else { + const directPkgDirs = Object.values(directDependenciesByImporterId[project.id]) + await linkBinsOfPackages( + ( + await Promise.all( + directPkgDirs.map(async (dir) => ({ + location: dir, + manifest: await safeReadProjectManifestOnly(dir), + })) + ) + ) + .filter(({ manifest }) => manifest != null) as Array<{ location: string, manifest: DependencyManifest }>, + project.binsDir, + { warn: (message: string) => logger.info({ message, prefix: project.rootDir }) } + ) + } + })) if (currentLockfile && !R.equals(opts.projects.map(({ id }) => id).sort(), Object.keys(filteredLockfile.importers).sort())) { Object.assign(filteredLockfile.packages, currentLockfile.packages) @@ -570,7 +589,7 @@ async function lockfileToDepGraph ( } const peerDeps = pkgSnapshot.peerDependencies ? new Set(Object.keys(pkgSnapshot.peerDependencies)) : null - graph[dir].children = await getChildrenPaths(ctx, allDeps, peerDeps) + graph[dir].children = await getChildrenPaths(ctx, allDeps, peerDeps, '.') } for (const importerId of opts.importerIds) { const projectSnapshot = lockfile.importers[importerId] @@ -579,7 +598,7 @@ async function lockfileToDepGraph ( ...(opts.include.dependencies ? projectSnapshot.dependencies : {}), ...(opts.include.optionalDependencies ? projectSnapshot.optionalDependencies : {}), } - directDependenciesByImporterId[importerId] = await getChildrenPaths(ctx, rootDeps, null) + directDependenciesByImporterId[importerId] = await getChildrenPaths(ctx, rootDeps, null, importerId) } } return { graph, directDependenciesByImporterId } @@ -599,13 +618,14 @@ async function getChildrenPaths ( storeController: StoreController }, allDeps: {[alias: string]: string}, - peerDeps: Set | null + peerDeps: Set | null, + importerId: string ) { const children: {[alias: string]: string} = {} for (const alias of Object.keys(allDeps)) { const childDepPath = dp.refToAbsolute(allDeps[alias], alias, ctx.registries) if (childDepPath === null) { - children[alias] = path.resolve(ctx.lockfileDir, allDeps[alias].substr(5)) + children[alias] = path.resolve(ctx.lockfileDir, importerId, allDeps[alias].substr(5)) continue } const childRelDepPath = dp.refToRelative(allDeps[alias], alias) as string diff --git a/packages/headless/test/index.ts b/packages/headless/test/index.ts index 9b9335b58d..7faa878e69 100644 --- a/packages/headless/test/index.ts +++ b/packages/headless/test/index.ts @@ -761,7 +761,7 @@ test('installing with no symlinks', async (t) => { symlink: false, })) - t.deepEqual(await fs.readdir(path.join(prefix, 'node_modules')), ['.modules.yaml', '.pnpm']) + t.deepEqual(await fs.readdir(path.join(prefix, 'node_modules')), ['.bin', '.modules.yaml', '.pnpm']) t.deepEqual(await fs.readdir(path.join(prefix, 'node_modules/.pnpm/rimraf@2.7.1/node_modules')), ['rimraf']) const project = assertProject(t, prefix) diff --git a/packages/supi/src/install/index.ts b/packages/supi/src/install/index.ts index 416d36f912..85db4d4a47 100644 --- a/packages/supi/src/install/index.ts +++ b/packages/supi/src/install/index.ts @@ -14,7 +14,7 @@ import headless from '@pnpm/headless' import { runLifecycleHooksConcurrently, } from '@pnpm/lifecycle' -import linkBins from '@pnpm/link-bins' +import linkBins, { linkBinsOfPackages } from '@pnpm/link-bins' import { ProjectSnapshot, writeCurrentLockfile, @@ -25,11 +25,11 @@ import logger, { streamParser } from '@pnpm/logger' import { getAllDependenciesFromManifest } from '@pnpm/manifest-utils' import { write as writeModulesYaml } from '@pnpm/modules-yaml' import readModulesDirs from '@pnpm/read-modules-dir' +import { safeReadProjectManifestOnly } from '@pnpm/read-project-manifest' import { removeBin } from '@pnpm/remove-bins' import resolveDependencies, { DependenciesGraph, DependenciesGraphNode, - ImporterToResolve, } from '@pnpm/resolve-dependencies' import { PreferredVersions, @@ -37,6 +37,7 @@ import { } from '@pnpm/resolver-base' import { DependenciesField, + DependencyManifest, ProjectManifest, } from '@pnpm/types' import parseWantedDependencies from '../parseWantedDependencies' @@ -712,16 +713,47 @@ async function installInContext ( }) } + const binWarn = (prefix: string, message: string) => logger.info({ message, prefix }) if (result.newDepPaths?.length) { const newPkgs = R.props(result.newDepPaths, dependenciesGraph) await linkAllBins(newPkgs, dependenciesGraph, { optional: opts.include.optionalDependencies, - warn: (message: string) => logger.warn({ message, prefix: opts.lockfileDir }), + warn: binWarn.bind(null, opts.lockfileDir), }) } await Promise.all(projectsToResolve.map(async (project, index) => { - const linkedPackages = await linkBinsOfImporter(project) + let linkedPackages!: string[] + if (ctx.publicHoistPattern?.length && path.relative(project.rootDir, opts.lockfileDir) === '') { + linkedPackages = await linkBins(project.modulesDir, project.binsDir, { + allowExoticManifests: true, + warn: binWarn.bind(null, project.rootDir), + }) + } else { + const directPkgs = [ + ...R.props( + Object.values(dependenciesByProjectId[project.id]).filter((depPath) => !ctx.skipped.has(depPath)), + dependenciesGraph + ), + ...linkedDependenciesByProjectId[project.id].map(({ pkgId }) => ({ + dir: path.join(project.rootDir, pkgId.substring(5)), + fetchingBundledManifest: undefined, + })), + ] + linkedPackages = await linkBinsOfPackages( + ( + await Promise.all( + directPkgs.map(async (dep) => ({ + location: dep.dir, + manifest: await dep.fetchingBundledManifest?.() ?? await safeReadProjectManifestOnly(dep.dir), + })) + ) + ) + .filter(({ manifest }) => manifest != null) as Array<{ location: string, manifest: DependencyManifest }>, + project.binsDir, + { warn: binWarn.bind(null, project.rootDir) } + ) + } const projectToInstall = projects[index] if (opts.global && projectToInstall.mutation.includes('install')) { projectToInstall.wantedDependencies.forEach(pkg => { @@ -846,12 +878,7 @@ function prefIsLocalTarball (pref: string) { const limitLinking = pLimit(16) -function linkBinsOfImporter ({ modulesDir, binsDir, rootDir }: ImporterToResolve) { - const warn = (message: string) => logger.info({ message, prefix: rootDir }) - return linkBins(modulesDir, binsDir, { allowExoticManifests: true, warn }) -} - -function linkAllBins ( +async function linkAllBins ( depNodes: DependenciesGraphNode[], depGraph: DependenciesGraph, opts: { @@ -859,7 +886,7 @@ function linkAllBins ( warn: (message: string) => void } ) { - return Promise.all( + return R.unnest(await Promise.all( depNodes.map(depNode => limitLinking(() => linkBinsOfDependencies(depNode, depGraph, opts))) - ) + )) } diff --git a/packages/supi/test/install/misc.ts b/packages/supi/test/install/misc.ts index ec1a3fa56c..01762a983f 100644 --- a/packages/supi/test/install/misc.ts +++ b/packages/supi/test/install/misc.ts @@ -1237,10 +1237,10 @@ test('installing with no symlinks', async (t) => { version: '0.0.0', }, ['rimraf@2.7.1'], - await testDefaults({ symlink: false }) + await testDefaults({ fastUnpack: false, symlink: false }) ) - t.deepEqual(await fs.readdir(path.resolve('node_modules')), ['.modules.yaml', '.pnpm']) + t.deepEqual(await fs.readdir(path.resolve('node_modules')), ['.bin', '.modules.yaml', '.pnpm']) t.deepEqual(await fs.readdir(path.resolve('node_modules/.pnpm/rimraf@2.7.1/node_modules')), ['rimraf']) t.ok(await project.readCurrentLockfile())