@teambit/workspace
Version:
1,314 lines (1,278 loc) • 101 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.Workspace = exports.ComponentRemoved = exports.ComponentChanged = exports.ComponentAdded = exports.AspectSpecificField = void 0;
function _commentJson() {
const data = require("comment-json");
_commentJson = function () {
return data;
};
return data;
}
function _pMapSeries() {
const data = _interopRequireDefault(require("p-map-series"));
_pMapSeries = function () {
return data;
};
return data;
}
function _pMap() {
const data = _interopRequireDefault(require("p-map"));
_pMap = function () {
return data;
};
return data;
}
function _graph() {
const data = require("@teambit/graph.cleargraph");
_graph = function () {
return data;
};
return data;
}
function _dependenciesModules() {
const data = require("@teambit/dependencies.modules.packages-excluder");
_dependenciesModules = function () {
return data;
};
return data;
}
function _component() {
const data = require("@teambit/component");
_component = function () {
return data;
};
return data;
}
function _bitError() {
const data = require("@teambit/bit-error");
_bitError = function () {
return data;
};
return data;
}
function _dependencyResolver() {
const data = require("@teambit/dependency-resolver");
_dependencyResolver = function () {
return data;
};
return data;
}
function _envs() {
const data = require("@teambit/envs");
_envs = function () {
return data;
};
return data;
}
function _workspaceModules() {
const data = require("@teambit/workspace.modules.match-pattern");
_workspaceModules = function () {
return data;
};
return data;
}
function _componentId() {
const data = require("@teambit/component-id");
_componentId = function () {
return data;
};
return data;
}
function _legacyBitId() {
const data = require("@teambit/legacy-bit-id");
_legacyBitId = function () {
return data;
};
return data;
}
function _legacy() {
const data = require("@teambit/legacy.consumer");
_legacy = function () {
return data;
};
return data;
}
function _legacy2() {
const data = require("@teambit/legacy.bit-map");
_legacy2 = function () {
return data;
};
return data;
}
function _harmonyModules() {
const data = require("@teambit/harmony.modules.in-memory-cache");
_harmonyModules = function () {
return data;
};
return data;
}
function _legacy3() {
const data = require("@teambit/legacy.component-list");
_legacy3 = function () {
return data;
};
return data;
}
function _legacy4() {
const data = require("@teambit/legacy.extension-data");
_legacy4 = function () {
return data;
};
return data;
}
function _toolboxPath() {
const data = require("@teambit/toolbox.path.path");
_toolboxPath = function () {
return data;
};
return data;
}
function _toolboxPath2() {
const data = require("@teambit/toolbox.path.is-path-inside");
_toolboxPath2 = function () {
return data;
};
return data;
}
function _fsExtra() {
const data = _interopRequireDefault(require("fs-extra"));
_fsExtra = function () {
return data;
};
return data;
}
function _lodash() {
const data = require("lodash");
_lodash = function () {
return data;
};
return data;
}
function _legacy5() {
const data = require("@teambit/legacy.constants");
_legacy5 = function () {
return data;
};
return data;
}
function _path() {
const data = _interopRequireDefault(require("path"));
_path = function () {
return data;
};
return data;
}
function _legacy6() {
const data = require("@teambit/legacy.consumer-component");
_legacy6 = function () {
return data;
};
return data;
}
function _component2() {
const data = require("@teambit/component.sources");
_component2 = function () {
return data;
};
return data;
}
function _legacy7() {
const data = require("@teambit/legacy.scope");
_legacy7 = function () {
return data;
};
return data;
}
function _legacy8() {
const data = require("@teambit/legacy.scope-api");
_legacy8 = function () {
return data;
};
return data;
}
function _scope() {
const data = require("@teambit/scope.remotes");
_scope = function () {
return data;
};
return data;
}
function _componentVersion() {
const data = require("@teambit/component-version");
_componentVersion = function () {
return data;
};
return data;
}
function _componentConfigFile() {
const data = require("./component-config-file");
_componentConfigFile = function () {
return data;
};
return data;
}
function _componentStatus() {
const data = require("./workspace-component/component-status");
_componentStatus = function () {
return data;
};
return data;
}
function _workspaceComponentLoader() {
const data = require("./workspace-component/workspace-component-loader");
_workspaceComponentLoader = function () {
return data;
};
return data;
}
function _buildGraphFromFs() {
const data = require("./build-graph-from-fs");
_buildGraphFromFs = function () {
return data;
};
return data;
}
function _bitMap() {
const data = require("./bit-map");
_bitMap = function () {
return data;
};
return data;
}
function _workspace() {
const data = require("./workspace.aspect");
_workspace = function () {
return data;
};
return data;
}
function _buildGraphIdsFromFs() {
const data = require("./build-graph-ids-from-fs");
_buildGraphIdsFromFs = function () {
return data;
};
return data;
}
function _aspectsMerger() {
const data = require("./aspects-merger");
_aspectsMerger = function () {
return data;
};
return data;
}
function _workspaceAspectsLoader() {
const data = require("./workspace-aspects-loader");
_workspaceAspectsLoader = function () {
return data;
};
return data;
}
function _mergeConfigConflict() {
const data = require("./exceptions/merge-config-conflict");
_mergeConfigConflict = function () {
return data;
};
return data;
}
function _compFiles() {
const data = require("./workspace-component/comp-files");
_compFiles = function () {
return data;
};
return data;
}
function _filter() {
const data = require("./filter");
_filter = function () {
return data;
};
return data;
}
function _componentStatusLoader() {
const data = require("./workspace-component/component-status-loader");
_componentStatusLoader = function () {
return data;
};
return data;
}
function _autoTag() {
const data = require("./auto-tag");
_autoTag = function () {
return data;
};
return data;
}
function _configStore() {
const data = require("@teambit/config-store");
_configStore = function () {
return data;
};
return data;
}
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /* eslint-disable max-lines */
/**
* Field used to mark aspect config as "specific" (set via .bitmap or component.json).
* When __specific is true, this config takes precedence over workspace variants during merging.
* See https://github.com/teambit/bit/pull/5342 for original implementation.
*
* Important behavior for dependency-resolver aspect:
* - Dependencies set via workspace variants are saved WITHOUT __specific (until first `bit deps set`)
* - Once `bit deps set` is called, the entire dependency-resolver config gets __specific: true
* - From that point forward, ALL deps in that aspect are considered "specific"
*/
const AspectSpecificField = exports.AspectSpecificField = '__specific';
const ComponentAdded = exports.ComponentAdded = 'componentAdded';
const ComponentChanged = exports.ComponentChanged = 'componentChanged';
const ComponentRemoved = exports.ComponentRemoved = 'componentRemoved';
const DEFAULT_VENDOR_DIR = 'vendor';
/**
* API of the Bit Workspace
*/
class Workspace {
constructor(config,
/**
* private access to the legacy consumer instance.
*/
consumer,
/**
* access to the workspace `Scope` instance
*/
scope,
/**
* access to the `ComponentProvider` instance
*/
componentAspect, dependencyResolver, variants, aspectLoader, logger,
/**
* private reference to the instance of Harmony.
*/
harmony,
/**
* on component load slot.
*/
onComponentLoadSlot,
/**
* on component change slot.
*/
onComponentChangeSlot, envs, globalConfig,
/**
* on component add slot.
*/
onComponentAddSlot, onComponentRemoveSlot, onAspectsResolveSlot, onRootAspectAddedSlot, graphql, onBitmapChangeSlot, onWorkspaceConfigChangeSlot, configStore) {
this.config = config;
this.consumer = consumer;
this.scope = scope;
this.componentAspect = componentAspect;
this.dependencyResolver = dependencyResolver;
this.variants = variants;
this.aspectLoader = aspectLoader;
this.logger = logger;
this.harmony = harmony;
this.onComponentLoadSlot = onComponentLoadSlot;
this.onComponentChangeSlot = onComponentChangeSlot;
this.envs = envs;
this.globalConfig = globalConfig;
this.onComponentAddSlot = onComponentAddSlot;
this.onComponentRemoveSlot = onComponentRemoveSlot;
this.onAspectsResolveSlot = onAspectsResolveSlot;
this.onRootAspectAddedSlot = onRootAspectAddedSlot;
this.graphql = graphql;
this.onBitmapChangeSlot = onBitmapChangeSlot;
this.onWorkspaceConfigChangeSlot = onWorkspaceConfigChangeSlot;
this.configStore = configStore;
_defineProperty(this, "warnedAboutMisconfiguredEnvs", []);
// cache env-ids that have been errored about not having "env" type
_defineProperty(this, "priority", true);
_defineProperty(this, "owner", void 0);
_defineProperty(this, "componentsScopeDirsMap", void 0);
_defineProperty(this, "componentLoader", void 0);
_defineProperty(this, "componentStatusLoader", void 0);
_defineProperty(this, "bitMap", void 0);
/**
* Indicate that we are now running installation process
* This is important to know to ignore missing modules across different places
*/
_defineProperty(this, "inInstallContext", false);
/**
* Indicate that we done with the package manager installation process
* This is important to skip stuff when package manager install is not done yet
*/
_defineProperty(this, "inInstallAfterPmContext", false);
_defineProperty(this, "componentLoadedSelfAsAspects", void 0);
// cache loaded components
_defineProperty(this, "aspectsMerger", void 0);
/**
* Components paths are calculated from the component package names of the workspace
* They are used in webpack configuration to only track changes from these paths inside `node_modules`
*/
_defineProperty(this, "componentPathsRegExps", []);
_defineProperty(this, "_componentList", void 0);
_defineProperty(this, "localAspects", {});
_defineProperty(this, "filter", void 0);
this.componentLoadedSelfAsAspects = (0, _harmonyModules().createInMemoryCache)({
maxSize: (0, _harmonyModules().getMaxSizeForComponents)()
});
this.componentLoader = new (_workspaceComponentLoader().WorkspaceComponentLoader)(this, logger, dependencyResolver, envs, aspectLoader);
this.validateConfig();
this.bitMap = new (_bitMap().BitMap)(this.consumer.bitMap, this.consumer);
this.aspectsMerger = new (_aspectsMerger().AspectsMerger)(this, this.harmony);
this.filter = new (_filter().Filter)(this);
this.componentStatusLoader = new (_componentStatusLoader().ComponentStatusLoader)(this);
}
validateConfig() {
if (this.consumer.isLegacy) return;
if ((0, _lodash().isEmpty)(this.config)) throw new (_bitError().BitError)(`fatal: workspace config is empty. probably one of bit files is missing. please run "bit init" to rewrite them`);
const defaultScope = this.config.defaultScope;
if (!defaultScope) throw new (_bitError().BitError)('defaultScope is missing');
if (!(0, _legacyBitId().isValidScopeName)(defaultScope)) throw new (_legacyBitId().InvalidScopeName)(defaultScope);
}
get componentList() {
if (!this._componentList) {
this._componentList = new (_legacy3().ComponentsList)(this);
}
return this._componentList;
}
/**
* root path of the Workspace.
*/
get path() {
return this.consumer.getPath();
}
/**
* Get the location of the bit roots folder
*/
get rootComponentsPath() {
const baseDir = this.config.rootComponentsDirectory != null ? _path().default.join(this.path, this.config.rootComponentsDirectory) : this.modulesPath;
return _path().default.join(baseDir, _legacy5().BIT_ROOTS_DIR);
}
/** get the `node_modules` folder of this workspace */
get modulesPath() {
return _path().default.join(this.path, 'node_modules');
}
get isLegacy() {
return this.consumer.isLegacy;
}
registerOnComponentLoad(loadFn) {
this.onComponentLoadSlot.register(loadFn);
return this;
}
registerOnComponentChange(onComponentChangeFunc) {
this.onComponentChangeSlot.register(onComponentChangeFunc);
return this;
}
registerOnComponentAdd(onComponentAddFunc) {
this.onComponentAddSlot.register(onComponentAddFunc);
return this;
}
registerOnComponentRemove(onComponentRemoveFunc) {
this.onComponentRemoveSlot.register(onComponentRemoveFunc);
return this;
}
registerOnBitmapChange(OnBitmapChangeFunc) {
this.onBitmapChangeSlot.register(OnBitmapChangeFunc);
return this;
}
registerOnWorkspaceConfigChange(onWorkspaceConfigChangeFunc) {
this.onWorkspaceConfigChangeSlot.register(onWorkspaceConfigChangeFunc);
}
registerOnAspectsResolve(onAspectsResolveFunc) {
this.onAspectsResolveSlot.register(onAspectsResolveFunc);
return this;
}
registerOnRootAspectAdded(onRootAspectAddedFunc) {
this.onRootAspectAddedSlot.register(onRootAspectAddedFunc);
return this;
}
/**
* name of the workspace as configured in either `workspace.json`.
* defaults to workspace root directory name.
*/
get name() {
if (this.config.name) return this.config.name;
const tokenizedPath = this.path.split('/');
return tokenizedPath[tokenizedPath.length - 1];
}
get icon() {
return this.config.icon;
}
getConfigStore() {
return {
list: () => this.getWorkspaceConfig().extension(_configStore().ConfigStoreAspect.id, true) || {},
set: (key, value) => {
this.getWorkspaceConfig().setExtension(_configStore().ConfigStoreAspect.id, {
[key]: value
}, {
ignoreVersion: true,
mergeIntoExisting: true
});
},
del: key => {
const current = this.getWorkspaceConfig().extension(_configStore().ConfigStoreAspect.id, true) || {};
delete current[key];
this.getWorkspaceConfig().setExtension(_configStore().ConfigStoreAspect.id, current, {
ignoreVersion: true,
overrideExisting: true
});
},
write: async () => {
await this.getWorkspaceConfig().write({
reasonForChange: 'store-config changes'
});
},
invalidateCache: async () => {
// no need to invalidate anything.
// if this is the same process, it'll get the updated one already.
// if this is another process, it'll react to "this.triggerOnWorkspaceConfigChange()" anyway.
},
getPath: () => this.getWorkspaceConfig().path
};
}
async getAutoTagInfo(changedComponents) {
return (0, _autoTag().getAutoTagInfo)(this.consumer, changedComponents);
}
async listAutoTagPendingComponentIds() {
const componentsList = new (_legacy3().ComponentsList)(this);
const modifiedComponents = (await this.modified()).map(c => c.id);
const newComponents = await componentsList.listNewComponents();
if (!modifiedComponents || !modifiedComponents.length) return [];
const autoTagPending = await (0, _autoTag().getAutoTagPending)(this.consumer, _componentId().ComponentIdList.fromArray(modifiedComponents));
const localOnly = this.listLocalOnly();
const comps = autoTagPending.filter(autoTagComp => !newComponents.has(autoTagComp.componentId)).filter(autoTagComp => !localOnly.has(autoTagComp.componentId));
return comps.map(c => c.id);
}
async hasModifiedDependencies(component) {
const listAutoTagPendingComponents = await this.listAutoTagPendingComponentIds();
const isAutoTag = listAutoTagPendingComponents.find(id => id.isEqualWithoutVersion(component.id));
if (isAutoTag) return true;
return false;
}
/**
* get Component issues
*/
getComponentIssues(component) {
return component.state._consumer.issues || null;
}
/**
* provides status of all components in the workspace.
*/
async getComponentStatus(component) {
const status = await this.getComponentStatusById(component.id);
const hasModifiedDependencies = await this.hasModifiedDependencies(component);
return _componentStatus().ComponentStatus.fromLegacy(status, hasModifiedDependencies, component.isOutdated());
}
/**
* list all workspace components.
*/
async list(filter, loadOpts) {
const loadOptsWithDefaults = Object.assign(loadOpts || {});
const ids = this.consumer.bitMap.getAllIdsAvailableOnLane();
const idsToGet = filter && filter.limit ? (0, _lodash().slice)(ids, filter.offset, filter.offset + filter.limit) : ids;
return this.getMany(idsToGet, loadOptsWithDefaults);
}
async listWithInvalid(loadOpts) {
const legacyIds = this.consumer.bitMap.getAllIdsAvailableOnLane();
return this.componentLoader.getMany(legacyIds, loadOpts, false);
}
/**
* list all invalid components.
* (see the invalid criteria in ConsumerComponent.isComponentInvalidByErrorType())
*/
async listInvalid() {
const ids = this.consumer.bitMap.getAllIdsAvailableOnLane();
return this.componentLoader.getInvalid(ids);
}
/**
* get ids of all workspace components.
* deleted components are filtered out. (use this.listIdsIncludeRemoved() if you need them)
*/
listIds() {
return this.consumer.bitmapIdsFromCurrentLane;
}
listIdsIncludeRemoved() {
return this.consumer.bitmapIdsFromCurrentLaneIncludeRemoved;
}
/**
* whether the given component-id is part of the workspace. default to check for the exact version
*/
hasId(componentId, opts) {
const ids = opts?.includeDeleted ? this.listIdsIncludeRemoved() : this.listIds();
return opts?.ignoreVersion ? ids.hasWithoutVersion(componentId) : ids.has(componentId);
}
/**
* given component-ids, return the ones that are part of the workspace
*/
async filterIds(ids) {
const workspaceIds = this.listIds();
return ids.filter(id => workspaceIds.find(wsId => wsId.isEqual(id, {
ignoreVersion: !id.hasVersion()
})));
}
/**
* whether or not a workspace has a component with the given name
*/
async hasName(name) {
const ids = await this.listIds();
return Boolean(ids.find(id => id.fullName === name));
}
/**
* Check if a specific id exist in the workspace or in the scope
* @param componentId
*/
async hasIdNested(componentId, includeCache = true) {
const found = await this.hasId(componentId);
if (found) return found;
return this.scope.hasIdNested(componentId, includeCache);
}
/**
* list all modified components in the workspace.
*/
async modified(loadOpts) {
const {
components
} = await this.listWithInvalid(loadOpts);
const modifiedIncludeNulls = await (0, _pMapSeries().default)(components, async component => {
const modified = await this.isModified(component);
return modified ? component : null;
});
return (0, _lodash().compact)(modifiedIncludeNulls);
}
/**
* list all new components in the workspace.
*/
async newComponents() {
const componentIds = await this.newComponentIds();
return this.getMany(componentIds);
}
async newComponentIds() {
const allIds = this.listIds();
return allIds.filter(id => !id.hasVersion());
}
async locallyDeletedIds() {
return this.componentList.listLocallySoftRemoved();
}
async duringMergeIds() {
const duringMerge = this.componentList.listDuringMergeStateComponents();
return this.resolveMultipleComponentIds(duringMerge);
}
/**
* @deprecated use `listIds()` instead.
* get all workspace component-ids
*/
getAllComponentIds() {
return this.listIds();
}
async listTagPendingIds() {
const newComponents = await this.newComponentIds();
const modifiedComponents = (await this.modified()).map(c => c.id);
const removedComponents = await this.locallyDeletedIds();
const duringMergeIds = await this.duringMergeIds();
const allIds = [...newComponents, ...modifiedComponents, ...removedComponents, ...duringMergeIds];
const allIdsUniq = (0, _lodash().uniqBy)(allIds, id => id.toString());
return allIdsUniq;
}
/**
* list all components that can be tagged. (e.g. when tagging/snapping with --unmodified).
* which are all components in the workspace, include locally deleted components.
*/
async listPotentialTagIds() {
const deletedIds = await this.locallyDeletedIds();
const allIdsWithoutDeleted = this.listIds();
return [...deletedIds, ...allIdsWithoutDeleted];
}
async getNewAndModifiedIds() {
const ids = await this.listTagPendingIds();
return ids;
}
async newAndModified() {
const ids = await this.getNewAndModifiedIds();
return this.getMany(ids);
}
async getLogs(id, shortHash = false, startsFrom) {
return this.scope.getLogs(id, shortHash, startsFrom);
}
async getGraph(ids, shouldThrowOnMissingDep = true) {
if (!ids || ids.length < 1) ids = this.listIds();
return this.buildOneGraphForComponents(ids, undefined, undefined, shouldThrowOnMissingDep);
}
async getGraphIds(ids, shouldThrowOnMissingDep = true) {
if (!ids || ids.length < 1) ids = this.listIds();
const graphIdsFromFsBuilder = new (_buildGraphIdsFromFs().GraphIdsFromFsBuilder)(this, this.logger, this.dependencyResolver, shouldThrowOnMissingDep);
return graphIdsFromFsBuilder.buildGraph(ids);
}
async getUnavailableOnMainComponents() {
const currentLaneId = this.consumer.getCurrentLaneId();
if (!currentLaneId.isDefault()) return [];
const allIds = this.consumer.bitMap.getAllBitIdsFromAllLanes();
const availableIds = this.consumer.bitMap.getAllIdsAvailableOnLane();
if (allIds.length === availableIds.length) return [];
const unavailableIds = allIds.filter(id => !availableIds.hasWithoutVersion(id));
if (!unavailableIds.length) return [];
const removedIds = this.consumer.bitMap.getRemoved();
const compsWithHead = [];
await Promise.all(unavailableIds.map(async id => {
if (removedIds.has(id)) return; // we don't care about removed components
const modelComp = await this.scope.legacyScope.getModelComponentIfExist(id);
if (modelComp && modelComp.head) compsWithHead.push(id);
}));
return compsWithHead;
}
getDependencies(component) {
return this.dependencyResolver.getDependencies(component);
}
async getSavedGraphOfComponentIfExist(component) {
if (!component.id.hasVersion()) return null;
const flattenedEdges = await this.scope.getFlattenedEdges(component.id);
const versionObj = await this.scope.getBitObjectVersionById(component.id);
if (!flattenedEdges || !versionObj) return null;
if (!flattenedEdges.length && versionObj.flattenedDependencies.length) {
// there are flattenedDependencies, so must be edges, if they're empty, it's because the component was tagged
// with a version < ~0.0.901, so this flattenedEdges wasn't exist.
return null;
}
const flattenedBitIdCompIdMap = {};
const getCurrentVersionAsTagIfPossible = () => {
const currentVer = component.id.version;
if (!currentVer) return undefined;
const isCurrentVerAHash = (0, _componentVersion().isHash)(currentVer);
if (!isCurrentVerAHash) return currentVer;
const tag = component.tags.byHash(currentVer)?.version.raw;
return tag || currentVer;
};
const currentVersion = getCurrentVersionAsTagIfPossible();
flattenedBitIdCompIdMap[component.id.changeVersion(currentVersion).toString()] = component.id;
versionObj.flattenedDependencies.forEach(bitId => {
flattenedBitIdCompIdMap[bitId.toString()] = bitId;
});
const getCompIdByIdStr = idStr => {
const compId = flattenedBitIdCompIdMap[idStr];
if (!compId) {
const suggestWrongSnap = (0, _componentVersion().isHash)(component.id.version) ? `\nplease check that .bitmap has the correct versions of ${component.id.toStringWithoutVersion()}.
it's possible that the version ${component.id.version} belong to ${idStr.split('@')[0]}` : '';
throw new Error(`id ${idStr} exists in flattenedEdges but not in flattened of ${component.id.toString()}.${suggestWrongSnap}`);
}
return compId;
};
const nodes = Object.values(flattenedBitIdCompIdMap);
const edges = flattenedEdges.map(edge => _objectSpread(_objectSpread({}, edge), {}, {
source: getCompIdByIdStr(edge.source.toString()),
target: getCompIdByIdStr(edge.target.toString())
}));
const graph = new (_graph().Graph)();
nodes.forEach(node => graph.setNode(new (_graph().Node)(node.toString(), node)));
edges.forEach(edge => graph.setEdge(new (_graph().Edge)(edge.source.toString(), edge.target.toString(), edge.type)));
return graph;
}
/**
* given component ids, find their dependents in the workspace
*/
async getDependentsIds(ids, filterOutNowWorkspaceIds = true) {
const graph = await this.getGraphIds();
const dependents = ids.map(id => graph.predecessors(id.toString(), {
nodeFilter: node => filterOutNowWorkspaceIds ? this.hasId(node.attr) : true
})).flat().map(node => node.attr);
return _componentId().ComponentIdList.uniqFromArray(dependents);
}
async createAspectList(extensionDataList) {
const entiresP = extensionDataList.map(entry => this.extensionDataEntryToAspectEntry(entry));
const entries = await Promise.all(entiresP);
return this.componentAspect.createAspectListFromEntries(entries);
}
async extensionDataEntryToAspectEntry(dataEntry) {
return new (_component().AspectEntry)(await this.resolveComponentId(dataEntry.id), dataEntry);
}
/**
* this is not the complete legacy component (ConsumerComponent), it's missing dependencies and hooks from Harmony
* are skipped. do not trust the data you get from this method unless you know what you're doing.
*/
async getLegacyMinimal(id) {
try {
const componentMap = this.consumer.bitMap.getComponent(id);
return await _legacy6().ConsumerComponent.loadFromFileSystem({
componentMap,
id,
consumer: this.consumer
});
} catch {
return undefined;
}
}
async getFilesModification(id) {
const bitMapEntry = this.bitMap.getBitmapEntry(id, {
ignoreVersion: true
});
const compDir = bitMapEntry.getComponentDir();
const compDirAbs = _path().default.join(this.path, compDir);
const sourceFilesVinyls = bitMapEntry.files.map(file => {
const filePath = _path().default.join(compDirAbs, file.relativePath);
return _component2().SourceFile.load(filePath, compDirAbs, this.path, {});
});
const repo = this.scope.legacyScope.objects;
const getModelFiles = async () => {
const modelComp = await this.scope.legacyScope.getModelComponentIfExist(id);
if (!modelComp) return [];
if (!bitMapEntry.id.hasVersion()) return [];
const verObj = await modelComp.loadVersion(bitMapEntry.id.version, repo);
return verObj.files;
};
return new (_compFiles().CompFiles)(id, repo, sourceFilesVinyls, compDir, await getModelFiles());
}
/**
* get a component from workspace
* @param id component ID
*/
async get(componentId, legacyComponent, useCache = true, storeInCache = true, loadOpts) {
this.logger.trace(`get ${componentId.toString()}`);
const component = await this.componentLoader.get(componentId, legacyComponent, useCache, storeInCache, loadOpts);
// When loading a component if it's an env make sure to load it as aspect as well
// We only want to try load it as aspect if it's the first time we load the component
const tryLoadAsAspect = this.componentLoadedSelfAsAspects.get(component.id.toString()) === undefined;
// const config = this.harmony.get<ConfigMain>('teambit.harmony/config');
// We are loading the component as aspect if it's an env, in order to be able to run the env-preview-template task which run only on envs.
// Without this loading we will have a problem in case the env is the only component in the workspace. in that case we will load it as component
// then we don't run it's provider so it doesn't register to the env slot, so we don't know it's an env.
if (tryLoadAsAspect && this.envs.isUsingEnvEnv(component) && !this.aspectLoader.isCoreAspect(component.id.toStringWithoutVersion()) && !this.aspectLoader.isAspectLoaded(component.id.toString()) && this.hasId(component.id)
// !config.extension(component.id.toStringWithoutVersion(), true)
) {
try {
this.componentLoadedSelfAsAspects.set(component.id.toString(), true);
this.logger.debug(`trying to load self as aspect with id ${component.id.toString()}`);
// ignore missing modules when loading self
await this.loadAspects([component.id.toString()], undefined, component.id.toString(), {
hideMissingModuleError: true
});
// In most cases if the load self as aspect failed we don't care about it.
// we only need it in specific cases to work, but this workspace.get runs on different
// cases where it might fail (like when importing aspect, after the import objects
// when we write the package.json we run the applyTransformers which get to pkg which call
// host.get, but the component not written yet to the fs, so it fails.)
} catch {
this.logger.debug(`fail to load self as aspect with id ${component.id.toString()}`);
this.componentLoadedSelfAsAspects.delete(component.id.toString());
return component;
}
}
this.componentLoadedSelfAsAspects.set(component.id.toString(), false);
return component;
}
async getConfiguredUserAspectsPackages(options) {
const workspaceAspectsLoader = this.getWorkspaceAspectsLoader();
return workspaceAspectsLoader.getConfiguredUserAspectsPackages(options);
}
/**
* clears workspace, scope and all components caches.
* doesn't clear the dependencies-data from the filesystem-cache.
*/
async clearCache(options = {}) {
this.logger.debug('clearing the workspace and scope caches');
this.aspectLoader.resetFailedLoadAspects();
if (!options.skipClearFailedToLoadEnvs) this.envs.resetFailedToLoadEnvs();
await this.scope.clearCache();
this.clearAllComponentsCache();
}
/**
* clear the cache of all components in the workspace.
* doesn't clear the dependencies-data from the filesystem-cache.
*/
clearAllComponentsCache() {
this.logger.debug('clearing all components caches');
this.componentLoader.clearCache();
this.consumer.componentLoader.clearComponentsCache();
this.componentStatusLoader.clearCache();
this._componentList = new (_legacy3().ComponentsList)(this);
}
clearComponentCache(id) {
this.componentLoader.clearComponentCache(id);
this.componentStatusLoader.clearOneComponentCache(id);
this.consumer.clearOneComponentCache(id);
this._componentList = new (_legacy3().ComponentsList)(this);
}
clearComponentsCache(ids) {
ids.forEach(id => this.clearComponentCache(id));
}
async warmCache() {
await this.list();
}
getWorkspaceConfig() {
const config = this.harmony.get('teambit.harmony/config');
const workspaceConfig = config.workspaceConfig;
if (!workspaceConfig) throw new Error('workspace config is missing from Config aspect');
return workspaceConfig;
}
async cleanFromConfig(ids) {
const workspaceConfig = this.getWorkspaceConfig();
const wereIdsRemoved = ids.map(id => workspaceConfig.removeExtension(id));
const hasChanged = wereIdsRemoved.some(isRemoved => isRemoved);
if (hasChanged) await workspaceConfig.write({
reasonForChange: 'remove components'
});
return hasChanged;
}
/**
* when tagging/snapping a component, its config data is written to the staged config. it helps for "bit reset" to
* revert it back.
* this method removes entries from that files. used by "bit export" and "bit remove".
* in case the component is not found in the staged config, it doesn't throw an error. it simply ignores it.
*/
async removeFromStagedConfig(ids) {
this.logger.debug(`removeFromStagedConfig, ${ids.length} ids`);
const stagedConfig = await this.scope.getStagedConfig();
ids.map(compId => stagedConfig.removeComponentConfig(compId));
await stagedConfig.write();
}
async triggerOnComponentChange(id, files, removedFiles, watchOpts) {
const component = await this.get(id);
const onChangeEntries = this.onComponentChangeSlot.toArray(); // e.g. [ [ 'teambit.bit/compiler', [Function: bound onComponentChange] ] ]
const results = [];
await (0, _pMapSeries().default)(onChangeEntries, async ([extension, onChangeFunc]) => {
const onChangeResult = await onChangeFunc(component, files, removedFiles, watchOpts);
if (onChangeResult) results.push({
extensionId: extension,
results: onChangeResult
});
});
// TODO: find way to standardize event names.
await this.graphql.pubsub.publish(ComponentChanged, {
componentChanged: {
component
}
});
return results;
}
async triggerOnComponentAdd(id, watchOpts, loadOptions) {
const component = await this.get(id, undefined, undefined, undefined, loadOptions);
const onAddEntries = this.onComponentAddSlot.toArray(); // e.g. [ [ 'teambit.bit/compiler', [Function: bound onComponentChange] ] ]
const results = [];
const files = component.state.filesystem.files.map(file => file.path);
await (0, _pMapSeries().default)(onAddEntries, async ([extension, onAddFunc]) => {
const onAddResult = await onAddFunc(component, files, watchOpts);
if (onAddResult) results.push({
extensionId: extension,
results: onAddResult
});
});
await this.graphql.pubsub.publish(ComponentAdded, {
componentAdded: {
component
}
});
return results;
}
async triggerOnComponentRemove(id) {
const onRemoveEntries = this.onComponentRemoveSlot.toArray(); // e.g. [ [ 'teambit.bit/compiler', [Function: bound onComponentChange] ] ]
const results = [];
await (0, _pMapSeries().default)(onRemoveEntries, async ([extension, onRemoveFunc]) => {
const onRemoveResult = await onRemoveFunc(id);
results.push({
extensionId: extension,
results: onRemoveResult
});
});
await this.graphql.pubsub.publish(ComponentRemoved, {
componentRemoved: {
componentIds: [id.toObject()]
}
});
return results;
}
async triggerOnBitmapChange() {
const onBitmapChangeEntries = this.onBitmapChangeSlot.toArray(); // e.g. [ [ 'teambit.bit/compiler', [Function: bound onComponentChange] ] ]
await (0, _pMapSeries().default)(onBitmapChangeEntries, async ([, onBitmapChangeFunc]) => {
await onBitmapChangeFunc();
});
}
/**
* the purpose is mostly to reload the workspace config when it changes, so entries like "defaultScope" are updated.
* it also updates the DependencyResolver config. I couldn't find a good way to update all aspects in workspace.jsonc.
*/
async triggerOnWorkspaceConfigChange() {
this.logger.debug('triggerOnWorkspaceConfigChange, reloading workspace config');
const config = this.harmony.get('teambit.harmony/config');
await config.reloadWorkspaceConfig(this.path);
const workspaceConfig = config.workspaceConfig;
if (!workspaceConfig) throw new Error('workspace config is missing from Config aspect');
const configOfWorkspaceAspect = workspaceConfig.extensions.findExtension(_workspace().WorkspaceAspect.id);
if (!configOfWorkspaceAspect) throw new Error('workspace extension is missing from workspace config');
this.config = configOfWorkspaceAspect.config;
const configOfDepResolverAspect = workspaceConfig.extensions.findExtension(_dependencyResolver().DependencyResolverAspect.id);
if (configOfDepResolverAspect) this.dependencyResolver.setConfig(configOfDepResolverAspect.config);
this.dependencyResolver.clearCache();
this.configStore.invalidateCache();
const onWorkspaceConfigChangeEntries = this.onWorkspaceConfigChangeSlot.toArray(); // e.g. [ [ 'teambit.bit/compiler', [Function: bound onComponentChange] ] ]
await (0, _pMapSeries().default)(onWorkspaceConfigChangeEntries, async ([, onWorkspaceConfigFunc]) => {
await onWorkspaceConfigFunc();
});
}
getState(id, hash) {
return this.scope.getState(id, hash);
}
getSnap(id, hash) {
return this.scope.getSnap(id, hash);
}
getCurrentLaneId() {
return this.consumer.getCurrentLaneId();
}
async getCurrentLaneObject() {
return this.consumer.getCurrentLaneObject();
}
isOnMain() {
return this.consumer.isOnMain();
}
isOnLane() {
return this.consumer.isOnLane();
}
/**
* if checked out to a lane and the lane exists in the remote,
* return the remote lane. otherwise, return null.
*/
async getCurrentRemoteLane() {
const currentLaneId = this.getCurrentLaneId();
if (currentLaneId.isDefault()) {
return null;
}
const scopeComponentImporter = _legacy7().ScopeComponentsImporter.getInstance(this.consumer.scope);
try {
const lanes = await scopeComponentImporter.importLanes([currentLaneId]);
return lanes[0];
} catch (err) {
if (err instanceof _legacyBitId().InvalidScopeName || err instanceof _scope().ScopeNotFoundOrDenied || err instanceof _legacy8().LaneNotFound || err instanceof _legacyBitId().InvalidScopeNameFromRemote) {
const bitMapLaneId = this.bitMap.getExportedLaneId();
if (bitMapLaneId?.isEqual(currentLaneId)) {
throw err; // we know the lane is not new, so the error is legit
}
// the lane could be a local lane so no need to throw an error in such case
this.logger.clearStatusLine();
this.logger.warn(`unable to get lane's data from a remote due to an error:\n${err.message}`);
return null;
}
throw err;
}
}
getDefaultExtensions() {
if (!this.config.extensions) {
return new (_legacy4().ExtensionDataList)();
}
return _legacy4().ExtensionDataList.fromConfigObject(this.config.extensions);
}
async getComponentConfigVinylFile(id, options, excludeLocalChanges = false) {
const componentId = await this.resolveComponentId(id);
const extensions = await this.getExtensionsFromScopeAndSpecific(id, excludeLocalChanges);
const aspects = await this.createAspectList(extensions);
this.removeEnvVersionIfExistsLocally(aspects);
const componentDir = this.componentDir(id, {
ignoreVersion: true
}, {
relative: true
});
const configFile = new (_componentConfigFile().ComponentConfigFile)(componentId, aspects, componentDir, options.propagate);
return configFile.toVinylFile(options);
}
removeEnvVersionIfExistsLocally(aspects) {
const env = aspects.get(_envs().EnvsAspect.id)?.config?.env;
if (!env) return;
const envAspect = aspects.get(env);
if (!envAspect) return;
const envExtId = envAspect.id;
if (!envExtId?.hasVersion()) return;
if (!this.hasId(envExtId, {
ignoreVersion: true
})) return;
envAspect.id = envExtId.changeVersion(undefined);
}
async ejectMultipleConfigs(ids, options) {
const vinylFiles = await Promise.all(ids.map(id => this.getComponentConfigVinylFile(id, options)));
const EjectConfResult = vinylFiles.map(file => ({
configPath: file.path
}));
const dataToPersist = new (_component2().DataToPersist)();
dataToPersist.addManyFiles(vinylFiles);
dataToPersist.addBasePath(this.path);
await dataToPersist.persistAllToFS();
ids.map(id => this.bitMap.removeEntireConfig(id));
await this.bitMap.write(`eject-conf (${ids.length} component(s))`);
return EjectConfResult;
}
async getAspectConfigForComponent(id, aspectId) {
const extensions = await this.getExtensionsFromScopeAndSpecific(id);
const obj = extensions.toConfigObject();
return obj[aspectId];
}
async getExtensionsFromScopeAndSpecific(id, excludeComponentJson = false) {
const componentFromScope = await this.scope.get(id);
const exclude = ['WorkspaceVariants'];
if (excludeComponentJson) exclude.push('ComponentJsonFile');
const {
extensions
} = await this.componentExtensions(id, componentFromScope, exclude);
return extensions;
}
/**
* @deprecated use `this.idsByPattern` instead for consistency. also, it supports negation and list of patterns.
*
* load components into the workspace through a variants pattern.
* @param pattern variants.
* @param scope scope name.
*/
async byPattern(pattern, scope = '**') {
const ids = await this.listIds();
const finalPattern = `${scope}/${pattern || '**'}`;
const targetIds = ids.filter(id => {
const spec = (0, _workspaceModules().isMatchNamespacePatternItem)(id.toStringWithoutVersion(), finalPattern);
return spec.match;
});
const components = await this.getMany(targetIds);
return components;
}
hasPattern(strArr) {
return strArr.some(str => this.isPattern(str));
}
isPattern(str) {
const specialSyntax = ['*', ',', '!', '$', ':'];
return specialSyntax.some(char => str.includes(char));
}
/**
* get component-ids matching the given pattern. a pattern can have multiple patterns separated by a comma.
* it supports negate (!) character to exclude ids.
*/
async idsByPattern(pattern, throwForNoMatch = true, opts = {}) {
const isId = !this.isPattern(pattern);
if (isId) {
// if it's not a pattern but just id, resolve it without multimatch to support specifying id without scope-name
const id = await this.resolveComponentId(pattern);
if (this.hasId(id, {
ignoreVersion: true,
includeDeleted: opts.includeDeleted
})) return [id];
if (throwForNoMatch) throw new (_legacy2().MissingBitMapComponent)(pattern);
return [];
}
const ids = opts.includeDeleted ? this.listIdsIncludeRemoved() : await this.listIds();
return this.filterIdsFromPoolIdsByPattern(pattern, ids, throwForNoMatch);
}
async filterIdsFromPoolIdsByPattern(pattern, ids, throwForNoMatch = true) {
return this.scope.filterIdsFromPoolIdsByPattern(pattern, ids, throwForNoMatch, this.filter.by.bind(this.filter));
}
/**
* useful for workspace commands, such as `bit build`, `bit compile`.
* by default, it should be running on new and modified components.
* a user can specify `--all` to run on all components or specify a pattern to limit to specific components.
* some commands such as build/test needs to run also on the dependents.
*/
async getComponentsByUserInput(all, pattern, includeDependents = false) {
if (all && pattern) {
throw new (_bitError().BitError)('Cannot use both "all" flag and component pattern simultaneously. Use either --all/--unmodified for all components, or specify component pattern.');
}
if (all) {
return this.list();
}
if (pattern) {
const ids = await this.idsByPattern(pattern);
return this.getMany(ids, {
loadExtensions: true,
loadSeedersAsAspects: true,
executeLoadSlot: true
});
}
const newAndModified = await this.newAndModified();
if (includeDependents) {
const newAndModifiedIds = newAndModified.map(comp => comp.id);
const dependentsIds = await this.getDependentsIds(newAndModifiedIds);
const dependentsIdsFiltered = dependentsIds.filter(id => !newAndModified.find(_ => _.id.isEqual(id)));
const dependents = await this.getMany(dependentsIdsFiltered);
newAndModified.push(...dependents);
}
return newAndModified;
}
async getComponentsUsingEnv(env, ignoreVersion = true, throwIfNotFound = false) {
const allComps = await this.list();
const availableEnvs = [];
const foundComps = allComps.filter(comp => {
const envId = this.envs.getEnvId(comp);
if (env === envId) return true;
availableEnvs.push(envId);
if (!ignoreVersion) return false;
const envWithoutVersion = envId.split('@')[0];
if (env === envWithoutVersion) return true;
// envIdFromConfig never has a version. so in case ignoreVersion is true, it's safe to compare without the version.
// also, in case the env failed to load, the envId above will be the default teambit.harmony/node env, which
// won't help. this one is the one configured on this component.
const envIdFromConfig = this.envs.getEnvIdFromEnvsConfig(comp);
return envIdFromConfig === env;
});
if (!foundComps.length && throwIfNotFound) {
throw new (_bitError().BitError)(`unable to find components that using "${env}" env.
the following envs are used in this workspace: ${(0, _lodash().uniq)(availableEnvs).join(', ')}`);
}
return foundComps;
}
async getMany(ids, loadOpts, throwOnFailure = true) {
this.logger.debug(`getMany, started. ${ids.length} components`);
const {
components
} = await this.componentLoader.getMany(ids, loadOpts, throwOnFailure);
this.logger.debug(`getMany, completed. ${components.length} components`);
return components;
}
getManyByLegacy(components, loadOpts) {
return (0, _pMapSeries().default)(components, async component => {
const id = component.id;
return this.get(id, component, true, true, loadOpts);
});
}
/**
* don't throw an error if the component was not found, simply return undefined.
*/
async getIfExist(componentId) {
return this.componentLoader.getIfExist(componentId);
}
/**
* @deprecated use `hasId` with "ignoreVersion: true" instead.
*/
exists(componentId, opts = {}) {
const allIds = opts.includeDeleted ? this.listIdsIncludeRemoved() : this.consumer.bitmapIdsFromCurrentLane;
return allIds.hasWithoutVersion(componentId);
}
getIdIfExist(componentId) {
return this.consumer.bitmapIdsFromCurrentLane.find(_ => _.isEqualWithoutVersion(componentId));
}
mergeBitmaps(bitmapContent, otherBitmapContent, opts = {}) {
return this.bitMap.mergeBitmaps(bitmapContent, otherBitmapContent, opts);
}
/**
* This will make sure to fetch the objects prior to load them
* do not use it if you are not sure you need it.
* It will influence the performance
* currently it used only for get many of aspects
* @param ids
*/
async importAndGetMany(ids, reason, loadOpts, throwOnError = true) {
if (!ids.length) return [];
const lane = await this.importCurrentLaneIfMissing();
await this.scope.import(ids, {
reFetchUnBuiltVersion: shouldReFetchUnBuiltVersion(),
preferDependencyGraph: true,
// add the lane object although it was imported with all its ids previously.
// in some cases, this import re-fetch existing ids whose their VersionHistory is incomplete, so it needs the Lane context.
lane,
reason
});
return this.getMany(ids, loadOpts, throwOnError);
}
/**
* This is happening when the user is running "git pull", which updates ".bitmap" file, but the local scope objects
* are not updated yet ("bit import" is not run yet).
* Although it might happen on a lane. This is rare. Because using git with bit normally means that locally you don't have
* any lane. The CI is the one that creates the lanes.
* The following conditions are checked:
* 1. we're on main.
* 2. git is enabled.
* 3. components on .bitmap has tags that are not in the local scope.
*
* It is designed to be performant. On mac M1 with 337 components, it takes around 100ms.
*
* @returns array of component IDs that have tags in .bitmap but not in local scope, or