UNPKG

@pnpm/core

Version:

Fast, disk space efficient installation engine

388 lines 18.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.linkPackages = linkPackages; const fs_1 = require("fs"); const path_1 = __importDefault(require("path")); const calc_dep_state_1 = require("@pnpm/calc-dep-state"); const core_loggers_1 = require("@pnpm/core-loggers"); const lockfile_filtering_1 = require("@pnpm/lockfile.filtering"); const pkg_manager_direct_dep_linker_1 = require("@pnpm/pkg-manager.direct-dep-linker"); const hoist_1 = require("@pnpm/hoist"); const logger_1 = require("@pnpm/logger"); const modules_cleaner_1 = require("@pnpm/modules-cleaner"); const symlink_dependency_1 = require("@pnpm/symlink-dependency"); const worker_1 = require("@pnpm/worker"); const p_limit_1 = __importDefault(require("p-limit")); const path_exists_1 = __importDefault(require("path-exists")); const equals_1 = __importDefault(require("ramda/src/equals")); const isEmpty_1 = __importDefault(require("ramda/src/isEmpty")); const difference_1 = __importDefault(require("ramda/src/difference")); const omit_1 = __importDefault(require("ramda/src/omit")); const pick_1 = __importDefault(require("ramda/src/pick")); const pickBy_1 = __importDefault(require("ramda/src/pickBy")); const props_1 = __importDefault(require("ramda/src/props")); const brokenModulesLogger = (0, logger_1.logger)('_broken_node_modules'); async function linkPackages(projects, depGraph, opts) { let depNodes = Object.values(depGraph).filter(({ depPath, id }) => { if (((opts.wantedLockfile.packages?.[depPath]) != null) && !opts.wantedLockfile.packages[depPath].optional) { opts.skipped.delete(depPath); return true; } if (opts.wantedToBeSkippedPackageIds.has(id)) { opts.skipped.add(depPath); return false; } opts.skipped.delete(depPath); return true; }); if (!opts.include.dependencies) { depNodes = depNodes.filter(({ dev, optional }) => dev || optional); } if (!opts.include.devDependencies) { depNodes = depNodes.filter(({ optional, prod }) => prod || optional); } if (!opts.include.optionalDependencies) { depNodes = depNodes.filter(({ optional }) => !optional); } depGraph = Object.fromEntries(depNodes.map((depNode) => [depNode.depPath, depNode])); const removedDepPaths = await (0, modules_cleaner_1.prune)(projects, { currentLockfile: opts.currentLockfile, dedupeDirectDeps: opts.dedupeDirectDeps, hoistedDependencies: opts.hoistedDependencies, hoistedModulesDir: (opts.hoistPattern != null) ? opts.hoistedModulesDir : undefined, include: opts.include, lockfileDir: opts.lockfileDir, pruneStore: opts.pruneStore, pruneVirtualStore: opts.pruneVirtualStore, publicHoistedModulesDir: (opts.publicHoistPattern != null) ? opts.rootModulesDir : undefined, skipped: opts.skipped, storeController: opts.storeController, virtualStoreDir: opts.virtualStoreDir, virtualStoreDirMaxLength: opts.virtualStoreDirMaxLength, wantedLockfile: opts.wantedLockfile, }); core_loggers_1.stageLogger.debug({ prefix: opts.lockfileDir, stage: 'importing_started', }); const projectIds = projects.map(({ id }) => id); const filterOpts = { include: opts.include, registries: opts.registries, skipped: opts.skipped, }; const newCurrentLockfile = (0, lockfile_filtering_1.filterLockfileByImporters)(opts.wantedLockfile, projectIds, { ...filterOpts, failOnMissingDependencies: true, skipped: new Set(), }); const { newDepPaths, added } = await linkNewPackages((0, lockfile_filtering_1.filterLockfileByImporters)(opts.currentLockfile, projectIds, { ...filterOpts, failOnMissingDependencies: false, }), newCurrentLockfile, depGraph, { allowBuild: opts.allowBuild, disableRelinkLocalDirDeps: opts.disableRelinkLocalDirDeps, force: opts.force, depsStateCache: opts.depsStateCache, ignoreScripts: opts.ignoreScripts, lockfileDir: opts.lockfileDir, optional: opts.include.optionalDependencies, sideEffectsCacheRead: opts.sideEffectsCacheRead, symlink: opts.symlink, skipped: opts.skipped, storeController: opts.storeController, virtualStoreDir: opts.virtualStoreDir, }); core_loggers_1.stageLogger.debug({ prefix: opts.lockfileDir, stage: 'importing_done', }); let currentLockfile; const allImportersIncluded = (0, equals_1.default)(projectIds.sort(), Object.keys(opts.wantedLockfile.importers).sort()); if (opts.makePartialCurrentLockfile || !allImportersIncluded) { const packages = opts.currentLockfile.packages ?? {}; if (opts.wantedLockfile.packages != null) { for (const depPath in opts.wantedLockfile.packages) { // eslint-disable-line:forin if (depGraph[depPath]) { packages[depPath] = opts.wantedLockfile.packages[depPath]; } } } const projects = { ...opts.currentLockfile.importers, ...(0, pick_1.default)(projectIds, opts.wantedLockfile.importers), }; currentLockfile = (0, lockfile_filtering_1.filterLockfileByImporters)({ ...opts.wantedLockfile, importers: projects, packages, }, Object.keys(projects), { ...filterOpts, failOnMissingDependencies: false, skipped: new Set(), }); } else if (opts.include.dependencies && opts.include.devDependencies && opts.include.optionalDependencies && opts.skipped.size === 0) { currentLockfile = opts.wantedLockfile; } else { currentLockfile = newCurrentLockfile; } let newHoistedDependencies; if (opts.hoistPattern == null && opts.publicHoistPattern == null) { newHoistedDependencies = {}; } else if (newDepPaths.length > 0 || removedDepPaths.size > 0) { // It is important to keep the skipped packages in the lockfile which will be saved as the "current lockfile". // pnpm is comparing the current lockfile to the wanted one and they should match. // But for hoisting, we need a version of the lockfile w/o the skipped packages, so we're making a copy. const hoistLockfile = { ...currentLockfile, packages: currentLockfile.packages != null ? (0, omit_1.default)(Array.from(opts.skipped), currentLockfile.packages) : {}, }; newHoistedDependencies = await (0, hoist_1.hoist)({ extraNodePath: opts.extraNodePaths, lockfile: hoistLockfile, importerIds: projectIds, privateHoistedModulesDir: opts.hoistedModulesDir, privateHoistPattern: opts.hoistPattern ?? [], publicHoistedModulesDir: opts.rootModulesDir, publicHoistPattern: opts.publicHoistPattern ?? [], virtualStoreDir: opts.virtualStoreDir, virtualStoreDirMaxLength: opts.virtualStoreDirMaxLength, hoistedWorkspacePackages: opts.hoistWorkspacePackages ? projects.reduce((hoistedWorkspacePackages, project) => { if (project.manifest.name && project.id !== '.') { hoistedWorkspacePackages[project.id] = { dir: project.rootDir, name: project.manifest.name, }; } return hoistedWorkspacePackages; }, {}) : undefined, }); } else { newHoistedDependencies = opts.hoistedDependencies; } let linkedToRoot = 0; if (opts.symlink) { const projectsToLink = Object.fromEntries(await Promise.all(projects.map(async ({ id, manifest, modulesDir, rootDir }) => { const deps = opts.dependenciesByProjectId[id]; const importerFromLockfile = newCurrentLockfile.importers[id]; return [id, { dir: rootDir, modulesDir, dependencies: await Promise.all([ ...Array.from(deps.entries()) .filter(([rootAlias]) => importerFromLockfile.specifiers[rootAlias]) .map(([rootAlias, depPath]) => ({ rootAlias, depGraphNode: depGraph[depPath] })) .filter(({ depGraphNode }) => depGraphNode) .map(async ({ rootAlias, depGraphNode }) => { const isDev = Boolean(manifest.devDependencies?.[depGraphNode.name]); const isOptional = Boolean(manifest.optionalDependencies?.[depGraphNode.name]); return { alias: rootAlias, name: depGraphNode.name, version: depGraphNode.version, dir: depGraphNode.dir, id: depGraphNode.id, dependencyType: (isDev && 'dev' || isOptional && 'optional' || 'prod'), latest: opts.outdatedDependencies[depGraphNode.id], isExternalLink: false, }; }), ...opts.linkedDependenciesByProjectId[id].map(async (linkedDependency) => { const dir = resolvePath(rootDir, linkedDependency.resolution.directory); return { alias: linkedDependency.alias, name: linkedDependency.name, version: linkedDependency.version, dir, id: linkedDependency.resolution.directory, dependencyType: (linkedDependency.dev && 'dev' || linkedDependency.optional && 'optional' || 'prod'), isExternalLink: true, }; }), ]), }]; }))); linkedToRoot = await (0, pkg_manager_direct_dep_linker_1.linkDirectDeps)(projectsToLink, { dedupe: opts.dedupeDirectDeps }); } return { currentLockfile, newDepPaths, newHoistedDependencies, removedDepPaths, stats: { added, removed: removedDepPaths.size, linkedToRoot, }, }; } const isAbsolutePath = /^\/|^[A-Z]:/i; // This function is copied from @pnpm/local-resolver function resolvePath(where, spec) { if (isAbsolutePath.test(spec)) return spec; return path_1.default.resolve(where, spec); } async function linkNewPackages(currentLockfile, wantedLockfile, depGraph, opts) { const wantedRelDepPaths = (0, difference_1.default)(Object.keys(wantedLockfile.packages ?? {}), Array.from(opts.skipped)); let newDepPathsSet; if (opts.force) { newDepPathsSet = new Set(wantedRelDepPaths // when installing a new package, not all the nodes are analyzed // just skip the ones that are in the lockfile but were not analyzed .filter((depPath) => depGraph[depPath])); } else { newDepPathsSet = await selectNewFromWantedDeps(wantedRelDepPaths, currentLockfile, depGraph); } const added = newDepPathsSet.size; core_loggers_1.statsLogger.debug({ added, prefix: opts.lockfileDir, }); const existingWithUpdatedDeps = []; if (!opts.force && (currentLockfile.packages != null) && (wantedLockfile.packages != null)) { // add subdependencies that have been updated // TODO: no need to relink everything. Can be relinked only what was changed for (const depPath of wantedRelDepPaths) { if (currentLockfile.packages[depPath] && (!(0, equals_1.default)(currentLockfile.packages[depPath].dependencies, wantedLockfile.packages[depPath].dependencies) || !(0, isEmpty_1.default)(currentLockfile.packages[depPath].optionalDependencies ?? {}) || !(0, isEmpty_1.default)(wantedLockfile.packages[depPath].optionalDependencies ?? {}))) { // TODO: come up with a test that triggers the usecase of depGraph[depPath] undefined // see related issue: https://github.com/pnpm/pnpm/issues/870 if (depGraph[depPath] && !newDepPathsSet.has(depPath)) { existingWithUpdatedDeps.push(depGraph[depPath]); } } } } if (!newDepPathsSet.size && (existingWithUpdatedDeps.length === 0)) return { newDepPaths: [], added }; const newDepPaths = Array.from(newDepPathsSet); const newPkgs = (0, props_1.default)(newDepPaths, depGraph); await Promise.all(newPkgs.map(async (depNode) => fs_1.promises.mkdir(depNode.modules, { recursive: true }))); await Promise.all([ !opts.symlink ? Promise.resolve() : linkAllModules([...newPkgs, ...existingWithUpdatedDeps], depGraph, { lockfileDir: opts.lockfileDir, optional: opts.optional, }), linkAllPkgs(opts.storeController, newPkgs, { allowBuild: opts.allowBuild, depGraph, depsStateCache: opts.depsStateCache, disableRelinkLocalDirDeps: opts.disableRelinkLocalDirDeps, force: opts.force, ignoreScripts: opts.ignoreScripts, lockfileDir: opts.lockfileDir, sideEffectsCacheRead: opts.sideEffectsCacheRead, }), ]); return { newDepPaths, added }; } async function selectNewFromWantedDeps(wantedRelDepPaths, currentLockfile, depGraph) { const newDeps = new Set(); const prevDeps = currentLockfile.packages ?? {}; await Promise.all(wantedRelDepPaths.map(async (depPath) => { const depNode = depGraph[depPath]; if (!depNode) return; const prevDep = prevDeps[depPath]; if (prevDep && // Local file should always be treated as a new dependency // https://github.com/pnpm/pnpm/issues/5381 depNode.resolution.type !== 'directory' && depNode.resolution.integrity === prevDep.resolution.integrity) { if (await (0, path_exists_1.default)(depNode.dir)) { return; } brokenModulesLogger.debug({ missing: depNode.dir, }); } newDeps.add(depPath); })); return newDeps; } const limitLinking = (0, p_limit_1.default)(16); async function linkAllPkgs(storeController, depNodes, opts) { await Promise.all(depNodes.map(async (depNode) => { const { files } = await depNode.fetching(); depNode.requiresBuild = files.requiresBuild; let sideEffectsCacheKey; if (opts.sideEffectsCacheRead && files.sideEffects && !(0, isEmpty_1.default)(files.sideEffects)) { if (opts?.allowBuild?.(depNode.name) !== false) { sideEffectsCacheKey = (0, calc_dep_state_1.calcDepState)(opts.depGraph, opts.depsStateCache, depNode.depPath, { isBuilt: !opts.ignoreScripts && depNode.requiresBuild, patchFileHash: depNode.patch?.file.hash, }); } } const { importMethod, isBuilt } = await storeController.importPackage(depNode.dir, { disableRelinkLocalDirDeps: opts.disableRelinkLocalDirDeps, filesResponse: files, force: opts.force, sideEffectsCacheKey, requiresBuild: depNode.patch != null || depNode.requiresBuild, }); if (importMethod) { core_loggers_1.progressLogger.debug({ method: importMethod, requester: opts.lockfileDir, status: 'imported', to: depNode.dir, }); } depNode.isBuilt = isBuilt; const selfDep = depNode.children[depNode.name]; if (selfDep) { const pkg = opts.depGraph[selfDep]; if (!pkg || !pkg.installable && pkg.optional) return; const targetModulesDir = path_1.default.join(depNode.modules, depNode.name, 'node_modules'); await limitLinking(async () => (0, symlink_dependency_1.symlinkDependency)(pkg.dir, targetModulesDir, depNode.name)); } })); } async function linkAllModules(depNodes, depGraph, opts) { await (0, worker_1.symlinkAllModules)({ deps: depNodes.map((depNode) => { const children = opts.optional ? depNode.children : (0, pickBy_1.default)((_, childAlias) => !depNode.optionalDependencies.has(childAlias), depNode.children); const childrenPaths = {}; for (const [alias, childDepPath] of Object.entries(children ?? {})) { if (childDepPath.startsWith('link:')) { childrenPaths[alias] = path_1.default.resolve(opts.lockfileDir, childDepPath.slice(5)); } else { const pkg = depGraph[childDepPath]; if (!pkg || !pkg.installable && pkg.optional || alias === depNode.name) continue; childrenPaths[alias] = pkg.dir; } } return { children: childrenPaths, modules: depNode.modules, name: depNode.name, }; }), }); } //# sourceMappingURL=link.js.map