UNPKG

nx

Version:

The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.

467 lines (466 loc) • 18.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPnpmLockfileNodes = getPnpmLockfileNodes; exports.getPnpmLockfileDependencies = getPnpmLockfileDependencies; exports.stringifyPnpmLockfile = stringifyPnpmLockfile; const pnpm_normalizer_1 = require("./utils/pnpm-normalizer"); const package_json_1 = require("./utils/package-json"); const object_sort_1 = require("../../../utils/object-sort"); const project_graph_builder_1 = require("../../../project-graph/project-graph-builder"); const project_graph_1 = require("../../../config/project-graph"); const file_hasher_1 = require("../../../hasher/file-hasher"); // we use key => node map to avoid duplicate work when parsing keys let keyMap = new Map(); let currentLockFileHash; let parsedLockFile; function parsePnpmLockFile(lockFileContent, lockFileHash) { if (lockFileHash === currentLockFileHash) { return parsedLockFile; } keyMap.clear(); const results = (0, pnpm_normalizer_1.parseAndNormalizePnpmLockfile)(lockFileContent); parsedLockFile = results; currentLockFileHash = lockFileHash; return results; } function getPnpmLockfileNodes(lockFileContent, lockFileHash) { const data = parsePnpmLockFile(lockFileContent, lockFileHash); if (+data.lockfileVersion.toString() >= 10) { console.warn('Nx was tested only with pnpm lockfile version 5-9. If you encounter any issues, please report them and downgrade to older version of pnpm.'); } const isV5 = (0, pnpm_normalizer_1.isV5Syntax)(data); return getNodes(data, keyMap, isV5); } function getPnpmLockfileDependencies(lockFileContent, lockFileHash, ctx) { const data = parsePnpmLockFile(lockFileContent, lockFileHash); if (+data.lockfileVersion.toString() >= 10) { console.warn('Nx was tested only with pnpm lockfile version 5-9. If you encounter any issues, please report them and downgrade to older version of pnpm.'); } const isV5 = (0, pnpm_normalizer_1.isV5Syntax)(data); return getDependencies(data, keyMap, isV5, ctx); } function matchPropValue(record, key, originalPackageName) { if (!record) { return undefined; } const index = Object.values(record).findIndex((version) => version === key); if (index > -1) { return Object.keys(record)[index]; } // check if non-aliased name is found if (record[originalPackageName] && key.startsWith(`/${originalPackageName}/${record[originalPackageName]}`)) { return originalPackageName; } } function matchedDependencyName(importer, key, originalPackageName) { return (matchPropValue(importer.dependencies, key, originalPackageName) || matchPropValue(importer.optionalDependencies, key, originalPackageName) || matchPropValue(importer.peerDependencies, key, originalPackageName)); } function createHashFromSnapshot(snapshot) { return (snapshot.resolution?.['integrity'] || (snapshot.resolution?.['tarball'] ? (0, file_hasher_1.hashArray)([snapshot.resolution['tarball']]) : undefined)); } function isAliasVersion(depVersion) { return depVersion.startsWith('/') || depVersion.includes('@'); } function getNodes(data, keyMap, isV5) { const nodes = new Map(); const maybeAliasedPackageVersions = new Map(); // <version, alias> if (data.importers['.'].optionalDependencies) { for (const [depName, depVersion] of Object.entries(data.importers['.'].optionalDependencies)) { if (isAliasVersion(depVersion)) { maybeAliasedPackageVersions.set(depVersion, depName); } } } if (data.importers['.'].devDependencies) { for (const [depName, depVersion] of Object.entries(data.importers['.'].devDependencies)) { if (isAliasVersion(depVersion)) { maybeAliasedPackageVersions.set(depVersion, depName); } } } if (data.importers['.'].dependencies) { for (const [depName, depVersion] of Object.entries(data.importers['.'].dependencies)) { if (isAliasVersion(depVersion)) { maybeAliasedPackageVersions.set(depVersion, depName); } } } const packageNames = new Set(); let packageNameObj; for (const [key, snapshot] of Object.entries(data.packages)) { const originalPackageName = extractNameFromKey(key, isV5); if (!originalPackageName) { continue; } const hash = createHashFromSnapshot(snapshot); // snapshot already has a name if (snapshot.name) { packageNameObj = { key, packageName: snapshot.name, hash, }; } const rootDependencyName = matchedDependencyName(data.importers['.'], key, originalPackageName) || matchedDependencyName(data.importers['.'], `/${key}`, originalPackageName) || // only root importers have devDependencies matchPropValue(data.importers['.'].devDependencies, key, originalPackageName) || matchPropValue(data.importers['.'].devDependencies, `/${key}`, originalPackageName); if (rootDependencyName) { packageNameObj = { key, packageName: rootDependencyName, hash: createHashFromSnapshot(snapshot), }; } if (!snapshot.name && !rootDependencyName) { packageNameObj = { key, packageName: originalPackageName, hash: createHashFromSnapshot(snapshot), }; } if (snapshot.peerDependencies) { for (const [depName, depVersion] of Object.entries(snapshot.peerDependencies)) { if (isAliasVersion(depVersion)) { maybeAliasedPackageVersions.set(depVersion, depName); } } } if (snapshot.optionalDependencies) { for (const [depName, depVersion] of Object.entries(snapshot.optionalDependencies)) { if (isAliasVersion(depVersion)) { maybeAliasedPackageVersions.set(depVersion, depName); } } } if (snapshot.dependencies) { for (const [depName, depVersion] of Object.entries(snapshot.dependencies)) { if (isAliasVersion(depVersion)) { maybeAliasedPackageVersions.set(depVersion, depName); } } } const aliasedDep = maybeAliasedPackageVersions.get(`/${key}`); if (aliasedDep) { packageNameObj = { key, packageName: aliasedDep, hash, alias: true, }; } packageNames.add(packageNameObj); const localAlias = maybeAliasedPackageVersions.get(key); if (localAlias) { packageNameObj = { key, packageName: localAlias, hash, alias: true, }; packageNames.add(packageNameObj); } } for (const { key, packageName, hash, alias } of packageNames) { const rawVersion = findVersion(key, packageName, isV5, alias); if (!rawVersion) { continue; } const version = parseBaseVersion(rawVersion, isV5); if (!version) { continue; } if (!nodes.has(packageName)) { nodes.set(packageName, new Map()); } if (!nodes.get(packageName).has(version)) { const node = { type: 'npm', name: version && !version.startsWith('npm:') ? `npm:${packageName}@${version}` : `npm:${packageName}`, data: { version, packageName, hash: hash ?? (0, file_hasher_1.hashArray)([packageName, version]), }, }; nodes.get(packageName).set(version, node); if (!keyMap.has(key)) { keyMap.set(key, new Set([node])); } else { keyMap.get(key).add(node); } } else { const node = nodes.get(packageName).get(version); if (!keyMap.has(key)) { keyMap.set(key, new Set([node])); } else { keyMap.get(key).add(node); } } } const hoistedDeps = (0, pnpm_normalizer_1.loadPnpmHoistedDepsDefinition)(); const results = {}; for (const [packageName, versionMap] of nodes.entries()) { let hoistedNode; if (versionMap.size === 1) { hoistedNode = versionMap.values().next().value; } else { const hoistedVersion = getHoistedVersion(hoistedDeps, packageName, isV5); hoistedNode = versionMap.get(hoistedVersion); } if (hoistedNode) { hoistedNode.name = `npm:${packageName}`; } versionMap.forEach((node) => { results[node.name] = node; }); } return results; } function getHoistedVersion(hoistedDependencies, packageName, isV5) { let version = (0, package_json_1.getHoistedPackageVersion)(packageName); if (!version) { const key = Object.keys(hoistedDependencies).find((k) => k.startsWith(`/${packageName}/`)); if (key) { version = parseBaseVersion(getVersion(key.slice(1), packageName), isV5); } else { // pnpm might not hoist every package // similarly those packages will not be available to be used via import return; } } return version; } function getDependencies(data, keyMap, isV5, ctx) { const results = []; Object.entries(data.packages).forEach(([key, snapshot]) => { const nodes = keyMap.get(key); nodes.forEach((node) => { [snapshot.dependencies, snapshot.optionalDependencies].forEach((section) => { if (section) { Object.entries(section).forEach(([name, versionRange]) => { const version = parseBaseVersion(findVersion(versionRange, name, isV5), isV5); const target = ctx.externalNodes[`npm:${name}@${version}`] || ctx.externalNodes[`npm:${name}`]; if (target) { const dep = { source: node.name, target: target.name, type: project_graph_1.DependencyType.static, }; (0, project_graph_builder_1.validateDependency)(dep, ctx); results.push(dep); } }); } }); }); }); return results; } function parseBaseVersion(rawVersion, isV5) { return isV5 ? rawVersion.split('_')[0] : rawVersion.split('(')[0]; } function stringifyPnpmLockfile(graph, rootLockFileContent, packageJson) { const data = (0, pnpm_normalizer_1.parseAndNormalizePnpmLockfile)(rootLockFileContent); const { lockfileVersion, packages } = data; const rootSnapshot = mapRootSnapshot(packageJson, packages, graph.externalNodes, +lockfileVersion); const snapshots = mapSnapshots(data.packages, graph.externalNodes, +lockfileVersion); const output = { ...data, lockfileVersion, importers: { '.': rootSnapshot, }, packages: (0, object_sort_1.sortObjectByKeys)(snapshots), }; return (0, pnpm_normalizer_1.stringifyToPnpmYaml)(output); } function mapSnapshots(packages, nodes, lockfileVersion) { const result = {}; Object.values(nodes).forEach((node) => { const matchedKeys = findOriginalKeys(packages, node, lockfileVersion, { returnFullKey: true, }); // the package manager doesn't check for types of dependencies // so we can safely set all to prod matchedKeys.forEach(([key, snapshot]) => { if (lockfileVersion >= 9) { delete snapshot['dev']; result[key] = snapshot; } else { snapshot['dev'] = false; // all dependencies are prod remapDependencies(snapshot); if (snapshot.resolution?.['tarball']) { // tarballs are not prefixed with / result[key] = snapshot; } else { result[`/${key}`] = snapshot; } } }); }); return result; } function remapDependencies(snapshot) { [ 'dependencies', 'optionalDependencies', 'devDependencies', 'peerDependencies', ].forEach((depType) => { if (snapshot[depType]) { for (const [packageName, version] of Object.entries(snapshot[depType])) { if (version.match(/^[a-zA-Z]+.*/)) { // remap packageName@version to packageName/version snapshot[depType][packageName] = `/${version.replace(/([a-zA-Z].+)@/, '$1/')}`; } } } }); } function findOriginalKeys(packages, { data: { packageName, version } }, lockfileVersion, { returnFullKey } = {}) { const matchedKeys = []; for (const key of Object.keys(packages)) { const snapshot = packages[key]; // tarball package if (key.startsWith(`${packageName}@${version}`) && snapshot.resolution?.['tarball']) { matchedKeys.push([getVersion(key, packageName), snapshot]); } // standard package if (lockfileVersion < 6 && key.startsWith(`${packageName}/${version}`)) { matchedKeys.push([ returnFullKey ? key : getVersion(key, packageName), snapshot, ]); } if (lockfileVersion >= 6 && lockfileVersion < 9 && key.startsWith(`${packageName}@${version}`)) { matchedKeys.push([ // we need to replace the @ with / for v5-7 syntax because the dpParse function expects old format returnFullKey ? key.replace(`${packageName}@${version}`, `${packageName}/${version}`) : getVersion(key, packageName), snapshot, ]); } if (lockfileVersion >= 9 && key.startsWith(`${packageName}@${version}`)) { matchedKeys.push([ returnFullKey ? key : getVersion(key, packageName), snapshot, ]); } // alias package if (versionIsAlias(key, version, lockfileVersion)) { if (lockfileVersion >= 9) { // no postprocessing needed for v9 matchedKeys.push([key, snapshot]); } else { // for root specifiers we need to ensure alias is prefixed with / const prefixedKey = returnFullKey ? key : `/${key}`; const mappedKey = prefixedKey.replace(/(\/?..+)@/, '$1/'); matchedKeys.push([mappedKey, snapshot]); } } } return matchedKeys; } // check if version has a form of npm:packageName@version and // key starts with /packageName/version function versionIsAlias(key, versionExpr, lockfileVersion) { const PREFIX = 'npm:'; if (!versionExpr.startsWith(PREFIX)) return false; const indexOfVersionSeparator = versionExpr.indexOf('@', PREFIX.length + 1); const packageName = versionExpr.slice(PREFIX.length, indexOfVersionSeparator); const version = versionExpr.slice(indexOfVersionSeparator + 1); return lockfileVersion < 6 ? key.startsWith(`${packageName}/${version}`) : key.startsWith(`${packageName}@${version}`); } function mapRootSnapshot(packageJson, packages, nodes, lockfileVersion) { const snapshot = { specifiers: {} }; [ 'dependencies', 'optionalDependencies', 'devDependencies', 'peerDependencies', ].forEach((depType) => { if (packageJson[depType]) { Object.keys(packageJson[depType]).forEach((packageName) => { const version = packageJson[depType][packageName]; const node = nodes[`npm:${packageName}@${version}`] || nodes[`npm:${packageName}`]; snapshot.specifiers[packageName] = version; // peer dependencies are mapped to dependencies let section = depType === 'peerDependencies' ? 'dependencies' : depType; snapshot[section] = snapshot[section] || {}; snapshot[section][packageName] = findOriginalKeys(packages, node, lockfileVersion)[0][0]; }); } }); Object.keys(snapshot).forEach((key) => { snapshot[key] = (0, object_sort_1.sortObjectByKeys)(snapshot[key]); }); return snapshot; } function findVersion(key, packageName, isV5, alias) { if (isV5 && key.startsWith(`${packageName}/`)) { return getVersion(key, packageName); } // this matches v6 syntax and tarball packages if (key.startsWith(`${packageName}@`)) { return getVersion(key, packageName); } if (alias) { const aliasName = isV5 ? key.slice(0, key.lastIndexOf('/')) : key.slice(0, key.indexOf('@', 2)); // we use 2 to ensure we don't catch the first @ const version = getVersion(key, aliasName); return `npm:${aliasName}@${version}`; } // for tarball package the entire key is the version spec return key; } function getVersion(key, packageName) { return key.slice(packageName.length + 1); } function extractNameFromKey(key, isV5) { // if package name contains org e.g. "@babel/runtime@7.12.5" if (key.startsWith('@')) { if (isV5) { const startFrom = key.indexOf('/'); return key.slice(0, key.indexOf('/', startFrom + 1)); } else { // find the position of the '@' return key.slice(0, key.indexOf('@', 1)); } } if (isV5) { // if package has just a name e.g. "react/7.12.5..." return key.slice(0, key.indexOf('/', 1)); } else { // if package has just a name e.g. "react@7.12.5..." return key.slice(0, key.indexOf('@', 1)); } }