@docupace/pipeline 1.0.77 → 1.0.78

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.
Files changed (69) hide show
  1. package/.prettierrc.cjs +12 -12
  2. package/README.md +135 -135
  3. package/bin/azuread_update_callbacks_for_app.sh +58 -58
  4. package/bin/next-changeset-validate.sh +8 -8
  5. package/bitbucket-pipelines.yml +59 -59
  6. package/eslint.config.js +49 -49
  7. package/jest.config.js +15 -15
  8. package/next/changeset.sh +75 -75
  9. package/next/copy.sh +34 -34
  10. package/next/deploy.sh +40 -40
  11. package/next/execute.sh +123 -123
  12. package/next/sonar.sh +23 -23
  13. package/package.json +1 -1
  14. package/src/auth0/alias.js +79 -79
  15. package/src/auth0/alias.test.js +172 -172
  16. package/src/auth0/cli.js +82 -82
  17. package/src/auth0/configure.js +902 -902
  18. package/src/auth0/configure.test.js +167 -167
  19. package/src/auth0/export.js +48 -48
  20. package/src/auth0/export.test.js +103 -103
  21. package/src/auth0/metadata.js +141 -141
  22. package/src/auth0/metadata.test.js +186 -186
  23. package/src/auth0/vault.js +60 -60
  24. package/src/auth0/vault.test.js +134 -134
  25. package/src/changeset/changelog-wiki.js +328 -328
  26. package/src/changeset/changeset-config.js +34 -34
  27. package/src/changeset/cli.js +237 -237
  28. package/src/changeset/create.js +215 -215
  29. package/src/changeset/package-util.js +87 -87
  30. package/src/changeset/util.js +43 -43
  31. package/src/changeset/validate.js +91 -91
  32. package/src/changeset/version.js +102 -102
  33. package/src/jenkins/setParams.groovy +58 -58
  34. package/src/lib/merge-driver.js +204 -204
  35. package/src/merge/cli-merge-all.js +507 -507
  36. package/src/merge/cli-merge.js +492 -492
  37. package/src/merge/cli-resolve.js +399 -399
  38. package/src/merge/cli.js +38 -38
  39. package/src/merge/merge-changelog.js +87 -87
  40. package/src/merge/merge-package.js +106 -106
  41. package/src/merge/merge-types.js +82 -82
  42. package/src/merge/pr.js +231 -231
  43. package/src/micro/deploy-microservice.js +153 -0
  44. package/src/micro/{ms-publish.js → publish-ms.js} +8 -6
  45. package/src/next/alias.js +53 -53
  46. package/src/next/apache.js +81 -81
  47. package/src/next/cli.js +10 -1
  48. package/src/next/config.js +137 -137
  49. package/src/next/connect.js +226 -226
  50. package/src/next/copy.js +61 -61
  51. package/src/next/deploy.js +633 -633
  52. package/src/next/git-to-semver.js +79 -79
  53. package/src/next/haproxy.js +76 -76
  54. package/src/next/jenkins.js +123 -123
  55. package/src/next/jira.js +84 -84
  56. package/src/next/logger.js +17 -17
  57. package/src/next/pipline.js +114 -114
  58. package/src/next/publish.js +71 -71
  59. package/src/next/pull.js +7 -7
  60. package/src/next/sonar.js +23 -23
  61. package/src/next/test.js +5 -5
  62. package/src/next/util.js +95 -93
  63. package/src/next/vault.js +45 -45
  64. package/terraform/apply.sh +37 -37
  65. package/terraform/init.sh +72 -72
  66. package/terraform/plan.sh +76 -76
  67. package/terraform/validate.sh +17 -17
  68. package/src/micro/cli.js +0 -71
  69. package/src/micro/ms-deploy.js +0 -271
