UNPKG

@teambit/workspace

Version:
1,314 lines (1,278 loc) • 101 kB
"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