bit-bin
Version:
<a href="https://opensource.org/licenses/Apache-2.0"><img alt="apache" src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"></a> <a href="https://github.com/teambit/bit/blob/master/CONTRIBUTING.md"><img alt="prs" src="https://img.shields.io/b
679 lines (558 loc) • 22.9 kB
JavaScript
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.resolveNodePackage = resolveNodePackage;
exports.resolveModulePath = resolveModulePath;
exports.getDependencyTree = getDependencyTree;
function _bluebird() {
const data = require("bluebird");
_bluebird = function () {
return data;
};
return data;
}
function _fs() {
const data = _interopRequireDefault(require("fs"));
_fs = function () {
return data;
};
return data;
}
function _path() {
const data = _interopRequireDefault(require("path"));
_path = function () {
return data;
};
return data;
}
function _ramda() {
const data = _interopRequireDefault(require("ramda"));
_ramda = function () {
return data;
};
return data;
}
function _lodash() {
const data = require("lodash");
_lodash = function () {
return data;
};
return data;
}
function _generateTreeMadge() {
const data = _interopRequireWildcard(require("./generate-tree-madge"));
_generateTreeMadge = function () {
return data;
};
return data;
}
function _constants() {
const data = require("../../../../constants");
_constants = function () {
return data;
};
return data;
}
function _pathMap() {
const data = require("./path-map");
_pathMap = function () {
return data;
};
return data;
}
function _packageJson() {
const data = _interopRequireDefault(require("../../package-json"));
_packageJson = function () {
return data;
};
return data;
}
function _bitId() {
const data = require("../../../../bit-id");
_bitId = function () {
return data;
};
return data;
}
// @flow
// TODO: This should be exported as a bit component
/**
* Group dependencies by types (files, bits, packages)
* @param {any} dependencies list of dependencies paths to group
* @returns {Function} function which group the dependencies
*/
const byType = (list, bindingPrefix) => {
const grouped = _ramda().default.groupBy(item => {
if (item.includes(`node_modules/${bindingPrefix}`) || item.includes(`node_modules/${_constants().DEFAULT_BINDINGS_PREFIX}`)) {
return 'bits';
}
return item.includes('node_modules') ? 'packages' : 'files';
});
return grouped(list);
};
/**
* Get a path to node package and return the name and version
*
* @param {any} packageFullPath full path to the package
* @returns {Object} name and version of the package
*/
function resolveNodePackage(cwd, packageFullPath) {
const NODE_MODULES = 'node_modules';
const result = {
fullPath: packageFullPath,
name: '',
componentId: undefined
}; // Start by searching in the component dir and up from there
// If not found search in package dir itself.
// We are doing this, because the package.json inside the package dir contain exact version
// And the component/consumer package.json might contain semver like ^ or ~
// We want to have this semver as dependency and not the exact version, otherwise it will be considered as modified all the time
// @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
const packageJsonInfo = _packageJson().default.findPackage(cwd);
if (packageJsonInfo) {
// The +1 is for the / after the node_modules, we didn't enter it into the NODE_MODULES const because it makes problems on windows
const packageRelativePath = packageFullPath.substring(packageFullPath.lastIndexOf(NODE_MODULES) + NODE_MODULES.length + 1, packageFullPath.length);
const packageName = resolvePackageNameByPath(packageRelativePath);
const packageNameNormalized = packageName.replace('\\', '/');
const packageVersion = _ramda().default.path(['dependencies', packageNameNormalized], packageJsonInfo) || _ramda().default.path(['devDependencies', packageNameNormalized], packageJsonInfo) || _ramda().default.path(['peerDependencies', packageNameNormalized], packageJsonInfo);
if (packageVersion) {
result.name = packageNameNormalized;
result.versionUsedByDependent = packageVersion;
}
} // Get the package relative path to the node_modules dir
const packageDir = resolvePackageDirFromFilePath(packageFullPath); // don't propagate here since loading a package.json of another folder and taking the version from it will result wrong version
// This for example happen in the following case:
// if you have 2 authored component which one dependent on the other
// we will look for the package.json on the dependency but won't find it
// if we propagate we will take the version from the root's package json which has nothing with the component version
// @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
const packageInfo = _packageJson().default.loadSync(packageDir); // when running 'bitjs get-dependencies' command, packageInfo is sometimes empty
// or when using custom-module-resolution it may be empty or the name/version are empty
if (!packageInfo || !packageInfo.name || !packageInfo.version) {
if (!result.name) {
return undefined;
}
return result;
}
result.name = packageInfo.name;
result.concreteVersion = packageInfo.version;
if (packageInfo.componentId) {
result.componentId = new (_bitId().BitId)(packageInfo.componentId);
}
return result;
}
/**
* given the full path of a package file, returns the root dir of the package, so then we could
* find the package.json in that directory.
*
* example of a normal package:
* absolutePackageFilePath: /user/workspace/node_modules/lodash.isboolean/index.js
* returns: /user/workspace/node_modules/lodash.isboolean
*
* example of a scoped package:
* absolutePackageFilePath: /user/workspace/node_modules/@babel/core/lib/index.js
* returns: /user/workspace/node_modules/@babel/core
*/
function resolvePackageDirFromFilePath(absolutePackageFilePath) {
const NODE_MODULES = 'node_modules';
const indexOfLastNodeModules = absolutePackageFilePath.lastIndexOf(NODE_MODULES) + NODE_MODULES.length + 1;
const pathInsideNodeModules = absolutePackageFilePath.substring(indexOfLastNodeModules);
const packageName = resolvePackageNameByPath(pathInsideNodeModules);
const pathUntilNodeModules = absolutePackageFilePath.substring(0, indexOfLastNodeModules);
return pathUntilNodeModules + packageName;
}
/**
* Gets a list of dependencies and group them by types (files, bits, packages)
* It's also transform the node package dependencies from array of paths to object in this format:
* {dependencyName: version} (like in package.json)
*
* @param {any} list of dependencies paths
* @param {any} cwd root of working directory (used for node packages version calculation)
* @returns {Object} object with the dependencies groups
*/
function groupDependencyList(list, cwd, bindingPrefix) {
const groups = byType(list, bindingPrefix);
const resultGroups = {
bits: [],
packages: {},
files: groups.files,
unidentifiedPackages: []
};
const unidentifiedPackages = [];
if (groups.packages) {
const packages = {};
groups.packages.forEach(packagePath => {
const resolvedPackage = resolveNodePackage(cwd, _path().default.join(cwd, packagePath)); // If the package is actually a component add it to the components (bits) list
if (resolvedPackage) {
if (resolvedPackage.componentId) {
resultGroups.bits.push(resolvedPackage);
} else {
const version = resolvedPackage.versionUsedByDependent || resolvedPackage.concreteVersion;
if (version) {
const packageWithVersion = {
[resolvedPackage.name]: version
};
Object.assign(packages, packageWithVersion);
}
}
} else unidentifiedPackages.push(packagePath);
});
resultGroups.packages = packages;
}
if (groups.bits) {
groups.bits.forEach(packagePath => {
const resolvedPackage = resolveNodePackage(cwd, _path().default.join(cwd, packagePath)); // If the package is actually a component add it to the components (bits) list
if (resolvedPackage) {
resultGroups.bits.push(resolvedPackage);
} else {
unidentifiedPackages.push(packagePath);
}
});
}
if (!_ramda().default.isEmpty(unidentifiedPackages)) {
resultGroups.unidentifiedPackages = unidentifiedPackages;
}
return resultGroups;
}
/**
* Run over each entry in the tree and transform the dependencies from list of paths
* to object with dependencies types
*
* @param {any} tree
* @param {any} cwd the working directory path
* @returns new tree with grouped dependencies
*/
function groupDependencyTree(tree, cwd, bindingPrefix) {
const result = {};
Object.keys(tree).forEach(key => {
if (tree[key] && !_ramda().default.isEmpty(tree[key])) {
result[key] = groupDependencyList(tree[key], cwd, bindingPrefix);
} else {
result[key] = {};
}
});
return result;
}
/**
* return the package name by the import statement path to node package
*
* @param {string} packagePath import statement path
* @returns {string} name of the package
*/
function resolvePackageNameByPath(packagePath) {
const packagePathArr = packagePath.split(_path().default.sep); // TODO: make sure this is working on windows
// Regular package without path. example - import _ from 'lodash'
if (packagePathArr.length === 1) return packagePath; // Scoped package. example - import getSymbolIterator from '@angular/core/src/util.d.ts';
if (packagePathArr[0].startsWith('@')) return _path().default.join(packagePathArr[0], packagePathArr[1]); // Regular package with internal path. example import something from 'mypackage/src/util/isString'
return packagePathArr[0];
}
/**
* Recursively search for node module inside node_modules dir
* This function propagate up until it gets to the root provided then stops
*
* @param {string} nmPath - package name
* @param {string} workingDir - dir to start searching of
* @param {string} root - path to dir to stop the search
* @returns The resolved path for the package directory
*/
function resolveModulePath(nmPath, workingDir, root) {
const pathToCheck = _path().default.resolve(workingDir, 'node_modules', nmPath);
if (_fs().default.existsSync(pathToCheck)) {
return pathToCheck;
}
if (workingDir === root) {
return undefined;
}
const parentWorkingDir = _path().default.dirname(workingDir);
if (parentWorkingDir === workingDir) return undefined;
return resolveModulePath(nmPath, parentWorkingDir, root);
}
/**
* Resolve package dependencies from package.json according to package names
*
* @param {Object} packageJson
* @param {string []} packagesNames
* @returns new object with found and missing
*/
function findPackagesInPackageJson(packageJson, packagesNames) {
const {
dependencies,
devDependencies,
peerDependencies
} = packageJson;
const foundPackages = {};
const mergedDependencies = Object.assign({}, dependencies, devDependencies, peerDependencies);
if (packagesNames && packagesNames.length && !_ramda().default.isNil(mergedDependencies)) {
const [foundPackagesPartition, missingPackages] = (0, _lodash().partition)(packagesNames, item => item in mergedDependencies);
foundPackagesPartition.forEach(pack => foundPackages[pack] = mergedDependencies[pack]);
return {
foundPackages,
missingPackages
};
}
return {
foundPackages: {},
missingPackages: packagesNames
};
}
/**
* Run over each entry in the missing array and transform the missing from list of paths
* to object with missing types
*
* @param {Array} missing
* @param {string} cwd
* @param {string} workspacePath
* @param {string} bindingPrefix
* @returns new object with grouped missing
*/
function groupMissing(missing, cwd, workspacePath, bindingPrefix) {
// temporarily disable this functionality since it cause few bugs: explanation below (on using the packageJson)
// const packageJson = PackageJson.findPackage(cwd);
/**
* Group missing dependencies by types (files, bits, packages)
* @param {Array} missing list of missing paths to group
* @returns {Function} function which group the dependencies
*/
const byPathType = _ramda().default.groupBy(item => {
if (item.startsWith(`${bindingPrefix}/`) || item.startsWith(`${_constants().DEFAULT_BINDINGS_PREFIX}/`)) return 'bits';
return item.startsWith('.') ? 'files' : 'packages';
});
const groups = Object.keys(missing).map(key => Object.assign({
originFile: (0, _generateTreeMadge().processPath)(key, {}, cwd)
}, byPathType(missing[key], bindingPrefix)));
groups.forEach(group => {
if (group.packages) group.packages = group.packages.map(resolvePackageNameByPath);
if (group.bits) group.bits = group.bits.map(resolvePackageNameByPath);
}); // This is a hack to solve problems that madge has with packages for type script files
// It see them as missing even if they are exists
const foundPackages = {
packages: {},
bits: []
};
const packageJson = _packageJson().default.findPackage(cwd);
groups.forEach(group => {
const missingPackages = [];
if (group.packages) {
group.packages.forEach(packageName => {
// Don't try to resolve the same package twice
if (_ramda().default.contains(packageName, missingPackages)) return;
const resolvedPath = resolveModulePath(packageName, cwd, workspacePath);
if (!resolvedPath) {
missingPackages.push(packageName);
return;
}
const resolvedPackage = resolveNodePackage(cwd, resolvedPath); // If the package is actually a component add it to the components (bits) list
if (resolvedPackage) {
if (resolvedPackage.componentId) {
foundPackages.bits.push(resolvedPackage);
} else {
const version = resolvedPackage.versionUsedByDependent || resolvedPackage.concreteVersion;
if (version) {
const packageWithVersion = {
[resolvedPackage.name]: version
};
Object.assign(foundPackages.packages, packageWithVersion);
}
}
} else {
missingPackages.push(packageName);
}
});
} // this was disabled since it cause these bugs:
// (as part of 9ddeb61aa29c170cd58df0c2cc1cc30db1ebded8 of bit-javascript)
// https://github.com/teambit/bit/issues/635
// https://github.com/teambit/bit/issues/690
// later it re-enabled by this commit (d192a295632255dba9f0d62232fb237feeb8f33a of bit-javascript)
// we should think if we really want it
if (packageJson) {
const result = findPackagesInPackageJson(packageJson, missingPackages); // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
groups.packages = result.missingPackages;
Object.assign(foundPackages.packages, result.foundPackages);
if (group.bits) {
const foundBits = findPackagesInPackageJson(packageJson, group.bits);
_ramda().default.forEachObjIndexed((version, name) => {
const resolvedFoundBit = {
name,
versionUsedByDependent: version
};
foundPackages.bits.push(resolvedFoundBit);
}, foundBits.foundPackages);
}
}
});
return {
missingGroups: groups,
foundPackages
};
}
/**
* add extra data such as custom-resolve and link-files from pathMap
*/
function updateTreeWithPathMap(tree, pathMapAbsolute, baseDir) {
if (!pathMapAbsolute.length) return;
const pathMapRelative = (0, _pathMap().convertPathMapToRelativePaths)(pathMapAbsolute, baseDir);
const pathMap = (0, _pathMap().getPathMapWithLinkFilesData)(pathMapRelative);
Object.keys(tree).forEach(filePath => {
const treeFiles = tree[filePath].files;
if (!treeFiles || !treeFiles.length) return; // file has no dependency
const mainFilePathMap = pathMap.find(file => file.file === filePath);
if (!mainFilePathMap) throw new Error(`updateTreeWithPathMap: PathMap is missing for ${filePath}`); // a file might have a cycle dependency with itself, remove it from the dependencies.
const files = treeFiles // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
.filter(dependency => dependency !== filePath) // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
.map(dependency => {
const dependencyPathMap = mainFilePathMap.dependencies.find(file => file.resolvedDep === dependency);
if (!dependencyPathMap) throw new Error(`updateTreeWithPathMap: dependencyPathMap is missing for ${dependency}`);
const fileObject = {
file: dependency,
importSource: dependencyPathMap.importSource,
isCustomResolveUsed: dependencyPathMap.isCustomResolveUsed
};
if (dependencyPathMap.linkFile) {
fileObject.isLink = true;
fileObject.linkDependencies = dependencyPathMap.realDependencies;
return fileObject;
}
if (dependencyPathMap.importSpecifiers && dependencyPathMap.importSpecifiers.length) {
const depImportSpecifiers = dependencyPathMap.importSpecifiers.map(importSpecifier => {
return {
mainFile: importSpecifier
};
});
fileObject.importSpecifiers = depImportSpecifiers;
}
return fileObject;
});
tree[filePath].files = files; // eslint-disable-line no-param-reassign
});
}
/**
* config aliases are passed later on to webpack-enhancer and it expects them to have the full path
*/
function getResolveConfigAbsolute(workspacePath, resolveConfig) {
if (!resolveConfig) return resolveConfig;
const resolveConfigAbsolute = _ramda().default.clone(resolveConfig);
if (resolveConfig.modulesDirectories) {
resolveConfigAbsolute.modulesDirectories = resolveConfig.modulesDirectories.map(moduleDirectory => {
return _path().default.isAbsolute(moduleDirectory) ? moduleDirectory : _path().default.join(workspacePath, moduleDirectory);
});
}
if (resolveConfigAbsolute.aliases) {
Object.keys(resolveConfigAbsolute.aliases).forEach(alias => {
if (!_path().default.isAbsolute(resolveConfigAbsolute.aliases[alias])) {
resolveConfigAbsolute.aliases[alias] = _path().default.join(workspacePath, resolveConfigAbsolute.aliases[alias]);
}
});
}
return resolveConfigAbsolute;
}
function mergeManuallyFoundPackagesToTree(foundPackages, missingGroups, tree) {
if (_ramda().default.isEmpty(foundPackages.bits) && _ramda().default.isEmpty(foundPackages.packages)) return; // Merge manually found packages (by groupMissing()) with the packages found by Madge (generate-tree-madge)
Object.keys(foundPackages.packages).forEach(pkg => {
// locate package in groups(contains missing)
missingGroups.forEach(fileDep => {
if (fileDep.packages && fileDep.packages.includes(pkg)) {
fileDep.packages = fileDep.packages.filter(packageName => packageName !== pkg);
(0, _lodash().set)(tree[fileDep.originFile], ['packages', pkg], foundPackages.packages[pkg]);
}
if (fileDep.bits && fileDep.bits.includes(pkg)) {
fileDep.bits = fileDep.bits.filter(packageName => packageName !== pkg);
if (!tree[fileDep.originFile]) tree[fileDep.originFile] = {};
if (!tree[fileDep.originFile].bits) tree[fileDep.originFile].bits = []; // @ts-ignore
tree[fileDep.originFile].bits.push(pkg);
}
});
});
foundPackages.bits.forEach(component => {
missingGroups.forEach(fileDep => {
if (fileDep.bits && (component.fullPath && fileDep.bits.includes(component.fullPath) || fileDep.bits.includes(component.name))) {
fileDep.bits = fileDep.bits.filter(existComponent => {
return existComponent !== component.fullPath && existComponent !== component.name;
});
if (!tree[fileDep.originFile]) tree[fileDep.originFile] = {};
if (!tree[fileDep.originFile].bits) tree[fileDep.originFile].bits = []; // @ts-ignore
tree[fileDep.originFile].bits.push(component);
}
});
});
}
function mergeMissingToTree(missingGroups, tree) {
if (_ramda().default.isEmpty(missingGroups)) return;
missingGroups.forEach(missing => {
const missingCloned = _ramda().default.clone(missing);
delete missingCloned.originFile;
if (tree[missing.originFile]) tree[missing.originFile].missing = missingCloned;else tree[missing.originFile] = {
missing: missingCloned
};
});
}
function mergeErrorsToTree(baseDir, errors, tree) {
if (_ramda().default.isEmpty(errors)) return;
Object.keys(errors).forEach(file => {
if (tree[file]) tree[file].error = errors[file];else tree[file] = {
error: errors[file]
};
});
}
/**
* Function for fetching dependency tree of file or dir
* @param baseDir working directory
* @param workspacePath
* @param filePaths path of the file to calculate the dependencies
* @param bindingPrefix
* @return {Promise<{missing, tree}>}
*/
function getDependencyTree(_x) {
return _getDependencyTree.apply(this, arguments);
}
function _getDependencyTree() {
_getDependencyTree = (0, _bluebird().coroutine)(function* ({
baseDir,
workspacePath,
filePaths,
bindingPrefix,
resolveModulesConfig,
visited = {},
cacheProjectAst
}) {
const resolveConfigAbsolute = getResolveConfigAbsolute(workspacePath, resolveModulesConfig);
const config = {
baseDir,
includeNpm: true,
requireConfig: null,
webpackConfig: null,
visited,
nonExistent: [],
resolveConfig: resolveConfigAbsolute,
cacheProjectAst
}; // This is important because without this, madge won't know to resolve files if we run the
// CMD not from the root dir
const fullPaths = filePaths.map(filePath => {
if (filePath.startsWith(baseDir)) {
return filePath;
}
return _path().default.resolve(baseDir, filePath);
}); // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
const {
madgeTree,
skipped,
pathMap,
errors
} = (0, _generateTreeMadge().default)(fullPaths, config); // @ts-ignore
const tree = groupDependencyTree(madgeTree, baseDir, bindingPrefix);
const {
missingGroups,
foundPackages
} = groupMissing(skipped, baseDir, workspacePath, bindingPrefix);
if (foundPackages) mergeManuallyFoundPackagesToTree(foundPackages, missingGroups, tree);
if (errors) mergeErrorsToTree(baseDir, errors, tree);
if (missingGroups) mergeMissingToTree(missingGroups, tree);
if (pathMap) updateTreeWithPathMap(tree, pathMap, baseDir);
return {
tree
};
});
return _getDependencyTree.apply(this, arguments);
}
;