@teambit/importer 1.0.108 → 1.0.110

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,775 +0,0 @@
1
- import chalk from 'chalk';
2
- import R from 'ramda';
3
- import { BitError } from '@teambit/bit-error';
4
- import { LaneId } from '@teambit/lane-id';
5
- import pMapSeries from 'p-map-series';
6
- import { getRemoteBitIdsByWildcards } from '@teambit/legacy/dist/api/consumer/lib/list-scope';
7
- import { ComponentID, ComponentIdList } from '@teambit/component-id';
8
- import { Consumer } from '@teambit/legacy/dist/consumer';
9
- import loader from '@teambit/legacy/dist/cli/loader';
10
- import { BEFORE_IMPORT_ACTION } from '@teambit/legacy/dist/cli/loader/loader-messages';
11
- import GeneralError from '@teambit/legacy/dist/error/general-error';
12
- import logger from '@teambit/legacy/dist/logger/logger';
13
- import { Scope } from '@teambit/legacy/dist/scope';
14
- import { Lane, ModelComponent, Version } from '@teambit/legacy/dist/scope/models';
15
- import { getLatestVersionNumber, pathNormalizeToLinux } from '@teambit/legacy/dist/utils';
16
- import hasWildcard from '@teambit/legacy/dist/utils/string/has-wildcard';
17
- import Component from '@teambit/legacy/dist/consumer/component';
18
- import { applyModifiedVersion } from '@teambit/checkout';
19
- import {
20
- FileStatus,
21
- getMergeStrategyInteractive,
22
- MergeOptions,
23
- threeWayMerge,
24
- } from '@teambit/legacy/dist/consumer/versions-ops/merge-version';
25
- import { FilesStatus, MergeStrategy } from '@teambit/legacy/dist/consumer/versions-ops/merge-version/merge-version';
26
- import { MergeResultsThreeWay } from '@teambit/legacy/dist/consumer/versions-ops/merge-version/three-way-merge';
27
- import ComponentsPendingMerge from '@teambit/legacy/dist/consumer/component-ops/exceptions/components-pending-merge';
28
- import ScopeComponentsImporter from '@teambit/legacy/dist/scope/component-ops/scope-components-importer';
29
- import VersionDependencies, {
30
- multipleVersionDependenciesToConsumer,
31
- } from '@teambit/legacy/dist/scope/version-dependencies';
32
- import { GraphMain } from '@teambit/graph';
33
- import { Workspace } from '@teambit/workspace';
34
- import { ComponentWriterMain, ComponentWriterResults, ManyComponentsWriterParams } from '@teambit/component-writer';
35
- import { LATEST_VERSION } from '@teambit/component-version';
36
- import { EnvsMain } from '@teambit/envs';
37
- import { compact } from 'lodash';
38
-
39
- export type ImportOptions = {
40
- ids: string[]; // array might be empty
41
- verbose?: boolean;
42
- merge?: boolean;
43
- mergeStrategy?: MergeStrategy;
44
- filterEnvs?: string[];
45
- writeToPath?: string;
46
- writeConfig?: boolean;
47
- override?: boolean;
48
- installNpmPackages: boolean; // default: true
49
- writeConfigFiles: boolean; // default: true
50
- objectsOnly?: boolean;
51
- saveDependenciesAsComponents?: boolean;
52
- importDependenciesDirectly?: boolean; // default: false, normally it imports them as packages, not as imported
53
- importDependents?: boolean;
54
- fromOriginalScope?: boolean; // default: false, otherwise, it fetches flattened dependencies from their dependents
55
- saveInLane?: boolean; // save the imported component on the current lane (won't be available on main)
56
- lanes?: {
57
- laneIds: LaneId[];
58
- lanes: Lane[]; // it can be an empty array when a lane is a local lane and doesn't exist on the remote
59
- };
60
- allHistory?: boolean;
61
- fetchDeps?: boolean; // by default, if a component was tagged with > 0.0.900, it has the flattened-deps-graph in the object
62
- trackOnly?: boolean;
63
- includeDeprecated?: boolean;
64
- isLaneFromRemote?: boolean; // whether the `lanes.lane` object is coming directly from the remote.
65
- };
66
- type ComponentMergeStatus = {
67
- component: Component;
68
- mergeResults: MergeResultsThreeWay | null | undefined;
69
- };
70
- type ImportedVersions = { [id: string]: string[] };
71
- export type ImportStatus = 'added' | 'updated' | 'up to date';
72
- export type ImportDetails = {
73
- id: string;
74
- versions: string[];
75
- latestVersion: string | null;
76
- status: ImportStatus;
77
- filesStatus: FilesStatus | null | undefined;
78
- missingDeps: ComponentID[];
79
- deprecated: boolean;
80
- removed?: boolean;
81
- };
82
- export type ImportResult = {
83
- importedIds: ComponentID[];
84
- importedDeps: ComponentID[];
85
- writtenComponents?: Component[];
86
- importDetails: ImportDetails[];
87
- cancellationMessage?: string;
88
- installationError?: Error;
89
- compilationError?: Error;
90
- missingIds?: string[]; // in case the import is configured to not throw when missing
91
- };
92
-
93
- export default class ImportComponents {
94
- consumer: Consumer;
95
- scope: Scope;
96
- mergeStatus: { [id: string]: FilesStatus };
97
- private laneObjects: Lane[];
98
- private divergeData: Array<ModelComponent> = [];
99
- constructor(
100
- private workspace: Workspace,
101
- private graph: GraphMain,
102
- private componentWriter: ComponentWriterMain,
103
- private envs: EnvsMain,
104
- public options: ImportOptions
105
- ) {
106
- this.consumer = this.workspace.consumer;
107
- this.scope = this.consumer.scope;
108
- this.laneObjects = this.options.lanes ? (this.options.lanes.lanes as Lane[]) : [];
109
- }
110
-
111
- async importComponents(): Promise<ImportResult> {
112
- let result;
113
- loader.start(BEFORE_IMPORT_ACTION);
114
- const startTime = process.hrtime();
115
- this.options.saveDependenciesAsComponents = this.consumer.config._saveDependenciesAsComponents;
116
- if (this.options.lanes && !this.options.ids.length) {
117
- result = await this.importObjectsOnLane();
118
- loader.succeed(BEFORE_IMPORT_ACTION, startTime);
119
- return result;
120
- }
121
- if (this.options.ids.length) {
122
- result = await this.importSpecificComponents();
123
- loader.succeed(BEFORE_IMPORT_ACTION, startTime);
124
- return result;
125
- }
126
- result = await this.importAccordingToBitMap();
127
- loader.succeed(BEFORE_IMPORT_ACTION, startTime);
128
- return result;
129
- }
130
-
131
- async importObjectsOnLane(): Promise<ImportResult> {
132
- if (!this.options.objectsOnly) {
133
- throw new Error(`importObjectsOnLane should have objectsOnly=true`);
134
- }
135
- if (this.laneObjects.length > 1) {
136
- throw new Error(`importObjectsOnLane does not support more than one lane`);
137
- }
138
- const lane = this.laneObjects.length ? this.laneObjects[0] : undefined;
139
- const bitIds: ComponentIdList = await this.getBitIds();
140
- lane
141
- ? logger.debug(`importObjectsOnLane, Lane: ${lane.id()}, Ids: ${bitIds.toString()}`)
142
- : logger.debug(`importObjectsOnLane, the lane does not exist on the remote. importing only the main components`);
143
- const beforeImportVersions = await this._getCurrentVersions(bitIds);
144
- const versionDependenciesArr = await this._importComponentsObjects(bitIds, {
145
- lane,
146
- });
147
-
148
- // merge the lane objects
149
- const mergeAllLanesResults = await pMapSeries(this.laneObjects, (laneObject) =>
150
- this.scope.sources.mergeLane(laneObject, true)
151
- );
152
- const mergedLanes = mergeAllLanesResults.map((result) => result.mergeLane);
153
- await Promise.all(mergedLanes.map((mergedLane) => this.scope.lanes.saveLane(mergedLane)));
154
-
155
- return this.returnCompleteResults(beforeImportVersions, versionDependenciesArr);
156
- }
157
-
158
- private async returnCompleteResults(
159
- beforeImportVersions: ImportedVersions,
160
- versionDependenciesArr: VersionDependencies[],
161
- writtenComponents?: Component[],
162
- componentWriterResults?: ComponentWriterResults
163
- ): Promise<ImportResult> {
164
- const importDetails = await this._getImportDetails(beforeImportVersions, versionDependenciesArr);
165
- const missingIds: string[] = [];
166
- if (Object.keys(beforeImportVersions).length > versionDependenciesArr.length) {
167
- const importedComps = versionDependenciesArr.map((c) => c.component.id.toStringWithoutVersion());
168
- Object.keys(beforeImportVersions).forEach((compIdStr) => {
169
- const found = importedComps.includes(compIdStr);
170
- if (!found) missingIds.push(compIdStr);
171
- });
172
- }
173
-
174
- return {
175
- importedIds: versionDependenciesArr.map((v) => v.component.id).flat(),
176
- importedDeps: versionDependenciesArr.map((v) => v.allDependenciesIds).flat(),
177
- writtenComponents,
178
- importDetails,
179
- installationError: componentWriterResults?.installationError,
180
- compilationError: componentWriterResults?.compilationError,
181
- missingIds,
182
- };
183
- }
184
-
185
- async importSpecificComponents(): Promise<ImportResult> {
186
- logger.debug(`importSpecificComponents, Ids: ${this.options.ids.join(', ')}`);
187
- const bitIds: ComponentIdList = await this.getBitIds();
188
- const beforeImportVersions = await this._getCurrentVersions(bitIds);
189
- await this._throwForPotentialIssues(bitIds);
190
- const versionDependenciesArr = await this._importComponentsObjects(bitIds, {
191
- lane: this.laneObjects?.[0],
192
- });
193
- if (this.laneObjects && this.options.objectsOnly) {
194
- // merge the lane objects
195
- const mergeAllLanesResults = await pMapSeries(this.laneObjects, (laneObject) =>
196
- this.scope.sources.mergeLane(laneObject, true)
197
- );
198
- const mergedLanes = mergeAllLanesResults.map((result) => result.mergeLane);
199
- await Promise.all(mergedLanes.map((mergedLane) => this.scope.lanes.saveLane(mergedLane)));
200
- }
201
- let writtenComponents: Component[] = [];
202
- let componentWriterResults: ComponentWriterResults | undefined;
203
- if (!this.options.objectsOnly) {
204
- const components = await multipleVersionDependenciesToConsumer(versionDependenciesArr, this.scope.objects);
205
- await this._fetchDivergeData(components);
206
- this._throwForDivergedHistory();
207
- await this.throwForComponentsFromAnotherLane(components.map((c) => c.id));
208
- const filteredComponents = await this._filterComponentsByFilters(components);
209
- componentWriterResults = await this._writeToFileSystem(filteredComponents);
210
- await this._saveLaneDataIfNeeded(filteredComponents);
211
- writtenComponents = filteredComponents;
212
- }
213
-
214
- return this.returnCompleteResults(
215
- beforeImportVersions,
216
- versionDependenciesArr,
217
- writtenComponents,
218
- componentWriterResults
219
- );
220
- }
221
-
222
- private async _filterComponentsByFilters(components: Component[]): Promise<Component[]> {
223
- if (!this.options.filterEnvs) return components;
224
- const filteredP = components.map(async (component) => {
225
- // If the id was requested explicitly, we don't want to filter it out
226
- if (this.options.ids) {
227
- if (
228
- this.options.ids.includes(component.id.toStringWithoutVersion()) ||
229
- this.options.ids.includes(component.id.toString())
230
- ) {
231
- return component;
232
- }
233
- }
234
- const currentEnv = await this.envs.calculateEnvIdFromExtensions(component.extensions);
235
- const currentEnvWithoutVersion = currentEnv.split('@')[0];
236
- if (
237
- this.options.filterEnvs?.includes(currentEnv) ||
238
- this.options.filterEnvs?.includes(currentEnvWithoutVersion)
239
- ) {
240
- return component;
241
- }
242
- return undefined;
243
- });
244
- const filtered = compact(await Promise.all(filteredP));
245
- return filtered;
246
- }
247
-
248
- async _fetchDivergeData(components: Component[]) {
249
- if (this.options.objectsOnly) {
250
- // no need for it when importing objects only. if it's enabled, in case when on a lane and a non-lane
251
- // component is in bitmap using an older version, it throws "getDivergeData: unable to find Version X of Y"
252
- return;
253
- }
254
- await Promise.all(
255
- components.map(async (component) => {
256
- const modelComponent = await this.scope.getModelComponent(component.id);
257
- await modelComponent.setDivergeData(this.scope.objects, undefined, false);
258
- this.divergeData.push(modelComponent);
259
- })
260
- );
261
- }
262
-
263
- _throwForDivergedHistory() {
264
- if (this.options.merge || this.options.objectsOnly) return;
265
- const divergedComponents = this.divergeData.filter((modelComponent) =>
266
- modelComponent.getDivergeData().isDiverged()
267
- );
268
- if (divergedComponents.length) {
269
- const divergeData = divergedComponents.map((modelComponent) => ({
270
- id: modelComponent.id(),
271
- snapsLocal: modelComponent.getDivergeData().snapsOnSourceOnly.length,
272
- snapsRemote: modelComponent.getDivergeData().snapsOnTargetOnly.length,
273
- }));
274
- throw new ComponentsPendingMerge(divergeData);
275
- }
276
- }
277
-
278
- private async throwForComponentsFromAnotherLane(bitIds: ComponentID[]) {
279
- if (this.options.objectsOnly) return;
280
- const currentLaneId = this.workspace.getCurrentLaneId();
281
- const currentRemoteLane = currentLaneId
282
- ? this.options.lanes?.lanes.find((l) => l.toLaneId().isEqual(currentLaneId))
283
- : undefined;
284
- const currentLane = await this.workspace.getCurrentLaneObject();
285
- const idsFromAnotherLane: ComponentID[] = [];
286
- if (currentRemoteLane) {
287
- await Promise.all(
288
- bitIds.map(async (bitId) => {
289
- const isOnCurrentLane =
290
- (await this.scope.isPartOfLaneHistory(bitId, currentRemoteLane)) ||
291
- (currentLane && (await this.scope.isPartOfLaneHistory(bitId, currentLane))) ||
292
- (await this.scope.isPartOfMainHistory(bitId));
293
- if (!isOnCurrentLane) idsFromAnotherLane.push(bitId);
294
- })
295
- );
296
- } else {
297
- await Promise.all(
298
- bitIds.map(async (bitId) => {
299
- const isIdOnMain = await this.scope.isPartOfMainHistory(bitId);
300
- if (!isIdOnMain) idsFromAnotherLane.push(bitId);
301
- })
302
- );
303
- }
304
- if (idsFromAnotherLane.length) {
305
- throw new BitError(`unable to import the following component(s) as they belong to other lane(s):
306
- ${idsFromAnotherLane.map((id) => id.toString()).join(', ')}
307
- if you need this specific snap, find the lane this snap is belong to, then run "bit lane merge <lane-id> [component-id]" to merge this component from the lane.
308
- `);
309
- }
310
- }
311
-
312
- private async _importComponentsObjects(
313
- ids: ComponentIdList,
314
- {
315
- fromOriginalScope = false,
316
- lane,
317
- ignoreMissingHead = false,
318
- }: {
319
- fromOriginalScope?: boolean;
320
- lane?: Lane;
321
- ignoreMissingHead?: boolean;
322
- }
323
- ): Promise<VersionDependencies[]> {
324
- const scopeComponentsImporter = ScopeComponentsImporter.getInstance(this.scope);
325
- await scopeComponentsImporter.importWithoutDeps(ids.toVersionLatest(), {
326
- cache: false,
327
- lane,
328
- includeVersionHistory: true,
329
- fetchHeadIfLocalIsBehind: !this.options.allHistory,
330
- collectParents: this.options.allHistory,
331
- // in case a user is merging a lane into a new workspace, then, locally main has head, but remotely the head is
332
- // empty, until it's exported. going to the remote and asking this component will throw an error if ignoreMissingHead is false
333
- ignoreMissingHead: true,
334
- includeUnexported: this.options.isLaneFromRemote,
335
- reason: `of their latest on ${lane ? `lane ${lane.id()}` : 'main'}`,
336
- });
337
-
338
- loader.start(`import ${ids.length} components with their dependencies (if missing)`);
339
- const results = fromOriginalScope
340
- ? await scopeComponentsImporter.importManyFromOriginalScopes(ids)
341
- : await scopeComponentsImporter.importMany({
342
- ids,
343
- ignoreMissingHead,
344
- lane,
345
- preferDependencyGraph: !this.options.fetchDeps,
346
- // when user is running "bit import", we want to re-fetch if it wasn't built. todo: check if this can be disabled when not needed
347
- reFetchUnBuiltVersion: true,
348
- // it's possible that .bitmap is not in sync and has local tags that don't exist on the remote. later, we
349
- // add them to "missingIds" of "importResult" and show them to the user
350
- throwForSeederNotFound: false,
351
- reason: this.options.fetchDeps
352
- ? 'for getting all dependencies'
353
- : `for getting dependencies of components that don't have dependency-graph`,
354
- });
355
-
356
- return results;
357
- }
358
-
359
- /**
360
- * consider the following use cases:
361
- * 1) no ids were provided. it should import all the lanes components objects AND main components objects
362
- * (otherwise, if main components are not imported and are missing, then bit-status complains about it)
363
- * 2) ids are provided with wildcards. we assume the user wants only the ids that are available on the lane.
364
- * because a user may entered "bit import scope/*" and this scope has many component on the lane and many not on the lane.
365
- * we want to bring only the components on the lane.
366
- * 3) ids are provided without wildcards. here, the user knows exactly what's needed and it's ok to get the ids from
367
- * main if not found on the lane.
368
- */
369
- private async getBitIdsForLanes(): Promise<ComponentID[]> {
370
- if (!this.options.lanes) {
371
- throw new Error(`getBitIdsForLanes: this.options.lanes must be set`);
372
- }
373
- const bitIdsFromLane = ComponentIdList.fromArray(this.laneObjects.flatMap((lane) => lane.toBitIds()));
374
-
375
- if (!this.options.ids.length) {
376
- const bitMapIds = this.consumer.bitMap.getAllBitIds();
377
- const bitMapIdsToImport = bitMapIds.filter((id) => id.hasScope() && !bitIdsFromLane.has(id));
378
- bitIdsFromLane.push(...bitMapIdsToImport);
379
-
380
- return bitIdsFromLane;
381
- }
382
-
383
- const idsWithWildcard = this.options.ids.filter((id) => hasWildcard(id));
384
- const idsWithoutWildcard = this.options.ids.filter((id) => !hasWildcard(id));
385
- const idsWithoutWildcardPreferFromLane = idsWithoutWildcard.map((idStr) => {
386
- const id = ComponentID.fromString(idStr);
387
- const fromLane = bitIdsFromLane.searchWithoutVersion(id);
388
- return fromLane && !id.hasVersion() ? fromLane : id;
389
- });
390
-
391
- const bitIds: ComponentID[] = [...idsWithoutWildcardPreferFromLane];
392
-
393
- if (!idsWithWildcard) {
394
- return bitIds;
395
- }
396
-
397
- await pMapSeries(idsWithWildcard, async (idStr: string) => {
398
- const idsFromRemote = await getRemoteBitIdsByWildcards(idStr, this.options.includeDeprecated);
399
- const existingOnLanes = idsFromRemote.filter((id) => bitIdsFromLane.hasWithoutVersion(id));
400
- if (!existingOnLanes.length) {
401
- throw new BitError(`the id with the the wildcard "${idStr}" has been parsed to multiple component ids.
402
- however, none of them existing on the lane "${this.laneObjects.map((l) => l.name).join(', ')}"
403
- in case you intend to import these components from main, please run the following:
404
- bit import ${idsFromRemote.map((id) => id.toStringWithoutVersion()).join(' ')}`);
405
- }
406
- bitIds.push(...existingOnLanes);
407
- });
408
-
409
- return bitIds;
410
- }
411
-
412
- private async getBitIdsForNonLanes() {
413
- const bitIds: ComponentID[] = [];
414
- await Promise.all(
415
- this.options.ids.map(async (idStr: string) => {
416
- if (hasWildcard(idStr)) {
417
- const ids = await getRemoteBitIdsByWildcards(idStr, this.options.includeDeprecated);
418
- loader.start(BEFORE_IMPORT_ACTION); // it stops the previous loader of BEFORE_REMOTE_LIST
419
- bitIds.push(...ids);
420
- } else {
421
- bitIds.push(ComponentID.fromString(idStr)); // we don't support importing without a scope name
422
- }
423
- })
424
- );
425
-
426
- return bitIds;
427
- }
428
-
429
- private async getBitIds(): Promise<ComponentIdList> {
430
- const bitIds: ComponentID[] = this.options.lanes
431
- ? await this.getBitIdsForLanes()
432
- : await this.getBitIdsForNonLanes();
433
- if (this.options.importDependenciesDirectly || this.options.importDependents) {
434
- if (this.options.importDependenciesDirectly) {
435
- const dependenciesIds = await this.getFlattenedDepsUnique(bitIds);
436
- bitIds.push(...dependenciesIds);
437
- }
438
- if (this.options.importDependents) {
439
- const graph = await this.graph.getGraphIds();
440
- const targetCompIds = await this.workspace.resolveMultipleComponentIds(bitIds);
441
- const sourceIds = await this.workspace.listIds();
442
- const ids = graph.findIdsFromSourcesToTargets(sourceIds, targetCompIds);
443
- logger.debug(
444
- `found ${ids.length} component for --dependents flag`,
445
- ids.map((id) => id.toString())
446
- );
447
- bitIds.push(...ids.map((id) => id));
448
- }
449
- }
450
- return ComponentIdList.uniqFromArray(bitIds);
451
- }
452
-
453
- private async getFlattenedDepsUnique(bitIds: ComponentID[]): Promise<ComponentID[]> {
454
- const remoteComps = await this.scope.scopeImporter.getManyRemoteComponents(bitIds);
455
- const versions = remoteComps.getVersions();
456
- const getFlattened = (): ComponentIdList => {
457
- if (versions.length === 1) return versions[0].flattenedDependencies;
458
- const flattenedDeps = versions.map((v) => v.flattenedDependencies).flat();
459
- return ComponentIdList.uniqFromArray(flattenedDeps);
460
- };
461
- const flattened = getFlattened();
462
- const withLatest = this.removeMultipleVersionsKeepLatest(flattened);
463
- return withLatest;
464
- }
465
-
466
- private removeMultipleVersionsKeepLatest(flattened: ComponentIdList): ComponentID[] {
467
- const grouped = flattened.toGroupByIdWithoutVersion();
468
- const latestVersions = Object.keys(grouped).map((key) => {
469
- const ids = grouped[key];
470
- if (ids.length === 1) return ids[0];
471
- const latest = getLatestVersionNumber(ids, ids[0].changeVersion(LATEST_VERSION));
472
- return latest;
473
- });
474
-
475
- return latestVersions;
476
- }
477
-
478
- async importAccordingToBitMap(): Promise<ImportResult> {
479
- this.options.objectsOnly = !this.options.merge && !this.options.override;
480
- const componentsIdsToImport = this.getIdsToImportFromBitmap();
481
- const emptyResult = {
482
- importedIds: [],
483
- importedDeps: [],
484
- importDetails: [],
485
- };
486
- if (R.isEmpty(componentsIdsToImport)) {
487
- return emptyResult;
488
- }
489
- await this._throwForModifiedOrNewComponents(componentsIdsToImport);
490
- const beforeImportVersions = await this._getCurrentVersions(componentsIdsToImport);
491
- if (!componentsIdsToImport.length) {
492
- return emptyResult;
493
- }
494
- if (!this.options.objectsOnly) {
495
- throw new Error(`bit import with no ids and --merge flag was not implemented yet`);
496
- }
497
- const versionDependenciesArr = await this._importComponentsObjects(componentsIdsToImport, {
498
- fromOriginalScope: this.options.fromOriginalScope,
499
- });
500
- let writtenComponents: Component[] = [];
501
- let componentWriterResults: ComponentWriterResults | undefined;
502
- if (!this.options.objectsOnly) {
503
- const components = await multipleVersionDependenciesToConsumer(versionDependenciesArr, this.scope.objects);
504
- componentWriterResults = await this._writeToFileSystem(components);
505
- writtenComponents = components;
506
- }
507
-
508
- return this.returnCompleteResults(
509
- beforeImportVersions,
510
- versionDependenciesArr,
511
- writtenComponents,
512
- componentWriterResults
513
- );
514
- }
515
-
516
- private getIdsToImportFromBitmap() {
517
- const allIds = this.consumer.bitMap.getAllBitIdsFromAllLanes();
518
- return ComponentIdList.fromArray(allIds.filter((id) => id.hasScope()));
519
- }
520
-
521
- async _getCurrentVersions(ids: ComponentIdList): Promise<ImportedVersions> {
522
- const versionsP = ids.map(async (id) => {
523
- const modelComponent = await this.consumer.scope.getModelComponentIfExist(id.changeVersion(undefined));
524
- const idStr = id.toStringWithoutVersion();
525
- if (!modelComponent) return [idStr, []];
526
- return [idStr, modelComponent.listVersions()];
527
- });
528
- const versions = await Promise.all(versionsP);
529
- return R.fromPairs(versions);
530
- }
531
-
532
- /**
533
- * get import details, includes the diff between the versions array before import and after import
534
- */
535
- async _getImportDetails(
536
- currentVersions: ImportedVersions,
537
- components: VersionDependencies[]
538
- ): Promise<ImportDetails[]> {
539
- const detailsP = components.map(async (component) => {
540
- const id = component.component.id;
541
- const idStr = id.toStringWithoutVersion();
542
- const beforeImportVersions = currentVersions[idStr];
543
- if (!beforeImportVersions) {
544
- throw new Error(
545
- `_getImportDetails failed finding ${idStr} in currentVersions, which has ${Object.keys(currentVersions).join(
546
- ', '
547
- )}`
548
- );
549
- }
550
- const modelComponent = await this.consumer.scope.getModelComponentIfExist(id);
551
- if (!modelComponent) throw new BitError(`imported component ${idStr} was not found in the model`);
552
- const afterImportVersions = modelComponent.listVersions();
553
- const versionDifference: string[] = R.difference(afterImportVersions, beforeImportVersions);
554
- const getStatus = (): ImportStatus => {
555
- if (!versionDifference.length) return 'up to date';
556
- if (!beforeImportVersions.length) return 'added';
557
- return 'updated';
558
- };
559
- const filesStatus = this.mergeStatus && this.mergeStatus[idStr] ? this.mergeStatus[idStr] : null;
560
- const deprecated = await modelComponent.isDeprecated(this.scope.objects);
561
- const removed = await component.component.component.isRemoved(this.scope.objects);
562
- const latestVersion = modelComponent.getHeadRegardlessOfLaneAsTagOrHash(true);
563
- return {
564
- id: idStr,
565
- versions: versionDifference,
566
- latestVersion: versionDifference.includes(latestVersion) ? latestVersion : null,
567
- status: getStatus(),
568
- filesStatus,
569
- missingDeps: this.options.fetchDeps ? component.getMissingDependencies() : [],
570
- deprecated,
571
- removed,
572
- };
573
- });
574
- const importDetails: ImportDetails[] = await Promise.all(detailsP);
575
-
576
- return importDetails;
577
- }
578
-
579
- async _throwForPotentialIssues(ids: ComponentIdList): Promise<void> {
580
- await this._throwForModifiedOrNewComponents(ids);
581
- this._throwForDifferentComponentWithSameName(ids);
582
- }
583
-
584
- async _throwForModifiedOrNewComponents(ids: ComponentIdList): Promise<void> {
585
- // the typical objectsOnly option is when a user cloned a project with components tagged to the source code, but
586
- // doesn't have the model objects. in that case, calling getComponentStatusById() may return an error as it relies
587
- // on the model objects when there are dependencies
588
- if (this.options.override || this.options.objectsOnly || this.options.merge || this.options.trackOnly) return;
589
- const componentsStatuses = await this.consumer.getManyComponentsStatuses(ids);
590
- const modifiedComponents = componentsStatuses
591
- .filter(({ status }) => status.modified || status.newlyCreated)
592
- .map((c) => c.id);
593
- if (modifiedComponents.length) {
594
- throw new GeneralError(
595
- chalk.yellow(
596
- `unable to import the following components due to local changes, use --merge flag to merge your local changes or --override to override them\n${modifiedComponents.join(
597
- '\n'
598
- )} `
599
- )
600
- );
601
- }
602
- }
603
-
604
- /**
605
- * Model Component id() calculation uses id.toString() for the hash.
606
- * If an imported component has scopereadonly name equals to a local name, both will have the exact same
607
- * hash and they'll override each other.
608
- */
609
- _throwForDifferentComponentWithSameName(ids: ComponentIdList): void {
610
- ids.forEach((id: ComponentID) => {
611
- const existingId = this.consumer.getParsedIdIfExist(id.toStringWithoutVersion());
612
- if (existingId && !existingId.hasScope()) {
613
- throw new GeneralError(`unable to import ${id.toString()}. the component name conflicted with your local component with the same name.
614
- it's fine to have components with the same name as long as their scope names are different.
615
- Make sure to export your component first to get a scope and then try importing again`);
616
- }
617
- });
618
- }
619
-
620
- async _getMergeStatus(component: Component): Promise<ComponentMergeStatus> {
621
- const componentStatus = await this.consumer.getComponentStatusById(component.id);
622
- const mergeStatus: ComponentMergeStatus = { component, mergeResults: null };
623
- if (!componentStatus.modified) return mergeStatus;
624
- const componentModel = await this.consumer.scope.getModelComponent(component.id);
625
- const existingBitMapBitId = this.consumer.bitMap.getComponentId(component.id, { ignoreVersion: true });
626
- // TODO: check if we really need the { loadExtensions: true } here
627
- const fsComponent = await this.consumer.loadComponent(existingBitMapBitId, { loadExtensions: true });
628
- const currentlyUsedVersion = existingBitMapBitId.version;
629
- // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
630
- const baseComponent: Version = await componentModel.loadVersion(currentlyUsedVersion, this.consumer.scope.objects);
631
- const otherComponent: Version = await componentModel.loadVersion(
632
- // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
633
- component.id.version,
634
- this.consumer.scope.objects
635
- );
636
- const mergeResults = await threeWayMerge({
637
- consumer: this.consumer,
638
- otherComponent,
639
- otherLabel: component.id.version as string,
640
- currentComponent: fsComponent,
641
- currentLabel: `${currentlyUsedVersion} modified`,
642
- baseComponent,
643
- });
644
- mergeStatus.mergeResults = mergeResults;
645
- return mergeStatus;
646
- }
647
-
648
- /**
649
- * 1) when there are conflicts and the strategy is "ours", don't write the imported component to
650
- * the filesystem, only update bitmap.
651
- *
652
- * 2) when there are conflicts and the strategy is "theirs", override the local changes by the
653
- * imported component. (similar to --override)
654
- *
655
- * 3) when there is no conflict or there are conflicts and the strategy is manual, write the files
656
- * according to the merge result. (done by applyModifiedVersion())
657
- */
658
- _updateComponentFilesPerMergeStrategy(componentMergeStatus: ComponentMergeStatus): FilesStatus | null | undefined {
659
- const mergeResults = componentMergeStatus.mergeResults;
660
- if (!mergeResults) return null;
661
- const component = componentMergeStatus.component;
662
- const files = component.files;
663
-
664
- if (mergeResults.hasConflicts && this.options.mergeStrategy === MergeOptions.ours) {
665
- const filesStatus = {};
666
- // don't write the files to the filesystem, only bump the bitmap version.
667
- files.forEach((file) => {
668
- // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
669
- filesStatus[pathNormalizeToLinux(file.relative)] = FileStatus.unchanged;
670
- });
671
- this.consumer.bitMap.updateComponentId(component.id);
672
- this.consumer.bitMap.hasChanged = true;
673
- return filesStatus;
674
- }
675
- if (mergeResults.hasConflicts && this.options.mergeStrategy === MergeOptions.theirs) {
676
- const filesStatus = {};
677
- // the local changes will be overridden (as if the user entered --override flag for this component)
678
- files.forEach((file) => {
679
- // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
680
- filesStatus[pathNormalizeToLinux(file.relative)] = FileStatus.updated;
681
- });
682
- return filesStatus;
683
- }
684
- const { filesStatus, modifiedFiles } = applyModifiedVersion(
685
- component.files,
686
- mergeResults,
687
- this.options.mergeStrategy
688
- );
689
- component.files = modifiedFiles;
690
-
691
- return filesStatus;
692
- }
693
-
694
- /**
695
- * update the component files if they are modified and there is a merge strategy.
696
- * returns only the components that need to be written to the filesystem
697
- */
698
- async updateAllComponentsAccordingToMergeStrategy(components: Component[]): Promise<Component[]> {
699
- if (!this.options.merge) return components;
700
- const componentsStatusP = components.map((component: Component) => {
701
- return this._getMergeStatus(component);
702
- });
703
- const componentsStatus = await Promise.all(componentsStatusP);
704
- const componentWithConflict = componentsStatus.find(
705
- (component) => component.mergeResults && component.mergeResults.hasConflicts
706
- );
707
- if (componentWithConflict && !this.options.mergeStrategy) {
708
- this.options.mergeStrategy = await getMergeStrategyInteractive();
709
- }
710
- this.mergeStatus = {};
711
-
712
- const componentsToWrite = componentsStatus.map((componentStatus) => {
713
- const filesStatus: FilesStatus | null | undefined = this._updateComponentFilesPerMergeStrategy(componentStatus);
714
- const component = componentStatus.component;
715
- if (!filesStatus) return component;
716
- this.mergeStatus[component.id.toStringWithoutVersion()] = filesStatus;
717
- const unchangedFiles = Object.keys(filesStatus).filter((file) => filesStatus[file] === FileStatus.unchanged);
718
- if (unchangedFiles.length === Object.keys(filesStatus).length) {
719
- // all files are unchanged
720
- return null;
721
- }
722
- return component;
723
- });
724
- const removeNulls = R.reject(R.isNil);
725
- return removeNulls(componentsToWrite);
726
- }
727
-
728
- _shouldSaveLaneData(): boolean {
729
- if (this.options.objectsOnly) {
730
- return false;
731
- }
732
- return this.consumer.isOnLane();
733
- }
734
-
735
- async _saveLaneDataIfNeeded(components: Component[]): Promise<void> {
736
- if (!this._shouldSaveLaneData()) {
737
- return;
738
- }
739
- const currentLane = await this.consumer.getCurrentLaneObject();
740
- if (!currentLane) {
741
- return; // user on main
742
- }
743
- const idsFromRemoteLanes = ComponentIdList.fromArray(this.laneObjects.flatMap((lane) => lane.toBitIds()));
744
- await Promise.all(
745
- components.map(async (comp) => {
746
- const existOnRemoteLane = idsFromRemoteLanes.has(comp.id);
747
- if (!existOnRemoteLane && !this.options.saveInLane) {
748
- this.consumer.bitMap.setOnLanesOnly(comp.id, false);
749
- return;
750
- }
751
- const modelComponent = await this.scope.getModelComponent(comp.id);
752
- const ref = modelComponent.getRef(comp.id.version as string);
753
- if (!ref) throw new Error(`_saveLaneDataIfNeeded unable to get ref for ${comp.id.toString()}`);
754
- currentLane.addComponent({ id: comp.id, head: ref });
755
- })
756
- );
757
- await this.scope.lanes.saveLane(currentLane);
758
- }
759
-
760
- async _writeToFileSystem(components: Component[]): Promise<ComponentWriterResults> {
761
- const componentsToWrite = await this.updateAllComponentsAccordingToMergeStrategy(components);
762
- const manyComponentsWriterOpts: ManyComponentsWriterParams = {
763
- components: componentsToWrite,
764
- writeToPath: this.options.writeToPath,
765
- writeConfig: this.options.writeConfig,
766
- skipDependencyInstallation: !this.options.installNpmPackages,
767
- skipWriteConfigFiles: !this.options.writeConfigFiles,
768
- verbose: this.options.verbose,
769
- throwForExistingDir: !this.options.override,
770
- skipWritingToFs: this.options.trackOnly,
771
- reasonForBitmapChange: 'import',
772
- };
773
- return this.componentWriter.writeMany(manyComponentsWriterOpts);
774
- }
775
- }