dependencies-hierarchy
Version:
Creates a dependencies hierarchy for a symlinked `node_modules`
314 lines • 12.5 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildDependenciesHierarchy = void 0;
const path_1 = __importDefault(require("path"));
const lockfile_file_1 = require("@pnpm/lockfile-file");
const lockfile_utils_1 = require("@pnpm/lockfile-utils");
const modules_yaml_1 = require("@pnpm/modules-yaml");
const normalize_registries_1 = require("@pnpm/normalize-registries");
const read_modules_dir_1 = require("@pnpm/read-modules-dir");
const read_package_json_1 = require("@pnpm/read-package-json");
const types_1 = require("@pnpm/types");
const dependency_path_1 = require("dependency-path");
const normalize_path_1 = __importDefault(require("normalize-path"));
const realpath_missing_1 = __importDefault(require("realpath-missing"));
const resolve_link_target_1 = __importDefault(require("resolve-link-target"));
async function buildDependenciesHierarchy(projectPaths, maybeOpts) {
if (!maybeOpts?.lockfileDir) {
throw new TypeError('opts.lockfileDir is required');
}
const modulesDir = await (0, realpath_missing_1.default)(path_1.default.join(maybeOpts.lockfileDir, 'node_modules'));
const modules = await (0, modules_yaml_1.readModulesManifest)(modulesDir);
const registries = (0, normalize_registries_1.normalizeRegistries)({
...maybeOpts?.registries,
...modules?.registries,
});
const currentLockfile = (modules?.virtualStoreDir && await (0, lockfile_file_1.readCurrentLockfile)(modules.virtualStoreDir, { ignoreIncompatible: false })) ?? null;
const result = {};
if (!currentLockfile) {
for (const projectPath of projectPaths) {
result[projectPath] = {};
}
return result;
}
const opts = {
depth: maybeOpts.depth || 0,
include: maybeOpts.include ?? {
dependencies: true,
devDependencies: true,
optionalDependencies: true,
},
lockfileDir: maybeOpts.lockfileDir,
registries,
search: maybeOpts.search,
skipped: new Set(modules?.skipped ?? []),
};
(await Promise.all(projectPaths.map(async (projectPath) => {
return [
projectPath,
await dependenciesHierarchyForPackage(projectPath, currentLockfile, opts),
];
}))).forEach(([projectPath, dependenciesHierarchy]) => {
result[projectPath] = dependenciesHierarchy;
});
return result;
}
exports.buildDependenciesHierarchy = buildDependenciesHierarchy;
async function dependenciesHierarchyForPackage(projectPath, currentLockfile, opts) {
const importerId = (0, lockfile_file_1.getLockfileImporterId)(opts.lockfileDir, projectPath);
if (!currentLockfile.importers[importerId])
return {};
const modulesDir = path_1.default.join(projectPath, 'node_modules');
const savedDeps = getAllDirectDependencies(currentLockfile.importers[importerId]);
const allDirectDeps = await (0, read_modules_dir_1.readModulesDir)(modulesDir) ?? [];
const unsavedDeps = allDirectDeps.filter((directDep) => !savedDeps[directDep]);
const wantedLockfile = await (0, lockfile_file_1.readWantedLockfile)(opts.lockfileDir, { ignoreIncompatible: false }) ?? { packages: {} };
const getChildrenTree = getTree.bind(null, {
currentDepth: 1,
currentPackages: currentLockfile.packages ?? {},
includeOptionalDependencies: opts.include.optionalDependencies,
lockfileDir: opts.lockfileDir,
maxDepth: opts.depth,
modulesDir,
registries: opts.registries,
search: opts.search,
skipped: opts.skipped,
wantedPackages: wantedLockfile.packages ?? {},
});
const result = {};
for (const dependenciesField of types_1.DEPENDENCIES_FIELDS.sort().filter(dependenciedField => opts.include[dependenciedField])) {
const topDeps = currentLockfile.importers[importerId][dependenciesField] ?? {};
result[dependenciesField] = [];
Object.entries(topDeps).forEach(([alias, ref]) => {
const { packageInfo, packageAbsolutePath } = getPkgInfo({
alias,
currentPackages: currentLockfile.packages ?? {},
lockfileDir: opts.lockfileDir,
modulesDir,
ref,
registries: opts.registries,
skipped: opts.skipped,
wantedPackages: wantedLockfile.packages ?? {},
});
let newEntry = null;
const matchedSearched = opts.search?.(packageInfo);
if (packageAbsolutePath === null) {
if ((opts.search != null) && !matchedSearched)
return;
newEntry = packageInfo;
}
else {
const relativeId = (0, dependency_path_1.refToRelative)(ref, alias);
if (relativeId) {
const dependencies = getChildrenTree([relativeId], relativeId);
if (dependencies.length > 0) {
newEntry = {
...packageInfo,
dependencies,
};
}
else if ((opts.search == null) || matchedSearched) {
newEntry = packageInfo;
}
}
}
if (newEntry != null) {
if (matchedSearched) {
newEntry.searched = true;
}
result[dependenciesField].push(newEntry);
}
});
}
await Promise.all(unsavedDeps.map(async (unsavedDep) => {
let pkgPath = path_1.default.join(modulesDir, unsavedDep);
let version;
try {
pkgPath = await (0, resolve_link_target_1.default)(pkgPath);
version = `link:${(0, normalize_path_1.default)(path_1.default.relative(projectPath, pkgPath))}`;
}
catch (err) { // eslint-disable-line
// if error happened. The package is not a link
const pkg = await (0, read_package_json_1.safeReadPackageJsonFromDir)(pkgPath);
version = pkg?.version ?? 'undefined';
}
const pkg = {
alias: unsavedDep,
isMissing: false,
isPeer: false,
isSkipped: false,
name: unsavedDep,
path: pkgPath,
version,
};
const matchedSearched = opts.search?.(pkg);
if ((opts.search != null) && !matchedSearched)
return;
const newEntry = pkg;
if (matchedSearched) {
newEntry.searched = true;
}
result.unsavedDependencies = result.unsavedDependencies ?? [];
result.unsavedDependencies.push(newEntry);
}));
return result;
}
function getAllDirectDependencies(projectSnapshot) {
return {
...projectSnapshot.dependencies,
...projectSnapshot.devDependencies,
...projectSnapshot.optionalDependencies,
};
}
function getTree(opts, keypath, parentId) {
const dependenciesCache = new Map();
return getTreeHelper(dependenciesCache, opts, keypath, parentId).dependencies;
}
function getTreeHelper(dependenciesCache, opts, keypath, parentId) {
const result = { dependencies: [] };
if (opts.currentDepth > opts.maxDepth || !opts.currentPackages || !opts.currentPackages[parentId])
return result;
const deps = !opts.includeOptionalDependencies
? opts.currentPackages[parentId].dependencies
: {
...opts.currentPackages[parentId].dependencies,
...opts.currentPackages[parentId].optionalDependencies,
};
if (deps == null)
return result;
const getChildrenTree = getTreeHelper.bind(null, dependenciesCache, {
...opts,
currentDepth: opts.currentDepth + 1,
});
const peers = new Set(Object.keys(opts.currentPackages[parentId].peerDependencies ?? {}));
Object.entries(deps).forEach(([alias, ref]) => {
const { packageInfo, packageAbsolutePath } = getPkgInfo({
alias,
currentPackages: opts.currentPackages,
lockfileDir: opts.lockfileDir,
modulesDir: opts.modulesDir,
peers,
ref,
registries: opts.registries,
skipped: opts.skipped,
wantedPackages: opts.wantedPackages,
});
let circular;
const matchedSearched = opts.search?.(packageInfo);
let newEntry = null;
if (packageAbsolutePath === null) {
circular = false;
if (opts.search == null || matchedSearched) {
newEntry = packageInfo;
}
}
else {
let dependencies;
const relativeId = (0, dependency_path_1.refToRelative)(ref, alias); // we know for sure that relative is not null if pkgPath is not null
circular = keypath.includes(relativeId);
if (circular) {
dependencies = [];
}
else {
dependencies = dependenciesCache.get(packageAbsolutePath);
if (dependencies == null) {
const children = getChildrenTree(keypath.concat([relativeId]), relativeId);
dependencies = children.dependencies;
if (children.circular) {
result.circular = true;
}
else {
dependenciesCache.set(packageAbsolutePath, dependencies);
}
}
}
if (dependencies.length > 0) {
newEntry = {
...packageInfo,
dependencies,
};
}
else if ((opts.search == null) || matchedSearched) {
newEntry = packageInfo;
}
}
if (newEntry != null) {
if (circular) {
newEntry.circular = true;
result.circular = true;
}
if (matchedSearched) {
newEntry.searched = true;
}
result.dependencies.push(newEntry);
}
});
return result;
}
function getPkgInfo(opts) {
let name;
let version;
let resolved;
let dev;
let optional;
let isSkipped = false;
let isMissing = false;
const depPath = (0, dependency_path_1.refToRelative)(opts.ref, opts.alias);
if (depPath) {
let pkgSnapshot;
if (opts.currentPackages[depPath]) {
pkgSnapshot = opts.currentPackages[depPath];
const parsed = (0, lockfile_utils_1.nameVerFromPkgSnapshot)(depPath, pkgSnapshot);
name = parsed.name;
version = parsed.version;
}
else {
pkgSnapshot = opts.wantedPackages[depPath];
if (pkgSnapshot) {
const parsed = (0, lockfile_utils_1.nameVerFromPkgSnapshot)(depPath, pkgSnapshot);
name = parsed.name;
version = parsed.version;
}
else {
name = opts.alias;
version = opts.ref;
}
isMissing = true;
isSkipped = opts.skipped.has(depPath);
}
resolved = (0, lockfile_utils_1.pkgSnapshotToResolution)(depPath, pkgSnapshot, opts.registries)['tarball'];
dev = pkgSnapshot.dev;
optional = pkgSnapshot.optional;
}
else {
name = opts.alias;
version = opts.ref;
}
const packageAbsolutePath = (0, dependency_path_1.refToRelative)(opts.ref, opts.alias);
const packageInfo = {
alias: opts.alias,
isMissing,
isPeer: Boolean(opts.peers?.has(opts.alias)),
isSkipped,
name,
path: depPath ? path_1.default.join(opts.modulesDir, '.pnpm', (0, dependency_path_1.depPathToFilename)(depPath)) : path_1.default.join(opts.modulesDir, '..', opts.ref.slice(5)),
version,
};
if (resolved) {
packageInfo['resolved'] = resolved;
}
if (optional === true) {
packageInfo['optional'] = true;
}
if (typeof dev === 'boolean') {
packageInfo['dev'] = dev;
}
return {
packageAbsolutePath,
packageInfo,
};
}
//# sourceMappingURL=index.js.map