diff --git a/e2e/flows/out-of-sync-componets.e2e.3.ts b/e2e/flows/out-of-sync-componets.e2e.3.ts index 081537d85940..488e53e244d0 100644 --- a/e2e/flows/out-of-sync-componets.e2e.3.ts +++ b/e2e/flows/out-of-sync-componets.e2e.3.ts @@ -272,23 +272,20 @@ describe('components that are not synced between the scope and the consumer', fu helper.general.expectToThrow(() => helper.command.status(), err); }); }); - // @TODO: FIX ON HARMONY! - describe.skip('bit show', () => { + describe('bit show', () => { it('should throw an error suggesting to import the components', () => { const err = new ComponentsPendingImport(); helper.general.expectToThrow(() => helper.command.showComponent('bar/foo'), err); }); }); - // @TODO: FIX ON HARMONY! - describe.skip('bit tag', () => { + describe('bit tag', () => { it('should throw an error suggesting to import the components', () => { const err = new ComponentsPendingImport(); helper.general.expectToThrow(() => helper.command.tagAllWithoutBuild(), err); }); }); }); - // @TODO: FIX ON HARMONY! - describe.skip('when the remote component does not exist or does not have this missing version', () => { + describe('when the remote component does not exist or does not have this missing version', () => { let scopeAfterV1; let scopeOutOfSync; before(() => { @@ -313,9 +310,7 @@ describe('components that are not synced between the scope and the consumer', fu }); it('should sync .bitmap according to the latest version of the scope', () => { helper.command.expectStatusToBeClean(); - const bitMap = helper.bitMap.read(); - const newId = `${helper.scopes.remote}/bar/foo@0.0.1`; - expect(bitMap).to.have.property(newId); + helper.bitMap.expectToHaveIdHarmony('bar/foo', '0.0.1', helper.scopes.remote); }); }); describe('bit tag', () => { diff --git a/e2e/harmony/lanes/lanes.e2e.ts b/e2e/harmony/lanes/lanes.e2e.ts index 5050603dd612..e0337c89fbab 100644 --- a/e2e/harmony/lanes/lanes.e2e.ts +++ b/e2e/harmony/lanes/lanes.e2e.ts @@ -1240,8 +1240,7 @@ describe('bit lane command', function () { }); }); }); - // @todo: fix! - describe.skip('head on the lane is not in the filesystem', () => { + describe('head on the lane is not in the filesystem', () => { before(() => { helper.scopeHelper.setNewLocalAndRemoteScopesHarmony(); helper.bitJsonc.setupDefault(); diff --git a/scopes/workspace/workspace/workspace.ts b/scopes/workspace/workspace/workspace.ts index 4ebf11b27c2b..bc6e963b1a3a 100644 --- a/scopes/workspace/workspace/workspace.ts +++ b/scopes/workspace/workspace/workspace.ts @@ -808,11 +808,12 @@ the following envs are used in this workspace: ${availableEnvs.join(', ')}`); if (!lane) { return; } - this.logger.debug(`current lane ${laneId.toString()} is missing, importing it`); + this.logger.info(`current lane ${laneId.toString()} is missing, importing it`); + await this.scope.legacyScope.objects.writeObjectsToTheFS([lane]); const scopeComponentsImporter = ScopeComponentsImporter.getInstance(this.scope.legacyScope); const ids = BitIds.fromArray(lane.toBitIds().filter((id) => id.hasScope())); await scopeComponentsImporter.importManyDeltaWithoutDeps(ids, true, lane); - await scopeComponentsImporter.importMany({ ids, lanes: lane ? [lane] : undefined }); + await scopeComponentsImporter.importMany({ ids, lanes: [lane] }); } /** diff --git a/src/consumer/component/component-loader.ts b/src/consumer/component/component-loader.ts index f8432573fbef..510521065270 100644 --- a/src/consumer/component/component-loader.ts +++ b/src/consumer/component/component-loader.ts @@ -16,6 +16,7 @@ import Consumer from '../consumer'; import { ComponentFsCache } from './component-fs-cache'; import { updateDependenciesVersions } from './dependencies/dependency-resolver'; import { DependenciesLoader } from './dependencies/dependency-resolver/dependencies-loader'; +import ComponentMap from '../bit-map/component-map'; export type ComponentLoadOptions = { loadDocs?: boolean; @@ -143,7 +144,7 @@ export default class ComponentLoader { removedComponents: Component[], loadOpts?: ComponentLoadOptions ) { - const componentMap = this.consumer.bitMap.getComponent(id); + let componentMap = this.consumer.bitMap.getComponent(id); if (componentMap.isRemoved()) { const fromModel = await this.consumer.scope.getConsumerComponentIfExist(id); if (!fromModel) { @@ -161,7 +162,6 @@ export default class ComponentLoader { removedComponents.push(fromModel); return null; } - const bitDir = path.join(this.consumer.getPath(), componentMap.rootDir); let component: Component; const handleError = (error) => { if (throwOnFailure) throw error; @@ -175,11 +175,16 @@ export default class ComponentLoader { } throw error; }; + const newId = await this._handleOutOfSyncScenarios(componentMap); + if (newId) { + componentMap = this.consumer.bitMap.getComponent(newId); + } + const updatedId = newId || id; + try { component = await Component.loadFromFileSystem({ - bitDir, componentMap, - id, + id: updatedId, consumer: this.consumer, }); } catch (err: any) { @@ -187,8 +192,8 @@ export default class ComponentLoader { } component.loadedFromFileSystem = true; // reload component map as it may be changed after calling Component.loadFromFileSystem() - component.componentMap = this.consumer.bitMap.getComponent(id); - await this._handleOutOfSyncScenarios(component); + component.componentMap = this.consumer.bitMap.getComponent(updatedId); + await this._handleOutOfSyncWithDefaultScope(component); const loadDependencies = async () => { await this.invalidateDependenciesCacheIfNeeded(); @@ -226,11 +231,10 @@ export default class ComponentLoader { }); } - private async _handleOutOfSyncScenarios(component: Component) { - const { componentFromModel, componentMap } = component; - // @ts-ignore componentMap is set here + private async _handleOutOfSyncScenarios(componentMap: ComponentMap): Promise { const currentId: BitId = componentMap.id; - let newId: BitId | null | undefined; + const componentFromModel = await this.consumer.loadComponentFromModelIfExist(currentId); + let newId: BitId | undefined; if (componentFromModel && !currentId.hasVersion()) { // component is in the scope but .bitmap doesn't have version, sync .bitmap with the scope data newId = currentId.changeVersion(componentFromModel.version); @@ -248,13 +252,23 @@ export default class ComponentLoader { // latest version from the scope await this._throwPendingImportIfNeeded(currentId); newId = currentId.changeVersion(modelComponent.latest()); - component.componentFromModel = await this.consumer.loadComponentFromModelIfExist(newId); } else if (!currentId.hasScope()) { // the scope doesn't have this component and .bitmap doesn't have scope, assume it's new newId = currentId.changeVersion(undefined); } } - if (!componentFromModel && !currentId.hasVersion() && component.defaultScope) { + + if (newId) { + this.consumer.bitMap.updateComponentId(newId); + } + return newId; + } + + private async _handleOutOfSyncWithDefaultScope(component: Component) { + const { componentFromModel, componentMap } = component; + // @ts-ignore componentMap is set here + const currentId: BitId = componentMap.id; + if (!componentFromModel && !currentId.hasVersion()) { // for Harmony, we know ahead the defaultScope, so even then .bitmap shows it as new and // there is nothing in the scope, we can check if there is a component with the same // default-scope in the objects @@ -262,16 +276,14 @@ export default class ComponentLoader { currentId.changeScope(component.defaultScope) ); if (modelComponent) { - newId = currentId.changeVersion(modelComponent.latest()).changeScope(modelComponent.scope); + const newId = currentId.changeVersion(modelComponent.latest()).changeScope(modelComponent.scope); component.componentFromModel = await this.consumer.loadComponentFromModelIfExist(newId); - } - } - if (newId) { - component.version = newId.version; - component.scope = newId.scope; - this.consumer.bitMap.updateComponentId(newId); - component.componentMap = this.consumer.bitMap.getComponent(newId); + component.version = newId.version; + component.scope = newId.scope; + this.consumer.bitMap.updateComponentId(newId); + component.componentMap = this.consumer.bitMap.getComponent(newId); + } } } diff --git a/src/consumer/component/consumer-component.ts b/src/consumer/component/consumer-component.ts index b7bcc7aa9c42..3701f2eb2b9b 100644 --- a/src/consumer/component/consumer-component.ts +++ b/src/consumer/component/consumer-component.ts @@ -19,7 +19,7 @@ import ComponentWithDependencies from '../../scope/component-dependencies'; import { ScopeListItem } from '../../scope/models/model-component'; import Version, { Log } from '../../scope/models/version'; import { pathNormalizeToLinux } from '../../utils'; -import { PathLinux, PathOsBased, PathOsBasedAbsolute, PathOsBasedRelative } from '../../utils/path'; +import { PathLinux, PathOsBased, PathOsBasedRelative } from '../../utils/path'; import ComponentMap from '../bit-map/component-map'; import { IgnoredDirectory } from '../component-ops/add-components/exceptions/ignored-directory'; import ComponentsPendingImport from '../component-ops/exceptions/components-pending-import'; @@ -496,19 +496,15 @@ export default class Component { return this.fromObject(object); } - // eslint-disable-next-line complexity static async loadFromFileSystem({ - bitDir, componentMap, id, consumer, }: { - bitDir: PathOsBasedAbsolute; componentMap: ComponentMap; id: BitId; consumer: Consumer; }): Promise { - const consumerPath = consumer.getPath(); const workspaceConfig: ILegacyWorkspaceConfig = consumer.config; const modelComponent = await consumer.scope.getModelComponentIfExist(id); const componentFromModel = await consumer.loadComponentFromModelIfExist(id); @@ -518,8 +514,8 @@ export default class Component { if (!inScopeWithAnyVersion) throw new ComponentsPendingImport(); } const deprecated = componentFromModel ? componentFromModel.deprecated : false; - const componentDir = componentMap.getComponentDir(); - if (!fs.existsSync(bitDir)) throw new ComponentNotFoundInPath(bitDir); + const compDirAbs = path.join(consumer.getPath(), componentMap.getComponentDir()); + if (!fs.existsSync(compDirAbs)) throw new ComponentNotFoundInPath(compDirAbs); // Load the base entry from the root dir in map file in case it was imported using -path // Or created using bit create so we don't want all the path but only the relative one @@ -527,11 +523,7 @@ export default class Component { // (like dependencies) logger.trace(`consumer-component.loadFromFileSystem, start loading config ${id.toString()}`); const componentConfig = await ComponentConfig.load({ - consumer, componentId: id, - componentDir, - workspaceDir: consumerPath, - workspaceConfig, }); logger.trace(`consumer-component.loadFromFileSystem, finish loading config ${id.toString()}`); // by default, imported components are not written with bit.json file. @@ -556,7 +548,7 @@ export default class Component { ); const packageJsonFile = (componentConfig && componentConfig.packageJsonFile) || undefined; const packageJsonChangedProps = componentFromModel ? componentFromModel.packageJsonChangedProps : undefined; - const files = await getLoadedFilesHarmony(consumer, componentMap, id, bitDir); + const files = await getLoadedFiles(consumer, componentMap, id, compDirAbs); const docsP = _getDocsForFiles(files, consumer.componentFsCache); const docs = await Promise.all(docsP); const flattenedDocs = docs ? R.flatten(docs) : []; @@ -593,7 +585,7 @@ export default class Component { } } -async function getLoadedFilesHarmony( +async function getLoadedFiles( consumer: Consumer, componentMap: ComponentMap, id: BitId, diff --git a/src/consumer/config/component-config.ts b/src/consumer/config/component-config.ts index 5f10a7b4bbc5..4ac6ac05de53 100644 --- a/src/consumer/config/component-config.ts +++ b/src/consumer/config/component-config.ts @@ -1,18 +1,15 @@ import mapSeries from 'p-map-series'; import R from 'ramda'; -import { Consumer } from '..'; import { BitId } from '../../bit-id'; import { DEFAULT_REGISTRY_DOMAIN_PREFIX } from '../../constants'; import ShowDoctorError from '../../error/show-doctor-error'; import logger from '../../logger/logger'; import filterObject from '../../utils/filter-object'; -import { PathOsBasedAbsolute, PathOsBasedRelative } from '../../utils/path'; import Component from '../component/consumer-component'; import PackageJsonFile from '../component/package-json-file'; import AbstractConfig from './abstract-config'; import { ComponentOverridesData } from './component-overrides'; import { ExtensionDataList } from './extension-data'; -import { ILegacyWorkspaceConfig } from './legacy-workspace-config-interface'; type ConfigProps = { lang?: string; @@ -83,240 +80,25 @@ export default class ComponentConfig extends AbstractConfig { return ExtensionDataList.fromArray(this.extensions); } - static fromPlainObject(object: Record): ComponentConfig { - const { lang, bindingPrefix, extensions, overrides } = object; - let parsedExtensions = new ExtensionDataList(); - if (!(extensions instanceof ExtensionDataList)) { - if (Array.isArray(extensions)) { - parsedExtensions = ExtensionDataList.fromArray(extensions); - } else { - parsedExtensions = ExtensionDataList.fromConfigObject(extensions); - } - } - return new ComponentConfig({ - extensions: parsedExtensions, - defaultScope: object.defaultScope, - lang, - bindingPrefix, - overrides, - }); - - // TODO: run runOnLoadEvent - } - - static fromComponent(component: Component): ComponentConfig { - return new ComponentConfig({ - // @ts-ignore - version: component.version, - scope: component.scope, - lang: component.lang, - bindingPrefix: component.bindingPrefix, - overrides: component.overrides.componentOverridesData, - }); - - // TODO: run runOnLoadEvent - } - mergeWithComponentData(component: Component) { this.bindingPrefix = this.bindingPrefix || component.bindingPrefix; this.lang = this.lang || component.lang; } - /** - * Use the workspaceConfig as a base. Override values if exist in componentConfig - * This only used for legacy props that were defined in the root like compiler / tester - */ - static mergeWithWorkspaceRootConfigs( - consumer: Consumer, - componentId: BitId, - componentConfig: Record, - workspaceConfig: ILegacyWorkspaceConfig | undefined - ): ComponentConfig { - const plainWorkspaceConfig = workspaceConfig ? workspaceConfig._legacyPlainObject() : undefined; - - let legacyWorkspaceConfigToMerge = {}; - if (plainWorkspaceConfig) { - legacyWorkspaceConfigToMerge = filterObject(plainWorkspaceConfig, (val, key) => key !== 'overrides'); - } - const componentConfigFromWorkspaceToMerge = workspaceConfig?.getComponentConfig(componentId) || {}; - const defaultOwner = workspaceConfig?.defaultOwner; - - if (defaultOwner && defaultOwner !== DEFAULT_REGISTRY_DOMAIN_PREFIX) { - componentConfigFromWorkspaceToMerge.bindingPrefix = defaultOwner.startsWith('@') - ? defaultOwner - : `@${defaultOwner}`; - } - const mergedObject = R.mergeAll([ - legacyWorkspaceConfigToMerge, - componentConfigFromWorkspaceToMerge, - componentConfig, - ]); - mergedObject.extensions = ExtensionDataList.fromConfigObject(mergedObject.extensions); - // Do not try to load extension for itself (usually happen when using '*' pattern) - mergedObject.extensions = mergedObject.extensions.remove(componentId); - mergedObject.defaultScope = componentConfigFromWorkspaceToMerge?.defaultScope || workspaceConfig?.defaultScope; - return ComponentConfig.fromPlainObject(mergedObject); - } - - /** - * component config is written by default to package.json inside "bit" property. - * in case "eject-conf" was running or the component was imported with "--conf" flag, the - * bit.json is written as well. - * - * @param {*} componentDir root component directory, needed for loading package.json file. - * in case a component is authored, leave this param empty to not load the project package.json - * @param {*} workspaceConfig - */ - static async loadConfigFromFolder({ - componentDir, - workspaceDir, - }: { - componentDir: PathOsBasedAbsolute | undefined; - workspaceDir: PathOsBasedAbsolute; - }): Promise<{ componentHasWrittenConfig: boolean; config: any; packageJsonFile: any; bitJsonPath: string }> { - let bitJsonPath; - let componentHasWrittenConfig = false; - let packageJsonFile; - - if (componentDir) { - bitJsonPath = AbstractConfig.composeBitJsonPath(componentDir); - } - const loadBitJson = async () => { - if (!bitJsonPath) { - return {}; - } - try { - const file = await AbstractConfig.loadJsonFileIfExist(bitJsonPath); - if (file) { - componentHasWrittenConfig = true; - return file; - } - return {}; - } catch (e: any) { - throw new ShowDoctorError( - `bit.json at "${bitJsonPath}" is not a valid JSON file, re-import the component with "--conf" flag to recreate it` - ); - } - }; - const loadPackageJson = async (): Promise => { - if (!componentDir) return {}; - try { - const file = await PackageJsonFile.load(workspaceDir, componentDir); - packageJsonFile = file; - const packageJsonObject = file.fileExist ? file.packageJsonObject : undefined; - const packageJsonHasConfig = Boolean(packageJsonObject && packageJsonObject.bit); - if (packageJsonHasConfig) { - const packageJsonConfig = packageJsonObject?.bit; - componentHasWrittenConfig = true; - return packageJsonConfig; - } - return {}; - } catch (e: any) { - throw new ShowDoctorError( - `package.json at ${AbstractConfig.composePackageJsonPath( - componentDir - )} is not a valid JSON file, consider to re-import the file to re-generate the file` - ); - } - }; - - const [bitJsonConfig, packageJsonConfig] = await Promise.all([loadBitJson(), loadPackageJson()]); - // in case of conflicts, bit.json wins package.json - const config = Object.assign(packageJsonConfig, bitJsonConfig); - return { - config, - bitJsonPath, - packageJsonFile, - componentHasWrittenConfig, - }; - } - - /** - * component config is written by default to package.json inside "bit" property. - * in case "eject-conf" was running or the component was imported with "--conf" flag, the - * bit.json is written as well. - * - * @param {*} componentDir root component directory, needed for loading package.json file. - * in case a component is authored, leave this param empty to not load the project package.json - * @param {*} workspaceConfig - */ - static async load({ - consumer, - componentId, - componentDir, - workspaceDir, - workspaceConfig, - }: { - consumer: Consumer; - componentId: BitId; - componentDir: PathOsBasedRelative | undefined; - workspaceDir: PathOsBasedRelative; - workspaceConfig: ILegacyWorkspaceConfig; - }): Promise { - let componentConfig; - // Harmony project - // TODO: consider loading legacy components in vendor folder as legacy instead of as harmony components - if (!workspaceConfig.isLegacy) { - const onLoadResults = await this.runOnLoadEvent(this.componentConfigLoadingRegistry, componentId); - const wsComponentConfig = onLoadResults[0]; - const defaultScope = wsComponentConfig.defaultScope; - const bindingPrefix = getBindingPrefixByDefaultScope(defaultScope); - componentConfig = new ComponentConfig({ - extensions: wsComponentConfig.extensions, - defaultScope, - bindingPrefix, - }); - // Legacy project - } else { - const { config, bitJsonPath, packageJsonFile, componentHasWrittenConfig } = await this.loadConfigFromFolder({ - componentDir, - workspaceDir, - }); - componentConfig = ComponentConfig.mergeWithWorkspaceRootConfigs(consumer, componentId, config, workspaceConfig); - - componentConfig.path = bitJsonPath; - componentConfig.componentHasWrittenConfig = componentHasWrittenConfig; - componentConfig.packageJsonFile = packageJsonFile; - - await this.runOnLegacyLoadEvent(this.componentConfigLegacyLoadingRegistry, componentId, componentConfig); - } + static async load({ componentId }: { componentId: BitId }): Promise { + const onLoadResults = await this.runOnLoadEvent(this.componentConfigLoadingRegistry, componentId); + const wsComponentConfig = onLoadResults[0]; + const defaultScope = wsComponentConfig.defaultScope; + const bindingPrefix = getBindingPrefixByDefaultScope(defaultScope); + const componentConfig = new ComponentConfig({ + extensions: wsComponentConfig.extensions, + defaultScope, + bindingPrefix, + }); - // @ts-ignore seems to be a bug in ts v3.7.x, it doesn't recognize Promise.all array correctly return componentConfig; } - /** - * Run all subscribers to the component config legacy load event - * - * @static - * @param {ConfigLegacyLoadRegistry} subscribers - * @param {BitId} id - * @param {*} config - * @memberof ComponentConfig - */ - static async runOnLegacyLoadEvent(subscribers: ConfigLegacyLoadRegistry, id: BitId, config: any) { - logger.debugAndAddBreadCrumb( - 'componentConfigLegacyLoad', - `running on legacy load even for component ${id.toString()}` - ); - try { - await mapSeries(Object.keys(subscribers), async (extId: string) => { - const func = subscribers[extId]; - return func(id, config); - }); - } catch (err: any) { - if (!ignoreLoadingExtensionsErrors) { - throw err; - } - // TODO: improve texts - logger.console(`\nfailed loading an extension for component ${id.toString()}, error is:`, 'warn', 'yellow'); - // TODO: this show an ugly error, we should somehow show a proper errors - logger.console(err, 'warn', 'yellow'); - logger.console('the error has been ignored', 'warn', 'yellow'); - logger.warn('extension on load event throw an error', err); - } - } - /** * Run all subscribers to the component config load event *