@teambit/merge-lanes 1.0.106 → 1.0.108

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.
package/last-merged.ts ADDED
@@ -0,0 +1,179 @@
1
+ import path from 'path';
2
+ import tempy from 'tempy';
3
+ import fs from 'fs-extra';
4
+ import yesno from 'yesno';
5
+ import chalk from 'chalk';
6
+ import BitMap from '@teambit/legacy/dist/consumer/bit-map';
7
+ import { PromptCanceled } from '@teambit/legacy/dist/prompts/exceptions';
8
+ import { ScopeMain } from '@teambit/scope';
9
+ import { Lane } from '@teambit/legacy/dist/scope/models';
10
+ import { StagedSnaps } from '@teambit/legacy/dist/scope/staged-snaps';
11
+ import { Consumer } from '@teambit/legacy/dist/consumer';
12
+ import { BitError } from '@teambit/bit-error';
13
+ import { BitObject } from '@teambit/legacy/dist/scope/objects';
14
+ import { Logger } from '@teambit/logger';
15
+ import { MergeAbortOpts } from './merge-abort.cmd';
16
+
17
+ const LAST_MERGED_LANE_FILENAME = 'lane';
18
+ const LAST_MERGED_BITMAP_FILENAME = 'bitmap';
19
+
20
+ type Snapshot = {
21
+ copyOfCurrentLane?: Lane;
22
+ copyOfBitmap: string;
23
+ copyOfWorkspaceJsonc: string;
24
+ copyOfStagedSnaps?: string;
25
+ copyOfStagedConfig?: string;
26
+ };
27
+
28
+ export class LastMerged {
29
+ constructor(private scope: ScopeMain, private consumer: Consumer, private logger: Logger) {}
30
+
31
+ get path() {
32
+ return this.scope.getLastMergedPath();
33
+ }
34
+
35
+ async takeSnapshot(currentLane?: Lane | null): Promise<Snapshot> {
36
+ const consumer = this.consumer;
37
+ const copyOfCurrentLane = currentLane ? currentLane.clone() : undefined;
38
+ const copyOfBitmap = tempy.file();
39
+ const copyOfWorkspaceJsonc = tempy.file();
40
+ let copyOfStagedSnaps: string | undefined;
41
+ await fs.copyFile(consumer.bitMap.mapPath, copyOfBitmap);
42
+ await fs.copyFile(consumer.config.path, copyOfWorkspaceJsonc);
43
+ if (!consumer.scope.stagedSnaps.isEmpty()) {
44
+ copyOfStagedSnaps = tempy.file();
45
+ await fs.copyFile(StagedSnaps.getPath(consumer.scope.path), copyOfStagedSnaps);
46
+ }
47
+ const stagedConfig = await this.scope.getStagedConfig();
48
+ let copyOfStagedConfig: string | undefined;
49
+ if (!stagedConfig.isEmpty()) {
50
+ copyOfStagedConfig = tempy.file();
51
+ await fs.copyFile(stagedConfig.filePath, copyOfStagedConfig);
52
+ }
53
+
54
+ return {
55
+ copyOfCurrentLane,
56
+ copyOfBitmap,
57
+ copyOfWorkspaceJsonc,
58
+ copyOfStagedSnaps,
59
+ copyOfStagedConfig,
60
+ };
61
+ }
62
+
63
+ async persistSnapshot(snapshot: Snapshot) {
64
+ const { copyOfCurrentLane, copyOfBitmap, copyOfWorkspaceJsonc, copyOfStagedSnaps, copyOfStagedConfig } = snapshot;
65
+ await fs.remove(this.path);
66
+ await fs.ensureDir(this.path);
67
+ await fs.copyFile(copyOfBitmap, this.getLastMergedBitmapPath());
68
+ await fs.copyFile(copyOfWorkspaceJsonc, this.getLastMergedWorkspacePath());
69
+ if (copyOfCurrentLane) {
70
+ const compressed = await copyOfCurrentLane.compress();
71
+ await fs.outputFile(this.getLastMergedLanePath(), compressed);
72
+ }
73
+ if (copyOfStagedSnaps) {
74
+ await fs.copyFile(copyOfStagedSnaps, this.getLastMergedStagedSnapsPath());
75
+ }
76
+ if (copyOfStagedConfig) {
77
+ await fs.copyFile(copyOfStagedConfig, this.getLastMergedStagedConfigPath());
78
+ }
79
+ }
80
+
81
+ async restoreFromLastMerged(mergeAbortOpts: MergeAbortOpts, currentLane?: Lane | null) {
82
+ if (!fs.pathExistsSync(this.path)) {
83
+ throw new BitError(`unable to abort the last lane-merge because "bit export" was running since then`);
84
+ }
85
+ const lastLane = await this.getLastMergedLaneContentIfExists();
86
+
87
+ if (!fs.pathExistsSync(this.getLastMergedBitmapPath())) {
88
+ throw new BitError(
89
+ `unable to abort the last lane-merge because the ${LAST_MERGED_BITMAP_FILENAME} is missing from ${this.path}`
90
+ );
91
+ }
92
+ if (!fs.pathExistsSync(this.getLastMergedWorkspacePath())) {
93
+ throw new BitError(
94
+ `unable to abort the last lane-merge because the workspace.jsonc is missing from ${this.path}`
95
+ );
96
+ }
97
+ if (currentLane) {
98
+ if (!lastLane) {
99
+ throw new BitError(
100
+ `unable to abort the last lane-merge because the ${LAST_MERGED_LANE_FILENAME} is missing from ${this.path}`
101
+ );
102
+ }
103
+ const laneFromBackup = await BitObject.parseObject(lastLane, LAST_MERGED_LANE_FILENAME);
104
+ await this.scope.legacyScope.objects.writeObjectsToTheFS([laneFromBackup]);
105
+ }
106
+ const previousBitmapBuffer = await fs.readFile(this.getLastMergedBitmapPath());
107
+ const previousBitmap = BitMap.loadFromContentWithoutLoadingFiles(previousBitmapBuffer, '', '', '');
108
+ const currentRootDirs = this.consumer.bitMap.getAllTrackDirs();
109
+ const previousRootDirs = previousBitmap.getAllTrackDirs();
110
+ const compDirsToRemove = Object.keys(currentRootDirs).filter((dir) => !previousRootDirs[dir]);
111
+
112
+ if (!mergeAbortOpts.silent) {
113
+ await this.mergeAbortPrompt(compDirsToRemove);
114
+ }
115
+ await Promise.all(compDirsToRemove.map((dir) => fs.remove(dir))); // it doesn't throw if not-exist, so we're good here.
116
+ await fs.copyFile(this.getLastMergedBitmapPath(), this.consumer.bitMap.mapPath);
117
+ await fs.copyFile(this.getLastMergedWorkspacePath(), this.consumer.config.path);
118
+ if (fs.pathExistsSync(this.getLastMergedStagedSnapsPath())) {
119
+ await fs.copyFile(this.getLastMergedStagedSnapsPath(), StagedSnaps.getPath(this.scope.path));
120
+ } else {
121
+ await this.scope.legacyScope.stagedSnaps.deleteFile();
122
+ }
123
+ const stagedConfig = await this.scope.getStagedConfig();
124
+ if (fs.pathExistsSync(this.getLastMergedStagedConfigPath())) {
125
+ await fs.copyFile(this.getLastMergedStagedConfigPath(), stagedConfig.filePath);
126
+ } else {
127
+ await stagedConfig.deleteFile();
128
+ }
129
+ await fs.remove(this.path);
130
+
131
+ return {
132
+ compDirsToRemove,
133
+ };
134
+ }
135
+
136
+ private async mergeAbortPrompt(dirsToRemove: string[]) {
137
+ this.logger.clearStatusLine();
138
+ const dirsToRemoveStr = dirsToRemove.length
139
+ ? `\nThe following directories introduced by the merge will be deleted: ${dirsToRemove.join(', ')}`
140
+ : '';
141
+ const ok = await yesno({
142
+ question: `Code changes that were done since the last lane-merge will be lost.${dirsToRemoveStr}
143
+ The .bitmap and workspace.jsonc files will be restored to the state before the merge.
144
+ This action is irreversible.
145
+ ${chalk.bold('Do you want to continue? [yes(y)/no(n)]')}`,
146
+ });
147
+ if (!ok) {
148
+ throw new PromptCanceled();
149
+ }
150
+ }
151
+
152
+ private async getLastMergedLaneContentIfExists(): Promise<Buffer | null> {
153
+ const filename = this.getLastMergedLanePath();
154
+ return this.getFileContentIfExist(filename);
155
+ }
156
+ private async getFileContentIfExist(filename: string): Promise<Buffer | null> {
157
+ try {
158
+ return await fs.readFile(filename);
159
+ } catch (err: any) {
160
+ if (err.code === 'ENOENT') return null;
161
+ throw err;
162
+ }
163
+ }
164
+ private getLastMergedBitmapPath() {
165
+ return path.join(this.path, LAST_MERGED_BITMAP_FILENAME);
166
+ }
167
+ private getLastMergedWorkspacePath() {
168
+ return path.join(this.path, 'workspace.jsonc');
169
+ }
170
+ private getLastMergedLanePath() {
171
+ return path.join(this.path, LAST_MERGED_LANE_FILENAME);
172
+ }
173
+ private getLastMergedStagedSnapsPath() {
174
+ return path.join(this.path, 'staged-snaps');
175
+ }
176
+ private getLastMergedStagedConfigPath() {
177
+ return path.join(this.path, 'staged-config.json');
178
+ }
179
+ }
@@ -0,0 +1,66 @@
1
+ import chalk from 'chalk';
2
+ import { CheckoutProps, checkoutOutput } from '@teambit/checkout';
3
+ import { Command, CommandOptions } from '@teambit/cli';
4
+ import { MergeLanesMain } from './merge-lanes.main.runtime';
5
+
6
+ export type MergeAbortOpts = {
7
+ silent?: boolean; // don't show prompt before aborting
8
+ };
9
+
10
+ export class MergeAbortLaneCmd implements Command {
11
+ name = 'merge-abort';
12
+ description = `abort the recent lane-merge. revert the lane object and checkout accordingly`;
13
+ extendedDescription = `restore the lane-object to its state before the last "bit lane merge" command.
14
+ also, checkout the workspace components according to the restored lane state`;
15
+ alias = '';
16
+ options = [
17
+ ['', 'verbose', "show details of components that didn't need to be merged"],
18
+ ['s', 'silent', 'skip confirmation'],
19
+ ['x', 'skip-dependency-installation', 'do not install packages of the imported components'],
20
+ ] as CommandOptions;
21
+ loader = true;
22
+ private = true;
23
+ migration = true;
24
+ remoteOp = true;
25
+
26
+ constructor(private mergeLanes: MergeLanesMain) {}
27
+
28
+ async report(
29
+ _,
30
+ {
31
+ skipDependencyInstallation = false,
32
+ verbose = false,
33
+ silent = false,
34
+ }: {
35
+ skipDependencyInstallation?: boolean;
36
+ verbose?: boolean;
37
+ silent?: boolean;
38
+ }
39
+ ): Promise<string> {
40
+ const checkoutProps: CheckoutProps = {
41
+ reset: true,
42
+ all: true,
43
+ verbose,
44
+ skipNpmInstall: skipDependencyInstallation,
45
+ };
46
+ const mergeAbortOpts = { silent };
47
+ const { checkoutResults, restoredItems, checkoutError } = await this.mergeLanes.abortLaneMerge(
48
+ checkoutProps,
49
+ mergeAbortOpts
50
+ );
51
+
52
+ const getCheckoutErrorStr = () => {
53
+ if (!checkoutError) return '';
54
+ const errMsg = `\n\nFailed to change component files to the pre-merge state due to an error:
55
+ ${checkoutError.message}
56
+ please fix the error and then run "bit checkout reset --all" to revert the components to the pre-merge state`;
57
+ return chalk.red(errMsg);
58
+ };
59
+
60
+ const checkoutOutputStr = checkoutResults ? checkoutOutput(checkoutResults, checkoutProps) : '';
61
+ const restoredItemsTitle = chalk.green('The following have been restored successfully:');
62
+ const restoredItemsOutput = restoredItems.map((item) => `[√] ${item}`).join('\n');
63
+
64
+ return `${checkoutOutputStr}\n\n${restoredItemsTitle}\n${restoredItemsOutput}${getCheckoutErrorStr()}`;
65
+ }
66
+ }
@@ -0,0 +1,124 @@
1
+ import chalk from 'chalk';
2
+ import { DEFAULT_LANE } from '@teambit/lane-id';
3
+ import { Command, CommandOptions } from '@teambit/cli';
4
+ import { fromBase64 } from '@teambit/legacy/dist/utils';
5
+ import { BitError } from '@teambit/bit-error';
6
+ import { MergeLanesMain } from './merge-lanes.main.runtime';
7
+
8
+ type Flags = {
9
+ pattern?: string;
10
+ push?: boolean;
11
+ keepReadme?: boolean;
12
+ noSquash: boolean;
13
+ includeDeps?: boolean;
14
+ title?: string;
15
+ titleBase64?: string;
16
+ };
17
+
18
+ /**
19
+ * private command. the underscore prefix is intended.
20
+ */
21
+ export class MergeLaneFromScopeCmd implements Command {
22
+ name = '_merge-lane <from-lane> [to-lane]';
23
+ description = `merge a remote lane into another lane or main via a bare-scope (not workspace)`;
24
+ extendedDescription = `to merge from a workspace, use "bit lane merge" command.
25
+ this is intended to use from the UI, which will have a button to merge an existing lane.
26
+ the lane must be up-to-date with the other lane, otherwise, conflicts might occur which are not handled in this command`;
27
+ arguments = [
28
+ {
29
+ name: 'from-lane',
30
+ description: 'lane-id to merge from',
31
+ },
32
+ {
33
+ name: 'to-lane',
34
+ description: `lane-id to merge to. default is "${DEFAULT_LANE}"`,
35
+ },
36
+ ];
37
+ alias = '';
38
+ options = [
39
+ ['', 'pattern <string>', 'partially merge the lane with the specified component-pattern'],
40
+ [
41
+ '',
42
+ 'title <string>',
43
+ 'if provided, it replaces the original message with this title and append squashed snaps messages',
44
+ ],
45
+ ['', 'title-base64 <string>', 'same as --title flag but the title is base64 encoded'],
46
+ ['', 'push', 'export the updated objects to the original scopes once done'],
47
+ ['', 'keep-readme', 'skip deleting the lane readme component after merging'],
48
+ ['', 'no-squash', 'relevant for merging lanes into main, which by default squash.'],
49
+ ['', 'include-deps', 'relevant for "--pattern". merge also dependencies of the given components'],
50
+ ['j', 'json', 'output as json format'],
51
+ ] as CommandOptions;
52
+ loader = true;
53
+ private = true;
54
+ migration = true;
55
+ remoteOp = true;
56
+
57
+ constructor(private mergeLanes: MergeLanesMain) {}
58
+
59
+ async report(
60
+ [fromLane, toLane]: [string, string],
61
+ { pattern, push = false, keepReadme = false, noSquash = false, includeDeps = false, title, titleBase64 }: Flags
62
+ ): Promise<string> {
63
+ if (includeDeps && !pattern) {
64
+ throw new BitError(`"--include-deps" flag is relevant only for --pattern flag`);
65
+ }
66
+
67
+ const titleBase64Decoded = titleBase64 ? fromBase64(titleBase64) : undefined;
68
+
69
+ const { mergedNow, mergedPreviously, exportedIds } = await this.mergeLanes.mergeFromScope(
70
+ fromLane,
71
+ toLane || DEFAULT_LANE,
72
+ {
73
+ push,
74
+ keepReadme,
75
+ noSquash,
76
+ pattern,
77
+ includeDeps,
78
+ snapMessage: titleBase64Decoded || title,
79
+ }
80
+ );
81
+
82
+ const mergedTitle = chalk.green(
83
+ `successfully merged ${mergedNow.length} components from ${fromLane} to ${toLane || DEFAULT_LANE}`
84
+ );
85
+ const mergedOutput = mergedNow.length ? `${mergedTitle}\n${mergedNow.join('\n')}` : '';
86
+
87
+ const nonMergedTitle = chalk.bold(
88
+ `the following ${mergedPreviously.length} components were already merged before, they were left intact`
89
+ );
90
+ const nonMergedOutput = mergedPreviously.length ? `\n${nonMergedTitle}\n${mergedPreviously.join('\n')}` : '';
91
+
92
+ const exportedTitle = chalk.green(`successfully exported ${exportedIds.length} components`);
93
+ const exportedOutput = exportedIds.length ? `\n${exportedTitle}\n${exportedIds.join('\n')}` : '';
94
+
95
+ return mergedOutput + nonMergedOutput + exportedOutput;
96
+ }
97
+ async json(
98
+ [fromLane, toLane]: [string, string],
99
+ { pattern, push = false, keepReadme = false, noSquash = false, includeDeps = false }: Flags
100
+ ) {
101
+ if (includeDeps && !pattern) {
102
+ throw new BitError(`"--include-deps" flag is relevant only for --pattern flag`);
103
+ }
104
+ let results: any;
105
+ try {
106
+ results = await this.mergeLanes.mergeFromScope(fromLane, toLane || DEFAULT_LANE, {
107
+ push,
108
+ keepReadme,
109
+ noSquash,
110
+ pattern,
111
+ includeDeps,
112
+ });
113
+ return {
114
+ code: 0,
115
+ data: results,
116
+ };
117
+ } catch (err: any) {
118
+ return {
119
+ code: 1,
120
+ error: err.message,
121
+ };
122
+ }
123
+ }
124
+ }
@@ -0,0 +1,202 @@
1
+ import chalk from 'chalk';
2
+ import { Command, CommandOptions } from '@teambit/cli';
3
+ import { MergeStrategy } from '@teambit/legacy/dist/consumer/versions-ops/merge-version';
4
+ import { mergeReport } from '@teambit/merging';
5
+ import { GlobalConfigMain } from '@teambit/global-config';
6
+ import { COMPONENT_PATTERN_HELP, CFG_FORCE_LOCAL_BUILD } from '@teambit/legacy/dist/constants';
7
+ import { BitError } from '@teambit/bit-error';
8
+ import { removeTemplate } from '@teambit/remove';
9
+ import { MergeLanesMain } from './merge-lanes.main.runtime';
10
+
11
+ export class MergeLaneCmd implements Command {
12
+ name = 'merge <lane> [pattern]';
13
+ description = `merge a local or a remote lane to the current lane`;
14
+ extendedDescription = `by default, the provided lane will be fetched from the remote before merging.
15
+ to merge the lane from the local scope without updating it first, use "--skip-fetch" flag.
16
+
17
+ when the current and merge candidate lanes are diverged in history and the files could be merged with no conflicts,
18
+ these components will be snap-merged to complete the merge. use "no-snap" to opt-out, or "tag" to tag instead.
19
+
20
+ in case a component in both ends don't share history (no snap is found in common), the merge will require "--resolve-unrelated" flag.
21
+ this flag keeps the history of one end and saves a reference to the other end. the decision of which end to keep is determined by the following:
22
+ 1. if the component exists on main, then the history linked to main will be kept.
23
+ in this case, the strategy of "--resolve-unrelated" only determines which source-code to keep. it's not about the history.
24
+ 2. if the component doesn't exist on main, then by default, the history of the current lane will be kept.
25
+ unless "--resolve-unrelated" is set to "theirs", in which case the history of the other lane will be kept.
26
+ 2. a. an edge case: if the component is deleted on the current lane, the strategy will always be "theirs".
27
+ so then the history (and the source-code) of the other lane will be kept.
28
+ `;
29
+ arguments = [
30
+ {
31
+ name: 'lane',
32
+ description: 'lane-name or full lane-id (if remote) to merge to the current lane',
33
+ },
34
+ {
35
+ name: 'pattern',
36
+ description: `partially merge the lane - only components that match the specified component-pattern
37
+ Component pattern format: ${COMPONENT_PATTERN_HELP}`,
38
+ },
39
+ ];
40
+ alias = '';
41
+ options = [
42
+ ['', 'ours', 'DEPRECATED. use --auto-merge-resolve. in case of a conflict, keep local modifications'],
43
+ ['', 'theirs', 'DEPRECATED. use --auto-merge-resolve. in case of a conflict, override local with incoming changes'],
44
+ ['', 'manual', 'DEPRECATED. use --auto-merge-resolve'],
45
+ [
46
+ '',
47
+ 'auto-merge-resolve <merge-strategy>',
48
+ 'in case of a merge conflict, resolve according to the provided strategy: [ours, theirs, manual]',
49
+ ],
50
+ ['', 'workspace', 'merge only lane components that are in the current workspace'],
51
+ ['', 'no-snap', 'do not auto snap after merge completed without conflicts'],
52
+ ['', 'tag', 'auto-tag all lane components after merging into main (or tag-merge in case of snap-merge)'],
53
+ ['', 'build', 'in case of snap during the merge, run the build-pipeline (similar to bit snap --build)'],
54
+ ['m', 'message <message>', 'override the default message for the auto snap'],
55
+ ['', 'keep-readme', 'skip deleting the lane readme component after merging'],
56
+ ['', 'no-squash', 'relevant for merging lanes into main, which by default squashes all lane snaps'],
57
+ [
58
+ '',
59
+ 'squash',
60
+ 'EXPERIMENTAL. relevant for merging a lane into another non-main lane, which by default does not squash',
61
+ ],
62
+ [
63
+ '',
64
+ 'ignore-config-changes',
65
+ 'allow merging when components are modified due to config changes (such as dependencies) only and not files',
66
+ ],
67
+ ['', 'verbose', 'show details of components that were not merged successfully'],
68
+ ['x', 'skip-dependency-installation', 'do not install dependencies of the imported components'],
69
+ ['', 'skip-fetch', 'use the local state of target-lane if exits locally, without updating it from the remote'],
70
+ [
71
+ '',
72
+ 'include-deps',
73
+ 'relevant for "--pattern" and "--workspace". merge also dependencies of the specified components',
74
+ ],
75
+ [
76
+ '',
77
+ 'resolve-unrelated [merge-strategy]',
78
+ 'relevant when a component on a lane and the component on main have nothing in common. merge-strategy can be "ours" (default) or "theirs"',
79
+ ],
80
+ [
81
+ '',
82
+ 'include-non-lane-comps',
83
+ 'DEPRECATED (this is now the default). when merging main, include workspace components that are not on the lane (by default only lane components are merged)',
84
+ ],
85
+ [
86
+ '',
87
+ 'exclude-non-lane-comps',
88
+ 'when merging main into a lane, exclude workspace components that are not on the lane (by default all workspace components are merged)',
89
+ ],
90
+ ] as CommandOptions;
91
+ loader = true;
92
+ private = true;
93
+ migration = true;
94
+ remoteOp = true;
95
+
96
+ constructor(private mergeLanes: MergeLanesMain, private globalConfig: GlobalConfigMain) {}
97
+
98
+ async report(
99
+ [name, pattern]: [string, string],
100
+ {
101
+ ours,
102
+ theirs,
103
+ manual,
104
+ autoMergeResolve,
105
+ build,
106
+ workspace: existingOnWorkspaceOnly = false,
107
+ squash = false,
108
+ noSnap = false,
109
+ tag = false,
110
+ message: snapMessage = '',
111
+ keepReadme = false,
112
+ noSquash = false,
113
+ skipDependencyInstallation = false,
114
+ skipFetch = false,
115
+ includeDeps = false,
116
+ resolveUnrelated,
117
+ ignoreConfigChanges,
118
+ verbose = false,
119
+ excludeNonLaneComps = false,
120
+ }: {
121
+ ours?: boolean;
122
+ theirs?: boolean;
123
+ manual?: boolean;
124
+ autoMergeResolve?: string;
125
+ workspace?: boolean;
126
+ build?: boolean;
127
+ noSnap: boolean;
128
+ tag: boolean;
129
+ message: string;
130
+ keepReadme?: boolean;
131
+ squash?: boolean;
132
+ noSquash: boolean;
133
+ skipDependencyInstallation?: boolean;
134
+ skipFetch: boolean;
135
+ includeDeps?: boolean;
136
+ resolveUnrelated?: string | boolean;
137
+ ignoreConfigChanges?: boolean;
138
+ verbose?: boolean;
139
+ excludeNonLaneComps?: boolean;
140
+ }
141
+ ): Promise<string> {
142
+ build = (await this.globalConfig.getBool(CFG_FORCE_LOCAL_BUILD)) || Boolean(build);
143
+ if (ours || theirs || manual) {
144
+ throw new BitError(
145
+ 'the "--ours", "--theirs" and "--manual" flags are deprecated. use "--auto-merge-resolve" instead. see "bit lane merge --help" for more information'
146
+ );
147
+ }
148
+ if (
149
+ autoMergeResolve &&
150
+ autoMergeResolve !== 'ours' &&
151
+ autoMergeResolve !== 'theirs' &&
152
+ autoMergeResolve !== 'manual'
153
+ ) {
154
+ throw new BitError('--auto-merge-resolve must be one of the following: [ours, theirs, manual]');
155
+ }
156
+ const mergeStrategy = autoMergeResolve;
157
+ if (noSnap && snapMessage) throw new BitError('unable to use "no-snap" and "message" flags together');
158
+ if (includeDeps && !pattern && !existingOnWorkspaceOnly) {
159
+ throw new BitError(`"--include-deps" flag is relevant only for --workspace and --pattern flags`);
160
+ }
161
+ const getResolveUnrelated = (): MergeStrategy | undefined => {
162
+ if (!resolveUnrelated) return undefined;
163
+ if (typeof resolveUnrelated === 'boolean') return 'ours';
164
+ if (resolveUnrelated !== 'ours' && resolveUnrelated !== 'theirs' && resolveUnrelated !== 'manual') {
165
+ throw new Error('--resolve-unrelated must be one of the following: [ours, theirs, manual]');
166
+ }
167
+ return resolveUnrelated;
168
+ };
169
+ if (resolveUnrelated && typeof resolveUnrelated === 'boolean') {
170
+ resolveUnrelated = 'ours';
171
+ }
172
+ const { mergeResults, deleteResults, configMergeResults } = await this.mergeLanes.mergeLane(name, {
173
+ build,
174
+ // @ts-ignore
175
+ mergeStrategy,
176
+ ours,
177
+ theirs,
178
+ existingOnWorkspaceOnly,
179
+ noSnap,
180
+ snapMessage,
181
+ keepReadme,
182
+ squash,
183
+ noSquash,
184
+ tag,
185
+ pattern,
186
+ skipDependencyInstallation,
187
+ skipFetch,
188
+ resolveUnrelated: getResolveUnrelated(),
189
+ ignoreConfigChanges,
190
+ includeDeps,
191
+ excludeNonLaneComps,
192
+ });
193
+
194
+ const mergeResult = mergeReport({ ...mergeResults, configMergeResults, verbose });
195
+ const deleteOutput = `\n${deleteResults.localResult ? removeTemplate(deleteResults.localResult, false) : ''}${(
196
+ deleteResults.remoteResult || []
197
+ ).map((item) => removeTemplate(item, true))}${
198
+ (deleteResults.readmeResult && chalk.yellow(deleteResults.readmeResult)) || ''
199
+ }\n`;
200
+ return mergeResult + deleteOutput;
201
+ }
202
+ }
@@ -0,0 +1,5 @@
1
+ import { Aspect } from '@teambit/harmony';
2
+
3
+ export const MergeLanesAspect = Aspect.create({
4
+ id: 'teambit.lanes/merge-lanes',
5
+ });