@teambit/ci 1.0.487 → 1.0.488

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.
@@ -0,0 +1,128 @@
1
+ import chalk from 'chalk';
2
+ import type { CheckoutMain } from '@teambit/checkout';
3
+ import type { ComponentID } from '@teambit/component-id';
4
+ import { ComponentIdList } from '@teambit/component-id';
5
+ import type { LanesMain } from '@teambit/lanes';
6
+ import type { Logger } from '@teambit/logger';
7
+ import type { Workspace } from '@teambit/workspace';
8
+ import { capEntries } from './lane-sync-executor';
9
+
10
+ export type AdoptLaneNewComponentsDeps = {
11
+ workspace: Workspace;
12
+ lanes: LanesMain;
13
+ logger: Logger;
14
+ };
15
+
16
+ export type AdoptRetryDeps = AdoptLaneNewComponentsDeps & {
17
+ checkout: CheckoutMain;
18
+ reloadWorkspace: () => Promise<void>;
19
+ };
20
+
21
+ /**
22
+ * The lane merge (`sources.mergeLane` in the legacy scope) throws a plain Error with this wording
23
+ * when a lane component's objects are absent. A typed error needs a change outside the ci aspect;
24
+ * the stale-lane recovery of `bit ci pr` matches the same text.
25
+ */
26
+ export function isLaneMissingComponentError(err: unknown): boolean {
27
+ const msg = (err as Error)?.message ?? String(err ?? '');
28
+ return msg.includes('unable to merge lane') && msg.includes('was not found');
29
+ }
30
+
31
+ /**
32
+ * A versionless `.bitmap` entry reads as local and unexported, so a lane fetch drops its id and
33
+ * the lane merge fails with `the component … was not found`. Adopt the lane's version into each
34
+ * such entry, and import the adopted objects (`includeUnexported`, because the entries made them
35
+ * look local). The mutation is in memory; the caller owns the rollback on a failed retry.
36
+ * The returned ids already record the lane's version, so a retried switch skips their files; an
37
+ * importing caller must write them (`checkout --reset`).
38
+ */
39
+ export async function adoptNewComponentsTheLaneProvides(
40
+ laneName: string,
41
+ { workspace, lanes, logger }: AdoptLaneNewComponentsDeps
42
+ ): Promise<ComponentID[]> {
43
+ const newIds = await workspace.newComponentIds();
44
+ if (!newIds.length) return [];
45
+ const laneId = await lanes.parseLaneId(laneName);
46
+ if (laneId.isDefault()) return [];
47
+ const lane = await lanes.importLaneObject(laneId);
48
+ const newIdsList = ComponentIdList.fromArray(newIds);
49
+ const adoptedIds = lane
50
+ .toComponentIdsIncludeUpdateDependents()
51
+ .filter((laneCompId) => newIdsList.hasWithoutVersion(laneCompId));
52
+ if (!adoptedIds.length) return [];
53
+ logger.console(
54
+ chalk.blue(
55
+ `Adopting the lane's version for component(s) this workspace tracks as new: ` +
56
+ capEntries(adoptedIds.map((id) => id.toStringWithoutVersion())).join(', ')
57
+ )
58
+ );
59
+ const bitMap = workspace.consumer.bitMap;
60
+ adoptedIds.forEach((id) => {
61
+ bitMap.updateComponentId(id);
62
+ bitMap.setOnLanesOnly(id, true);
63
+ });
64
+ // cached component lists still reflect the pre-adoption `.bitmap`
65
+ workspace.clearAllComponentsCache();
66
+ await workspace.scope.legacyScope.scopeImporter.importMany({
67
+ ids: ComponentIdList.fromArray(adoptedIds),
68
+ lane,
69
+ includeUnexported: true,
70
+ includeUpdateDependents: true,
71
+ reason: `lane ${laneId.toString()} provides component(s) this workspace tracks as new`,
72
+ });
73
+ return adoptedIds;
74
+ }
75
+
76
+ /**
77
+ * Runs after a switch failed with the missing-component marker: adopt the lane's versions, then
78
+ * retry the switch once. Returns undefined on success, and the error to report on failure. A
79
+ * failed retry reloads the workspace from disk — the failed switch never wrote to it — so the
80
+ * in-memory adoption disappears completely (including `updatedIds` and the changed flag).
81
+ */
82
+ export async function adoptAndRetrySwitch(
83
+ laneName: string,
84
+ originalErr: Error,
85
+ doSwitch: () => Promise<unknown>,
86
+ writeAdoptedFiles: boolean | undefined,
87
+ deps: AdoptRetryDeps
88
+ ): Promise<Error | undefined> {
89
+ const { logger, reloadWorkspace } = deps;
90
+ const rollback = async (err: Error) => {
91
+ await reloadWorkspace().catch((reloadErr) => {
92
+ logger.console(
93
+ chalk.yellow(`Failed to reload the workspace after the adoption retry: ${reloadErr?.toString() ?? reloadErr}`)
94
+ );
95
+ });
96
+ return err;
97
+ };
98
+ const adopted = await adoptNewComponentsTheLaneProvides(laneName, deps).catch((adoptErr) => {
99
+ logger.console(chalk.yellow(`The adoption retry failed: ${adoptErr?.toString() ?? adoptErr}`));
100
+ return [];
101
+ });
102
+ if (!adopted.length) return rollback(originalErr);
103
+ try {
104
+ await doSwitch();
105
+ // the retried switch skips an id already at the lane's version, so its files are still unwritten
106
+ if (writeAdoptedFiles) await writeFilesKeepingConfig(adopted, deps);
107
+ return undefined;
108
+ } catch (retryErr: any) {
109
+ return rollback(retryErr);
110
+ }
111
+ }
112
+
113
+ /**
114
+ * `checkout --reset` writes the files of an entry that already records the target version, but its
115
+ * `resetConfig` also deletes the entry's `.bitmap` config — keep the config across the call.
116
+ */
117
+ async function writeFilesKeepingConfig(adopted: ComponentID[], { workspace, checkout }: AdoptRetryDeps) {
118
+ const bitMap = workspace.consumer.bitMap;
119
+ const entryConfig = (id: ComponentID) => bitMap.getComponentIfExist(id, { ignoreVersion: true })?.config;
120
+ const configs = adopted.map((id) => ({ id, config: entryConfig(id) }));
121
+ await checkout.checkout({ ids: adopted, reset: true, skipNpmInstall: true });
122
+ configs.forEach(({ id, config }) => {
123
+ const entry = bitMap.getComponentIfExist(id, { ignoreVersion: true });
124
+ if (!config || !entry || entry.config) return;
125
+ entry.config = config;
126
+ bitMap.markAsChanged();
127
+ });
128
+ }
@@ -1142,7 +1142,11 @@ export class LaneSyncExecutor {
1142
1142
  }
1143
1143
  }
1144
1144
 
1145
- const switchErr = await this.deps.ci.switchToLaneForSync(laneIdStr, { forceOurs: false, forceTheirs: true });
1145
+ const switchErr = await this.deps.ci.switchToLaneForSync(laneIdStr, {
1146
+ forceOurs: false,
1147
+ forceTheirs: true,
1148
+ writeAdoptedFiles: true,
1149
+ });
1146
1150
  if (switchErr) return switchErr;
1147
1151
 
1148
1152
  // Verify before the caller commits a `.bitmap` asserting this lane's content.