@@ -1,492 +1,492 @@
1
- import { execSync } from 'child_process';
2
- import { createBitbucketPR } from './pr.js';
3
- import { autoResolveConflicts } from './cli-resolve.js';
4
- import { getPackageNames } from '../changeset/package-util.js';
5
- import { writeChangesetFile } from '../changeset/create.js';
6
-
7
- /**
8
- * Create and checkout merge branch from target branch
9
- */
10
- function createMergeBranch(
11
- sourceBranch,
12
- targetBranch,
13
- checkoutOnly = false,
14
- verbose = false,
15
- ) {
16
- const mergeBranchName = `merge/${sourceBranch.replaceAll('/', '-')}-to-${targetBranch.replaceAll('/', '-')}`;
17
-
18
- if (!checkoutOnly) {
19
- if (verbose) console.log(`📋 Checking out target branch: ${targetBranch}`);
20
- execSync(`git checkout ${targetBranch}`, {
21
- stdio: verbose ? 'inherit' : 'pipe',
22
- });
23
- execSync(`git pull origin ${targetBranch}`, {
24
- stdio: verbose ? 'inherit' : 'pipe',
25
- });
26
-
27
- // Check if merge branch exists and delete it (both local and remote)
28
- try {
29
- execSync(`git branch -D ${mergeBranchName}`, {
30
- stdio: 'pipe',
31
- });
32
- if (verbose)
33
- console.log(`🗑️ Deleted existing local branch: ${mergeBranchName}`);
34
- } catch {
35
- // Branch doesn't exist, which is fine
36
- }
37
-
38
- // Try to delete remote branch
39
- try {
40
- execSync(`git push origin --delete ${mergeBranchName}`, {
41
- stdio: 'pipe',
42
- });
43
- if (verbose)
44
- console.log(`🗑️ Deleted existing remote branch: ${mergeBranchName}`);
45
- } catch {
46
- // Remote branch doesn't exist, which is fine
47
- }
48
-
49
- if (verbose) console.log(`🌿 Creating merge branch: ${mergeBranchName}`);
50
- execSync(`git checkout -b ${mergeBranchName}`, {
51
- stdio: verbose ? 'inherit' : 'pipe',
52
- });
53
- } else {
54
- console.log('Do nothing. Should be checked out');
55
- }
56
-
57
- return mergeBranchName;
58
- }
59
-
60
- /**
61
- * Attempt to merge source branch into current branch
62
- * Returns: { hasConflicts: boolean, hasChanges: boolean }
63
- */
64
- function attemptMerge(sourceBranch, targetBranch, verbose = false) {
65
- if (verbose) {
66
- console.log(`🔀 Merging ${sourceBranch} into current branch`);
67
- }
68
-
69
- try {
70
- execSync(`git merge --no-commit --no-ff origin/${sourceBranch}`, {
71
- stdio: 'pipe',
72
- encoding: 'utf8',
73
- });
74
-
75
- // Check if there are any changes
76
- try {
77
- execSync('git diff --cached --quiet', { stdio: 'pipe' });
78
- // No changes
79
- return { hasConflicts: false, hasChanges: false };
80
- } catch {
81
- // Has changes
82
- if (verbose) console.log('✅ Merge completed with changes, no conflicts');
83
- return { hasConflicts: false, hasChanges: true };
84
- }
85
- } catch (error) {
86
- if (verbose) console.log('⚠️ Merge conflicts detected');
87
- return { hasConflicts: true, hasChanges: true };
88
- }
89
- }
90
-
91
- /**
92
- * Get packages that have changes to be merged
93
- */
94
- function getChangedPackages(verbose = false) {
95
- try {
96
- const allPackages = getPackageNames();
97
-
98
- // Get list of changed files
99
- const changedFiles = execSync('git diff --cached --name-only', {
100
- encoding: 'utf8',
101
- stdio: 'pipe',
102
- })
103
- .trim()
104
- .split('\n')
105
- .filter(Boolean);
106
-
107
- if (verbose) {
108
- console.log('📄 Changed files:', changedFiles);
109
- }
110
-
111
- // Filter packages that have changes
112
- const changedPackages = allPackages.filter((pkg) => {
113
- const packagePath = pkg.path === '.' ? '' : pkg.path + '/';
114
- return changedFiles.some((file) => file.startsWith(packagePath));
115
- });
116
-
117
- if (verbose) {
118
- console.log(
119
- '📦 Changed packages:',
120
- changedPackages.map((p) => p.name),
121
- );
122
- }
123
-
124
- return changedPackages;
125
- } catch (error) {
126
- if (verbose) console.log('Error getting changed packages:', error.message);
127
- return [];
128
- }
129
- }
130
-
131
- /**
132
- * Run build script
133
- */
134
- function runBuild(buildScript, verbose = false) {
135
- if (verbose) console.log(`🔨 Running build: ${buildScript}`);
136
-
137
- try {
138
- execSync(buildScript, {
139
- stdio: verbose ? 'inherit' : 'pipe',
140
- });
141
- if (verbose) console.log('✅ Build passed');
142
- return true;
143
- } catch (error) {
144
- if (verbose) console.log('❌ Build failed');
145
- return false;
146
- }
147
- }
148
-
149
- /**
150
- * Create changeset and commit it
151
- */
152
- async function createAndCommitChangeset(
153
- sourceBranch,
154
- targetBranch,
155
- changedPackages,
156
- verbose = false,
157
- ) {
158
- const changesetId = `merge-${sourceBranch.replaceAll('/', '-')}-to-${targetBranch.replaceAll('/', '-')}-${Date.now()}`;
159
- const text = `Merge ${sourceBranch} to ${targetBranch}`;
160
-
161
- if (verbose) console.log('📝 Creating changeset...');
162
-
163
- await writeChangesetFile(changedPackages, changesetId, 'patch', text);
164
-
165
- execSync('git add .', {
166
- stdio: verbose ? 'inherit' : 'pipe',
167
- });
168
-
169
- execSync(
170
- `git commit -m "Automatic merge operation for ${sourceBranch} to ${targetBranch}"`,
171
- {
172
- stdio: verbose ? 'inherit' : 'pipe',
173
- },
174
- );
175
-
176
- if (verbose) console.log('✅ Changeset created and committed');
177
- }
178
-
179
- /**
180
- * Merge into target branch
181
- */
182
-
183
- function branchExists(branchName) {
184
- try {
185
- // This will list branches that match the name
186
- const result = execSync(`git branch --list ${branchName}`, {
187
- encoding: 'utf8',
188
- });
189
- return result.trim().length > 0;
190
- } catch (error) {
191
- // If git fails, you can log or handle differently
192
- console.error('Error checking branch:', error.message);
193
- return false;
194
- }
195
- }
196
-
197
- function mergeIntoTarget(
198
- sourceBranch,
199
- targetBranch,
200
- mergeBranchName,
201
- verbose = false,
202
- ) {
203
- if (verbose)
204
- console.log(`🔀 Merging ${mergeBranchName} into ${targetBranch}`);
205
-
206
- execSync(`git checkout ${targetBranch}`, {
207
- stdio: verbose ? 'inherit' : 'pipe',
208
- });
209
-
210
- if (branchExists(mergeBranchName)) {
211
- execSync(`git merge ${mergeBranchName}`, {
212
- stdio: verbose ? 'inherit' : 'pipe',
213
- });
214
-
215
- execSync(`git push origin ${targetBranch}`, {
216
- stdio: verbose ? 'inherit' : 'pipe',
217
- });
218
-
219
- execSync(`git branch -d ${mergeBranchName}`, {
220
- stdio: verbose ? 'inherit' : 'pipe',
221
- });
222
-
223
- if (verbose) console.log(`✅ Successfully merged into ${targetBranch}`);
224
- } else {
225
- if (verbose) console.log(`✅ Nothing to merge`);
226
- }
227
- }
228
-
229
- /**
230
- * Create pull request for merge branch
231
- */
232
- async function createMergePR(
233
- sourceBranch,
234
- targetBranch,
235
- mergeBranchName,
236
- reason,
237
- verbose = false,
238
- ) {
239
- if (verbose) console.log('📋 Creating pull request...');
240
-
241
- const prTitle = `Merge ${sourceBranch} into ${targetBranch}`;
242
- const prDescription = `Automated merge with changeset.\n\nReason: ${reason}`;
243
-
244
- const prResult = await createBitbucketPR(
245
- mergeBranchName,
246
- targetBranch,
247
- prTitle,
248
- prDescription,
249
- );
250
-
251
- if (prResult.success) {
252
- console.log(`✅ Pull request created: ${prResult.url}`);
253
- } else if (prResult.manual) {
254
- console.log(`📋 Please create PR manually: ${prResult.url}`);
255
- } else {
256
- console.log(`❌ Failed to create pull request: ${prResult.error}`);
257
- }
258
- }
259
-
260
- /**
261
- * Clean up failed merge
262
- */
263
- function cleanupFailedMerge(verbose = false) {
264
- try {
265
- execSync('git merge --abort', { stdio: 'pipe' });
266
- if (verbose) console.log('🧹 Cleaned up failed merge');
267
- } catch (cleanupError) {
268
- // Ignore cleanup errors
269
- }
270
- }
271
-
272
- export function setupMerge(program) {
273
- program
274
- .command('merge')
275
- .description(
276
- 'Merge source branch into target branch with automatic conflict resolution',
277
- )
278
- .argument('<sourceBranch>', 'Source branch to merge from')
279
- .argument('<targetBranch>', 'Target branch to merge into')
280
- .requiredOption('-b, --build <script>', 'Build script to run after merge')
281
- .option('-v, --verbose', 'Verbose output')
282
- .option('--no-push', 'Do not push changes automatically')
283
- .option('--no-pr', 'Do not create pull request if conflicts remain')
284
- .option(
285
- '--prepare-only',
286
- 'Always create PR instead of direct merge, even if build passes',
287
- )
288
- .option('--merge-success', 'Merge with success')
289
- .option('--merge-fail <reason>', 'Merge with with fail')
290
- .action(async (sourceBranch, targetBranch, options) => {
291
- try {
292
- const {
293
- verbose,
294
- push = true,
295
- pr = true,
296
- build,
297
- prepareOnly = false,
298
- mergeSuccess = false,
299
- mergeFail = '',
300
- } = options;
301
- const isDevMode = process.env.DEV_MODE === 'true';
302
-
303
- if (verbose) {
304
- console.log(
305
- `🔄 Starting merge of ${sourceBranch} into ${targetBranch}`,
306
- );
307
- }
308
-
309
- // 1. Create merge branch from target branch
310
- const mergeBranchName = createMergeBranch(
311
- sourceBranch,
312
- targetBranch,
313
- mergeSuccess || mergeFail,
314
- verbose,
315
- );
316
-
317
- if (!mergeFail && !mergeSuccess) {
318
- // 2. Attempt merge
319
- const { hasConflicts, hasChanges } = attemptMerge(
320
- sourceBranch,
321
- targetBranch,
322
- verbose,
323
- );
324
-
325
- // 3. If no changes, exit
326
- if (!hasChanges) {
327
- console.log('✅ No changes to merge');
328
- execSync(`git checkout ${targetBranch}`, { stdio: 'pipe' });
329
- execSync(`git branch -d ${mergeBranchName}`, { stdio: 'pipe' });
330
- process.exit(4);
331
- }
332
-
333
- // 4. If conflicts, try auto-resolve
334
- let allConflictsResolved = true;
335
- if (hasConflicts) {
336
- if (verbose) console.log('🤖 Running auto-conflict resolution...');
337
-
338
- const autoResult = await autoResolveConflicts({ verbose });
339
- allConflictsResolved = autoResult.success;
340
- }
341
-
342
- // Get changed packages
343
- const changedPackages = getChangedPackages(verbose);
344
-
345
- if (!allConflictsResolved) {
346
- // 5. Cannot resolve conflicts
347
- if (verbose)
348
- console.log('⚠️ Cannot resolve all conflicts automatically');
349
-
350
- // 5a) Get changed packages (already done above)
351
- // 5b) Abort merge
352
- cleanupFailedMerge(verbose);
353
-
354
- // 5c) Create changeset
355
- await createAndCommitChangeset(
356
- sourceBranch,
357
- targetBranch,
358
- changedPackages,
359
- verbose,
360
- );
361
-
362
- if (pr && !prepareOnly) {
363
- // 5d) Push and create PR
364
- if (push) {
365
- execSync(`git push -u origin ${mergeBranchName}`, {
366
- stdio: verbose ? 'inherit' : 'pipe',
367
- });
368
- }
369
-
370
- await createMergePR(
371
- sourceBranch,
372
- targetBranch,
373
- mergeBranchName,
374
- 'Unresolved conflicts',
375
- verbose,
376
- );
377
- } else {
378
- console.log(
379
- `⚠️ Merge completed with unresolved conflicts on branch: ${mergeBranchName}`,
380
- );
381
- process.exit(2);
382
- }
383
-
384
- console.log(
385
- `⚠️ Merge completed with unresolved conflicts on branch: ${mergeBranchName}`,
386
- );
387
- return;
388
- }
389
-
390
- // 6. Can resolve conflicts
391
- if (verbose) console.log('✅ All conflicts resolved');
392
-
393
- // 6a) Get changed packages (already done above)
394
- // 6b) Create changeset
395
- await createAndCommitChangeset(
396
- sourceBranch,
397
- targetBranch,
398
- changedPackages,
399
- verbose,
400
- );
401
-
402
- // 6c) Run build
403
- const buildPassed = runBuild(build, verbose);
404
-
405
- if (buildPassed && !isDevMode && !prepareOnly) {
406
- // 6d) Build passed - merge into target
407
- mergeIntoTarget(
408
- sourceBranch,
409
- targetBranch,
410
- mergeBranchName,
411
- verbose,
412
- );
413
- console.log(
414
- `🎉 Successfully merged ${sourceBranch} into ${targetBranch}`,
415
- );
416
- } else if (prepareOnly) {
417
- if (push) {
418
- execSync(`git push -u origin ${mergeBranchName}`, {
419
- stdio: verbose ? 'inherit' : 'pipe',
420
- });
421
- }
422
- if (buildPassed) {
423
- process.exit(0);
424
- } else {
425
- process.exit(3);
426
- }
427
- } else {
428
- // 6e) Build failed, dev mode, or prepare-only - create PR
429
- if (push) {
430
- execSync(`git push -u origin ${mergeBranchName}`, {
431
- stdio: verbose ? 'inherit' : 'pipe',
432
- });
433
- }
434
-
435
- if (pr) {
436
- let reason;
437
- if (isDevMode) {
438
- reason = 'Dev mode enabled';
439
- } else {
440
- reason = 'Build failed';
441
- }
442
-
443
- await createMergePR(
444
- sourceBranch,
445
- targetBranch,
446
- mergeBranchName,
447
- reason,
448
- verbose,
449
- );
450
- }
451
-
452
- let reason;
453
- if (prepareOnly) {
454
- reason = 'prepare-only mode';
455
- } else if (isDevMode) {
456
- reason = 'dev mode';
457
- } else {
458
- reason = 'build failure';
459
- }
460
-
461
- console.log(
462
- `⚠️ Merge completed but creating PR due to ${reason}: ${mergeBranchName}`,
463
- );
464
- }
465
- } else {
466
- if (mergeSuccess) {
467
- mergeIntoTarget(
468
- sourceBranch,
469
- targetBranch,
470
- mergeBranchName,
471
- verbose,
472
- );
473
- } else {
474
- execSync(`git push -u origin ${mergeBranchName}`, {
475
- stdio: verbose ? 'inherit' : 'pipe',
476
- });
477
- await createMergePR(
478
- sourceBranch,
479
- targetBranch,
480
- mergeBranchName,
481
- mergeFail,
482
- verbose,
483
- );
484
- }
485
- }
486
- } catch (error) {
487
- console.error(`❌ Merge failed: ${error.message}`);
488
- cleanupFailedMerge();
489
- process.exit(1);
490
- }
491
- });
492
- }
1
+ import { execSync } from 'child_process';
2
+ import { createBitbucketPR } from './pr.js';
3
+ import { autoResolveConflicts } from './cli-resolve.js';
4
+ import { getPackageNames } from '../changeset/package-util.js';
5
+ import { writeChangesetFile } from '../changeset/create.js';
6
+
7
+ /**
8
+ * Create and checkout merge branch from target branch
9
+ */
10
+ function createMergeBranch(
11
+ sourceBranch,
12
+ targetBranch,
13
+ checkoutOnly = false,
14
+ verbose = false,
15
+ ) {
16
+ const mergeBranchName = `merge/${sourceBranch.replaceAll('/', '-')}-to-${targetBranch.replaceAll('/', '-')}`;
17
+
18
+ if (!checkoutOnly) {
19
+ if (verbose) console.log(`📋 Checking out target branch: ${targetBranch}`);
20
+ execSync(`git checkout ${targetBranch}`, {
21
+ stdio: verbose ? 'inherit' : 'pipe',
22
+ });
23
+ execSync(`git pull origin ${targetBranch}`, {
24
+ stdio: verbose ? 'inherit' : 'pipe',
25
+ });
26
+
27
+ // Check if merge branch exists and delete it (both local and remote)
28
+ try {
29
+ execSync(`git branch -D ${mergeBranchName}`, {
30
+ stdio: 'pipe',
31
+ });
32
+ if (verbose)
33
+ console.log(`🗑️ Deleted existing local branch: ${mergeBranchName}`);
34
+ } catch {
35
+ // Branch doesn't exist, which is fine
36
+ }
37
+
38
+ // Try to delete remote branch
39
+ try {
40
+ execSync(`git push origin --delete ${mergeBranchName}`, {
41
+ stdio: 'pipe',
42
+ });
43
+ if (verbose)
44
+ console.log(`🗑️ Deleted existing remote branch: ${mergeBranchName}`);
45
+ } catch {
46
+ // Remote branch doesn't exist, which is fine
47
+ }
48
+
49
+ if (verbose) console.log(`🌿 Creating merge branch: ${mergeBranchName}`);
50
+ execSync(`git checkout -b ${mergeBranchName}`, {
51
+ stdio: verbose ? 'inherit' : 'pipe',
52
+ });
53
+ } else {
54
+ console.log('Do nothing. Should be checked out');
55
+ }
56
+
57
+ return mergeBranchName;
58
+ }
59
+
60
+ /**
61
+ * Attempt to merge source branch into current branch
62
+ * Returns: { hasConflicts: boolean, hasChanges: boolean }
63
+ */
64
+ function attemptMerge(sourceBranch, targetBranch, verbose = false) {
65
+ if (verbose) {
66
+ console.log(`🔀 Merging ${sourceBranch} into current branch`);
67
+ }
68
+
69
+ try {
70
+ execSync(`git merge --no-commit --no-ff origin/${sourceBranch}`, {
71
+ stdio: 'pipe',
72
+ encoding: 'utf8',
73
+ });
74
+
75
+ // Check if there are any changes
76
+ try {
77
+ execSync('git diff --cached --quiet', { stdio: 'pipe' });
78
+ // No changes
79
+ return { hasConflicts: false, hasChanges: false };
80
+ } catch {
81
+ // Has changes
82
+ if (verbose) console.log('✅ Merge completed with changes, no conflicts');
83
+ return { hasConflicts: false, hasChanges: true };
84
+ }
85
+ } catch (error) {
86
+ if (verbose) console.log('⚠️ Merge conflicts detected');
87
+ return { hasConflicts: true, hasChanges: true };
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Get packages that have changes to be merged
93
+ */
94
+ function getChangedPackages(verbose = false) {
95
+ try {
96
+ const allPackages = getPackageNames();
97
+
98
+ // Get list of changed files
99
+ const changedFiles = execSync('git diff --cached --name-only', {
100
+ encoding: 'utf8',
101
+ stdio: 'pipe',
102
+ })
103
+ .trim()
104
+ .split('\n')
105
+ .filter(Boolean);
106
+
107
+ if (verbose) {
108
+ console.log('📄 Changed files:', changedFiles);
109
+ }
110
+
111
+ // Filter packages that have changes
112
+ const changedPackages = allPackages.filter((pkg) => {
113
+ const packagePath = pkg.path === '.' ? '' : pkg.path + '/';
114
+ return changedFiles.some((file) => file.startsWith(packagePath));
115
+ });
116
+
117
+ if (verbose) {
118
+ console.log(
119
+ '📦 Changed packages:',
120
+ changedPackages.map((p) => p.name),
121
+ );
122
+ }
123
+
124
+ return changedPackages;
125
+ } catch (error) {
126
+ if (verbose) console.log('Error getting changed packages:', error.message);
127
+ return [];
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Run build script
133
+ */
134
+ function runBuild(buildScript, verbose = false) {
135
+ if (verbose) console.log(`🔨 Running build: ${buildScript}`);
136
+
137
+ try {
138
+ execSync(buildScript, {
139
+ stdio: verbose ? 'inherit' : 'pipe',
140
+ });
141
+ if (verbose) console.log('✅ Build passed');
142
+ return true;
143
+ } catch (error) {
144
+ if (verbose) console.log('❌ Build failed');
145
+ return false;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Create changeset and commit it
151
+ */
152
+ async function createAndCommitChangeset(
153
+ sourceBranch,
154
+ targetBranch,
155
+ changedPackages,
156
+ verbose = false,
157
+ ) {
158
+ const changesetId = `merge-${sourceBranch.replaceAll('/', '-')}-to-${targetBranch.replaceAll('/', '-')}-${Date.now()}`;
159
+ const text = `Merge ${sourceBranch} to ${targetBranch}`;
160
+
161
+ if (verbose) console.log('📝 Creating changeset...');
162
+
163
+ await writeChangesetFile(changedPackages, changesetId, 'patch', text);
164
+
165
+ execSync('git add .', {
166
+ stdio: verbose ? 'inherit' : 'pipe',
167
+ });
168
+
169
+ execSync(
170
+ `git commit -m "Automatic merge operation for ${sourceBranch} to ${targetBranch}"`,
171
+ {
172
+ stdio: verbose ? 'inherit' : 'pipe',
173
+ },
174
+ );
175
+
176
+ if (verbose) console.log('✅ Changeset created and committed');
177
+ }
178
+
179
+ /**
180
+ * Merge into target branch
181
+ */
182
+
183
+ function branchExists(branchName) {
184
+ try {
185
+ // This will list branches that match the name
186
+ const result = execSync(`git branch --list ${branchName}`, {
187
+ encoding: 'utf8',
188
+ });
189
+ return result.trim().length > 0;
190
+ } catch (error) {
191
+ // If git fails, you can log or handle differently
192
+ console.error('Error checking branch:', error.message);
193
+ return false;
194
+ }
195
+ }
196
+
197
+ function mergeIntoTarget(
198
+ sourceBranch,
199
+ targetBranch,
200
+ mergeBranchName,
201
+ verbose = false,
202
+ ) {
203
+ if (verbose)
204
+ console.log(`🔀 Merging ${mergeBranchName} into ${targetBranch}`);
205
+
206
+ execSync(`git checkout ${targetBranch}`, {
207
+ stdio: verbose ? 'inherit' : 'pipe',
208
+ });
209
+
210
+ if (branchExists(mergeBranchName)) {
211
+ execSync(`git merge ${mergeBranchName}`, {
212
+ stdio: verbose ? 'inherit' : 'pipe',
213
+ });
214
+
215
+ execSync(`git push origin ${targetBranch}`, {
216
+ stdio: verbose ? 'inherit' : 'pipe',
217
+ });
218
+
219
+ execSync(`git branch -d ${mergeBranchName}`, {
220
+ stdio: verbose ? 'inherit' : 'pipe',
221
+ });
222
+
223
+ if (verbose) console.log(`✅ Successfully merged into ${targetBranch}`);
224
+ } else {
225
+ if (verbose) console.log(`✅ Nothing to merge`);
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Create pull request for merge branch
231
+ */
232
+ async function createMergePR(
233
+ sourceBranch,
234
+ targetBranch,
235
+ mergeBranchName,
236
+ reason,
237
+ verbose = false,
238
+ ) {
239
+ if (verbose) console.log('📋 Creating pull request...');
240
+
241
+ const prTitle = `Merge ${sourceBranch} into ${targetBranch}`;
242
+ const prDescription = `Automated merge with changeset.\n\nReason: ${reason}`;
243
+
244
+ const prResult = await createBitbucketPR(
245
+ mergeBranchName,
246
+ targetBranch,
247
+ prTitle,
248
+ prDescription,
249
+ );
250
+
251
+ if (prResult.success) {
252
+ console.log(`✅ Pull request created: ${prResult.url}`);
253
+ } else if (prResult.manual) {
254
+ console.log(`📋 Please create PR manually: ${prResult.url}`);
255
+ } else {
256
+ console.log(`❌ Failed to create pull request: ${prResult.error}`);
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Clean up failed merge
262
+ */
263
+ function cleanupFailedMerge(verbose = false) {
264
+ try {
265
+ execSync('git merge --abort', { stdio: 'pipe' });
266
+ if (verbose) console.log('🧹 Cleaned up failed merge');
267
+ } catch (cleanupError) {
268
+ // Ignore cleanup errors
269
+ }
270
+ }
271
+
272
+ export function setupMerge(program) {
273
+ program
274
+ .command('merge')
275
+ .description(
276
+ 'Merge source branch into target branch with automatic conflict resolution',
277
+ )
278
+ .argument('<sourceBranch>', 'Source branch to merge from')
279
+ .argument('<targetBranch>', 'Target branch to merge into')
280
+ .requiredOption('-b, --build <script>', 'Build script to run after merge')
281
+ .option('-v, --verbose', 'Verbose output')
282
+ .option('--no-push', 'Do not push changes automatically')
283
+ .option('--no-pr', 'Do not create pull request if conflicts remain')
284
+ .option(
285
+ '--prepare-only',
286
+ 'Always create PR instead of direct merge, even if build passes',
287
+ )
288
+ .option('--merge-success', 'Merge with success')
289
+ .option('--merge-fail <reason>', 'Merge with with fail')
290
+ .action(async (sourceBranch, targetBranch, options) => {
291
+ try {
292
+ const {
293
+ verbose,
294
+ push = true,
295
+ pr = true,
296
+ build,
297
+ prepareOnly = false,
298
+ mergeSuccess = false,
299
+ mergeFail = '',
300
+ } = options;
301
+ const isDevMode = process.env.DEV_MODE === 'true';
302
+
303
+ if (verbose) {
304
+ console.log(
305
+ `🔄 Starting merge of ${sourceBranch} into ${targetBranch}`,
306
+ );
307
+ }
308
+
309
+ // 1. Create merge branch from target branch
310
+ const mergeBranchName = createMergeBranch(
311
+ sourceBranch,
312
+ targetBranch,
313
+ mergeSuccess || mergeFail,
314
+ verbose,
315
+ );
316
+
317
+ if (!mergeFail && !mergeSuccess) {
318
+ // 2. Attempt merge
319
+ const { hasConflicts, hasChanges } = attemptMerge(
320
+ sourceBranch,
321
+ targetBranch,
322
+ verbose,
323
+ );
324
+
325
+ // 3. If no changes, exit
326
+ if (!hasChanges) {
327
+ console.log('✅ No changes to merge');
328
+ execSync(`git checkout ${targetBranch}`, { stdio: 'pipe' });
329
+ execSync(`git branch -d ${mergeBranchName}`, { stdio: 'pipe' });
330
+ process.exit(4);
331
+ }
332
+
333
+ // 4. If conflicts, try auto-resolve
334
+ let allConflictsResolved = true;
335
+ if (hasConflicts) {
336
+ if (verbose) console.log('🤖 Running auto-conflict resolution...');
337
+
338
+ const autoResult = await autoResolveConflicts({ verbose });
339
+ allConflictsResolved = autoResult.success;
340
+ }
341
+
342
+ // Get changed packages
343
+ const changedPackages = getChangedPackages(verbose);
344
+
345
+ if (!allConflictsResolved) {
346
+ // 5. Cannot resolve conflicts
347
+ if (verbose)
348
+ console.log('⚠️ Cannot resolve all conflicts automatically');
349
+
350
+ // 5a) Get changed packages (already done above)
351
+ // 5b) Abort merge
352
+ cleanupFailedMerge(verbose);
353
+
354
+ // 5c) Create changeset
355
+ await createAndCommitChangeset(
356
+ sourceBranch,
357
+ targetBranch,
358
+ changedPackages,
359
+ verbose,
360
+ );
361
+
362
+ if (pr && !prepareOnly) {
363
+ // 5d) Push and create PR
364
+ if (push) {
365
+ execSync(`git push -u origin ${mergeBranchName}`, {
366
+ stdio: verbose ? 'inherit' : 'pipe',
367
+ });
368
+ }
369
+
370
+ await createMergePR(
371
+ sourceBranch,
372
+ targetBranch,
373
+ mergeBranchName,
374
+ 'Unresolved conflicts',
375
+ verbose,
376
+ );
377
+ } else {
378
+ console.log(
379
+ `⚠️ Merge completed with unresolved conflicts on branch: ${mergeBranchName}`,
380
+ );
381
+ process.exit(2);
382
+ }
383
+
384
+ console.log(
385
+ `⚠️ Merge completed with unresolved conflicts on branch: ${mergeBranchName}`,
386
+ );
387
+ return;
388
+ }
389
+
390
+ // 6. Can resolve conflicts
391
+ if (verbose) console.log('✅ All conflicts resolved');
392
+
393
+ // 6a) Get changed packages (already done above)
394
+ // 6b) Create changeset
395
+ await createAndCommitChangeset(
396
+ sourceBranch,
397
+ targetBranch,
398
+ changedPackages,
399
+ verbose,
400
+ );
401
+
402
+ // 6c) Run build
403
+ const buildPassed = runBuild(build, verbose);
404
+
405
+ if (buildPassed && !isDevMode && !prepareOnly) {
406
+ // 6d) Build passed - merge into target
407
+ mergeIntoTarget(
408
+ sourceBranch,
409
+ targetBranch,
410
+ mergeBranchName,
411
+ verbose,
412
+ );
413
+ console.log(
414
+ `🎉 Successfully merged ${sourceBranch} into ${targetBranch}`,
415
+ );
416
+ } else if (prepareOnly) {
417
+ if (push) {
418
+ execSync(`git push -u origin ${mergeBranchName}`, {
419
+ stdio: verbose ? 'inherit' : 'pipe',
420
+ });
421
+ }
422
+ if (buildPassed) {
423
+ process.exit(0);
424
+ } else {
425
+ process.exit(3);
426
+ }
427
+ } else {
428
+ // 6e) Build failed, dev mode, or prepare-only - create PR
429
+ if (push) {
430
+ execSync(`git push -u origin ${mergeBranchName}`, {
431
+ stdio: verbose ? 'inherit' : 'pipe',
432
+ });
433
+ }
434
+
435
+ if (pr) {
436
+ let reason;
437
+ if (isDevMode) {
438
+ reason = 'Dev mode enabled';
439
+ } else {
440
+ reason = 'Build failed';
441
+ }
442
+
443
+ await createMergePR(
444
+ sourceBranch,
445
+ targetBranch,
446
+ mergeBranchName,
447
+ reason,
448
+ verbose,
449
+ );
450
+ }
451
+
452
+ let reason;
453
+ if (prepareOnly) {
454
+ reason = 'prepare-only mode';
455
+ } else if (isDevMode) {
456
+ reason = 'dev mode';
457
+ } else {
458
+ reason = 'build failure';
459
+ }
460
+
461
+ console.log(
462
+ `⚠️ Merge completed but creating PR due to ${reason}: ${mergeBranchName}`,
463
+ );
464
+ }
465
+ } else {
466
+ if (mergeSuccess) {
467
+ mergeIntoTarget(
468
+ sourceBranch,
469
+ targetBranch,
470
+ mergeBranchName,
471
+ verbose,
472
+ );
473
+ } else {
474
+ execSync(`git push -u origin ${mergeBranchName}`, {
475
+ stdio: verbose ? 'inherit' : 'pipe',
476
+ });
477
+ await createMergePR(
478
+ sourceBranch,
479
+ targetBranch,
480
+ mergeBranchName,
481
+ mergeFail,
482
+ verbose,
483
+ );
484
+ }
485
+ }
486
+ } catch (error) {
487
+ console.error(`❌ Merge failed: ${error.message}`);
488
+ cleanupFailedMerge();
489
+ process.exit(1);
490
+ }
491
+ });
492
+ }