UNPKG

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
"use strict"; 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); }