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
498 lines (386 loc) • 16.5 kB
JavaScript
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
function _bluebird() {
const data = require("bluebird");
_bluebird = function () {
return data;
};
return data;
}
function _defineProperty2() {
const data = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
_defineProperty2 = function () {
return data;
};
return data;
}
function _ramda() {
const data = _interopRequireDefault(require("ramda"));
_ramda = function () {
return data;
};
return data;
}
function _bitId() {
const data = require("../../bit-id");
_bitId = function () {
return data;
};
return data;
}
function packageJsonUtils() {
const data = _interopRequireWildcard(require("../component/package-json-utils"));
packageJsonUtils = function () {
return data;
};
return data;
}
function _installPackages() {
const data = require("../../npm-client/install-packages");
_installPackages = function () {
return data;
};
return data;
}
function _logger() {
const data = _interopRequireDefault(require("../../logger/logger"));
_logger = function () {
return data;
};
return data;
}
function _defaultErrorHandler() {
const data = _interopRequireDefault(require("../../cli/default-error-handler"));
_defaultErrorHandler = function () {
return data;
};
return data;
}
function _scopeRemotes() {
const data = require("../../scope/scope-remotes");
_scopeRemotes = function () {
return data;
};
return data;
}
function _packageJsonFile() {
const data = _interopRequireDefault(require("../component/package-json-file"));
_packageJsonFile = function () {
return data;
};
return data;
}
function _componentVersion() {
const data = _interopRequireDefault(require("../../scope/component-version"));
_componentVersion = function () {
return data;
};
return data;
}
function _deleteComponentFiles() {
const data = _interopRequireDefault(require("./delete-component-files"));
_deleteComponentFiles = function () {
return data;
};
return data;
}
function _scopeGraph() {
const data = _interopRequireDefault(require("../../scope/graph/scope-graph"));
_scopeGraph = function () {
return data;
};
return data;
}
function _componentIdToPackageName() {
const data = _interopRequireDefault(require("../../utils/bit/component-id-to-package-name"));
_componentIdToPackageName = function () {
return data;
};
return data;
}
/**
* @flow
* a classic use case of eject is when a user imports a component using `bit import` to update it,
* but the user has no intention to have the code as part of the project source code.
* the eject provides the option to delete the component locally and install it via the NPM client.
*
* an implementation note, the entire process is done with rollback in mind.
* since installing the component via NPM client is an error prone process, we do it first, before
* removing the component files, so then it's easier to rollback.
*/
class EjectComponents {
// @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
// @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
// for rollback in case of errors
constructor(consumer, componentsIds, force) {
(0, _defineProperty2().default)(this, "consumer", void 0);
(0, _defineProperty2().default)(this, "componentsIds", void 0);
(0, _defineProperty2().default)(this, "force", void 0);
(0, _defineProperty2().default)(this, "componentsToEject", void 0);
(0, _defineProperty2().default)(this, "notEjectedDependents", void 0);
(0, _defineProperty2().default)(this, "failedComponents", void 0);
(0, _defineProperty2().default)(this, "packageJsonFilesBeforeChanges", void 0);
this.consumer = consumer;
this.componentsIds = componentsIds;
this.force = force || false;
this.componentsToEject = new (_bitId().BitIds)();
this.failedComponents = {
modifiedComponents: new (_bitId().BitIds)(),
stagedComponents: new (_bitId().BitIds)(),
notExportedComponents: new (_bitId().BitIds)(),
selfHostedExportedComponents: new (_bitId().BitIds)()
};
}
eject() {
var _this = this;
return (0, _bluebird().coroutine)(function* () {
yield _this.decideWhichComponentsToEject();
_logger().default.debugAndAddBreadCrumb('eject-components.eject', `${_this.componentsToEject.length} to eject`);
if (_this.componentsToEject.length) {
_this._validateIdsHaveScopesAndVersions();
yield _this.findNonEjectedDependents();
yield _this.loadPackageJsonFilesForPotentialRollBack();
yield _this.removeComponentsFromPackageJsonAndNodeModules();
yield _this.addComponentsAsPackagesToPackageJsonFiles();
yield _this.installPackagesUsingNPMClient();
yield _this.removeComponents();
}
_logger().default.debug('eject: completed successfully');
return {
ejectedComponents: _this.componentsToEject,
failedComponents: _this.failedComponents
};
})();
}
/**
* needed for update their package.json later with the dependencies version (instead of the
* relative paths)
*/
findNonEjectedDependents() {
var _this2 = this;
return (0, _bluebird().coroutine)(function* () {
// this also loads all non-nested components into the memory, so retrieving them later is at no cost
const graph = yield _scopeGraph().default.buildGraphFromCurrentlyUsedComponents(_this2.consumer);
const scopeGraph = new (_scopeGraph().default)(graph);
const notEjectedData = [];
_this2.componentsToEject.forEach(componentId => {
// @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
const dependents = scopeGraph.getImmediateDependentsPerId(componentId, true);
const notEjectedDependents = dependents.filter(d => !_this2.componentsToEject.hasWithoutScopeAndVersion(d));
notEjectedDependents.forEach(dependentId => {
const foundInNotEjectedData = notEjectedData.find(d => d.id.isEqual(dependentId));
if (foundInNotEjectedData) foundInNotEjectedData.dependencies.push(componentId);else notEjectedData.push({
id: dependentId,
dependencies: [componentId]
});
});
});
const notEjectedComponentsDataP = notEjectedData.map( /*#__PURE__*/function () {
var _ref = (0, _bluebird().coroutine)(function* (notEjectedItem) {
const dependent = yield _this2.consumer.loadComponent(notEjectedItem.id); // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
const {
components: ejectedDependencies
} = yield _this2.consumer.loadComponents(notEjectedItem.dependencies);
return {
dependent,
ejectedDependencies
};
});
return function (_x) {
return _ref.apply(this, arguments);
};
}());
const notEjectedComponentsData = yield Promise.all(notEjectedComponentsDataP);
_this2.notEjectedDependents = notEjectedComponentsData.filter(d => d.dependent.packageJsonFile);
})();
}
loadPackageJsonFilesForPotentialRollBack() {
var _this3 = this;
return (0, _bluebird().coroutine)(function* () {
const rootPackageJson = yield _packageJsonFile().default.load(_this3.consumer.getPath());
_this3.packageJsonFilesBeforeChanges = [rootPackageJson];
_this3.notEjectedDependents.forEach(({
dependent
}) => {
// $FlowFixMe notEjectedDependents has only dependents with packageJsonFile
// @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
_this3.packageJsonFilesBeforeChanges.push(dependent.packageJsonFile.clone());
});
})();
}
decideWhichComponentsToEject() {
var _this4 = this;
return (0, _bluebird().coroutine)(function* () {
_logger().default.debug('eject: getting the components status');
if (_ramda().default.isEmpty(_this4.componentsIds)) return;
const remotes = yield (0, _scopeRemotes().getScopeRemotes)(_this4.consumer.scope);
const hubExportedComponents = new (_bitId().BitIds)();
_this4.componentsIds.forEach(bitId => {
if (!bitId.hasScope()) _this4.failedComponents.notExportedComponents.push(bitId);else if (remotes.isHub(bitId.scope)) hubExportedComponents.push(bitId);else _this4.failedComponents.selfHostedExportedComponents.push(bitId);
});
if (_this4.force) {
_this4.componentsToEject = hubExportedComponents;
} else {
yield Promise.all(hubExportedComponents.map( /*#__PURE__*/function () {
var _ref2 = (0, _bluebird().coroutine)(function* (id) {
try {
const componentStatus = yield _this4.consumer.getComponentStatusById(id);
if (componentStatus.modified) _this4.failedComponents.modifiedComponents.push(id);else if (componentStatus.staged) _this4.failedComponents.stagedComponents.push(id);else _this4.componentsToEject.push(id);
} catch (err) {
_this4.throwEjectError(`eject operation failed getting the status of ${id.toString()}, no action has been done.
please fix the issue to continue.`, err);
}
});
return function (_x2) {
return _ref2.apply(this, arguments);
};
}()));
}
})();
}
removeComponentsFromPackageJsonAndNodeModules() {
var _this5 = this;
return (0, _bluebird().coroutine)(function* () {
const action = 'removing the existing components from package.json and node_modules';
try {
_logger().default.debugAndAddBreadCrumb('eject', action);
yield packageJsonUtils().removeComponentsFromWorkspacesAndDependencies(_this5.consumer, _this5.componentsToEject);
} catch (err) {
_logger().default.warn(`eject: failed ${action}, restoring package.json`);
yield _this5.rollBack(action);
_this5.throwEjectError(_this5._buildExceptionMessageWithRollbackData(action), err);
}
})();
}
addComponentsAsPackagesToPackageJsonFiles() {
var _this6 = this;
return (0, _bluebird().coroutine)(function* () {
const action = 'adding the components as packages into package.json';
try {
_logger().default.debugAndAddBreadCrumb('eject', action);
const componentsVersions = yield Promise.all(_this6.componentsToEject.map( /*#__PURE__*/function () {
var _ref3 = (0, _bluebird().coroutine)(function* (bitId) {
const modelComponent = yield _this6.consumer.scope.getModelComponent(bitId); // $FlowFixMe componentsToEject has scope and version, see @_validateIdsHaveScopesAndVersions
// @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
return new (_componentVersion().default)(modelComponent, bitId.version);
});
return function (_x3) {
return _ref3.apply(this, arguments);
};
}()));
yield packageJsonUtils().addComponentsWithVersionToRoot(_this6.consumer, componentsVersions);
_this6.notEjectedDependents.forEach(({
dependent,
ejectedDependencies
}) => {
const dependenciesToReplace = ejectedDependencies.reduce((acc, dependency) => {
const packageName = (0, _componentIdToPackageName().default)(dependency.id, dependency.bindingPrefix);
acc[packageName] = dependency.version;
return acc;
}, {}); // $FlowFixMe notEjectedDependents has only dependents with packageJsonFile
// @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
dependent.packageJsonFile.replaceDependencies(dependenciesToReplace);
}); // $FlowFixMe notEjectedDependents has only dependents with packageJsonFile
// @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
yield Promise.all(_this6.notEjectedDependents.map(({
dependent
}) => dependent.packageJsonFile.write()));
} catch (err) {
_logger().default.error(`eject: failed ${action}, restoring package.json`, err);
yield _this6.rollBack(action);
_this6.throwEjectError(_this6._buildExceptionMessageWithRollbackData(action), err);
}
})();
}
installPackagesUsingNPMClient() {
var _this7 = this;
return (0, _bluebird().coroutine)(function* () {
const action = 'installing the components using the NPM client';
try {
_logger().default.debugAndAddBreadCrumb('eject', action); // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
const dirs = _this7.notEjectedDependents // $FlowFixMe componentMap must be set for authored and imported
// @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
.map(({
dependent
}) => dependent.componentMap.rootDir).filter(x => x);
yield (0, _installPackages().installPackages)(_this7.consumer, dirs, true, true);
} catch (err) {
yield _this7.rollBack(action);
_this7.throwEjectError(_this7._buildExceptionMessageWithRollbackData(action), err);
}
})();
}
rollBack(action) {
var _this8 = this;
return (0, _bluebird().coroutine)(function* () {
yield Promise.all(_this8.packageJsonFilesBeforeChanges.map( /*#__PURE__*/function () {
var _ref4 = (0, _bluebird().coroutine)(function* (packageJsonFile) {
if (packageJsonFile.fileExist) {
_logger().default.warn(`eject: failed ${action}, restoring package.json at ${packageJsonFile.filePath}`);
yield packageJsonFile.write();
} else {
_logger().default.warn(`eject: failed ${action}, no package.json to restore at ${packageJsonFile.filePath}`);
}
});
return function (_x4) {
return _ref4.apply(this, arguments);
};
}()));
})();
}
_buildExceptionMessageWithRollbackData(action) {
return `eject failed ${action}.
your package.json (if existed) has been restored, however, some bit generated data may have been deleted, please run "bit link" to restore them.`;
}
removeComponents() {
var _this9 = this;
return (0, _bluebird().coroutine)(function* () {
try {
_logger().default.debug('eject: removing the components files from the filesystem');
yield _this9.removeLocalComponents();
} catch (err) {
_this9.throwEjectError(`eject operation has installed your components successfully using the NPM client.
however, it failed removing the old components from the filesystem.
please use bit remove command to remove them.`, err);
}
})();
}
/**
* as part of the 'eject' operation, a component is removed locally. as opposed to the remove
* command, in this case, no need to remove the objects from the scope, only remove from the
* filesystem, which means, delete the component files, untrack from .bitmap and clean
* package.json and bit.json traces.
*/
removeLocalComponents() {
var _this10 = this;
return (0, _bluebird().coroutine)(function* () {
// @todo: what about the dependencies? if they are getting deleted we have to make sure they
// not used anywhere else. Because this is part of the eject operation, the user probably
// gets the dependencies as npm packages so we don't need to worry much about have extra
// dependencies on the filesystem
yield (0, _deleteComponentFiles().default)(_this10.consumer, _this10.componentsToEject, true);
yield _this10.consumer.cleanFromBitMap(_this10.componentsToEject, new (_bitId().BitIds)());
})();
}
throwEjectError(message, originalError) {
const {
message: originalErrorMessage
} = (0, _defaultErrorHandler().default)(originalError);
_logger().default.error(`eject has stopped due to an error ${originalErrorMessage}`, originalError);
throw new Error(`${message}
got the following error: ${originalErrorMessage}`);
}
_validateIdsHaveScopesAndVersions() {
this.componentsToEject.forEach(id => {
if (!id.hasScope() || !id.hasVersion()) {
throw new TypeError(`EjectComponents expects ids with scope and version, got ${id.toString()}`);
}
});
}
}
exports.default = EjectComponents;
;