@docupace/pipeline 1.0.77-alpha.3 → 1.0.77

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/cli.js +71 -0
  44. package/src/micro/ms-deploy.js +271 -0
  45. package/src/micro/{publish-ms.js → ms-publish.js} +6 -8
  46. package/src/next/alias.js +53 -53
  47. package/src/next/apache.js +81 -81
  48. package/src/next/cli.js +1 -10
  49. package/src/next/config.js +137 -137
  50. package/src/next/connect.js +226 -226
  51. package/src/next/copy.js +61 -61
  52. package/src/next/deploy.js +633 -633
  53. package/src/next/git-to-semver.js +79 -79
  54. package/src/next/haproxy.js +76 -76
  55. package/src/next/jenkins.js +123 -123
  56. package/src/next/jira.js +84 -84
  57. package/src/next/logger.js +17 -17
  58. package/src/next/pipline.js +114 -114
  59. package/src/next/publish.js +71 -71
  60. package/src/next/pull.js +7 -7
  61. package/src/next/sonar.js +23 -23
  62. package/src/next/test.js +5 -5
  63. package/src/next/util.js +93 -95
  64. package/src/next/vault.js +45 -45
  65. package/terraform/apply.sh +37 -37
  66. package/terraform/init.sh +72 -72
  67. package/terraform/plan.sh +76 -76
  68. package/terraform/validate.sh +17 -17
  69. package/src/micro/deploy-microservice.js +0 -153
@@ -1,507 +1,507 @@
1
- import { execSync } from 'child_process';
2
- import { readFileSync, existsSync, mkdirSync } from 'fs';
3
- import { join, dirname } from 'path';
4
- import { fileURLToPath } from 'url';
5
- import { getExistingPRsWithDescription } from './pr.js';
6
-
7
- const __dirname = dirname(fileURLToPath(import.meta.url));
8
-
9
- /**
10
- * Read merge configuration from current directory
11
- */
12
- function readMergeConfig(verbose = false) {
13
- const configPath = join(process.cwd(), 'merge-config.json');
14
-
15
- if (!existsSync(configPath)) {
16
- throw new Error('merge-config.json not found in current directory');
17
- }
18
-
19
- try {
20
- const config = JSON.parse(readFileSync(configPath, 'utf8'));
21
-
22
- if (!config.repositories || !Array.isArray(config.repositories)) {
23
- throw new Error('Invalid config: repositories array is required');
24
- }
25
-
26
- // Validate repository objects
27
- config.repositories.forEach((repo, index) => {
28
- if (!repo.url) {
29
- throw new Error(`Repository at index ${index} is missing 'url' field`);
30
- }
31
- if (!repo.buildScript) {
32
- throw new Error(
33
- `Repository at index ${index} is missing 'buildScript' field`,
34
- );
35
- }
36
- if (!repo.name) {
37
- throw new Error(`Repository at index ${index} is missing 'name' field`);
38
- }
39
- });
40
-
41
- if (!config.mergeBranches || !Array.isArray(config.mergeBranches)) {
42
- throw new Error('Invalid config: mergeBranches array is required');
43
- }
44
-
45
- // Validate merge branches
46
- config.mergeBranches.forEach((branch, index) => {
47
- if (!branch.from || !branch.to) {
48
- throw new Error(
49
- `Merge branch at index ${index} is missing 'from' or 'to' field`,
50
- );
51
- }
52
- if (typeof branch.from !== 'object' || typeof branch.to !== 'object') {
53
- throw new Error(
54
- `Merge branch at index ${index}: 'from' and 'to' must be objects`,
55
- );
56
- }
57
- if (!branch.from['*'] || !branch.to['*']) {
58
- throw new Error(
59
- `Merge branch at index ${index}: 'from' and 'to' must have '*' default value`,
60
- );
61
- }
62
- });
63
-
64
- if (verbose) {
65
- console.log('šŸ“‹ Configuration loaded:');
66
- console.log(` Repositories: ${config.repositories.length}`);
67
- console.log(` Merge branches: ${config.mergeBranches.length}`);
68
- }
69
-
70
- return config;
71
- } catch (error) {
72
- throw new Error(`Failed to parse merge-config.json: ${error.message}`);
73
- }
74
- }
75
-
76
- /**
77
- * Extract repository name from git URL
78
- */
79
- function getRepoName(gitUrl) {
80
- const match = gitUrl.match(/\/([^\/]+)\.git$/);
81
- return match ? match[1] : gitUrl.split('/').pop().replace('.git', '');
82
- }
83
-
84
- /**
85
- * Get branch name for a specific repository
86
- */
87
- function getBranchForRepo(branchConfig, repoName) {
88
- // Check if there's a specific override for this repository
89
- if (branchConfig[repoName]) {
90
- return branchConfig[repoName];
91
- }
92
- // Fall back to the default branch
93
- return branchConfig['*'];
94
- }
95
-
96
- /**
97
- * Clone or update repository
98
- */
99
- function setupRepository(repository, reposDir, verbose = false) {
100
- const repoName = getRepoName(repository.url);
101
- const repoPath = join(reposDir, repoName);
102
-
103
- if (existsSync(repoPath)) {
104
- if (verbose) console.log(`šŸ“ Repository ${repoName} exists, updating...`);
105
-
106
- execSync('git fetch --all', {
107
- cwd: repoPath,
108
- stdio: verbose ? 'inherit' : 'pipe',
109
- });
110
- } else {
111
- if (verbose) console.log(`šŸ“„ Cloning repository ${repoName}...`);
112
-
113
- execSync(`git clone ${repository.url} ${repoName}`, {
114
- cwd: reposDir,
115
- stdio: verbose ? 'inherit' : 'pipe',
116
- });
117
- }
118
-
119
- return {
120
- repoName,
121
- repoPath,
122
- buildScript: repository.buildScript,
123
- configName: repository.name,
124
- };
125
- }
126
-
127
- /**
128
- * Run merge command in repository with specific flags
129
- */
130
- function runMergeCommand(
131
- repoPath,
132
- fromBranch,
133
- toBranch,
134
- buildScript,
135
- flags = '',
136
- verbose = false,
137
- ) {
138
- const cliPath = join(__dirname, 'cli.js');
139
- const verboseFlag = verbose ? '--verbose' : '';
140
-
141
- const command =
142
- `node ${cliPath} merge ${fromBranch} ${toBranch} --build "${buildScript}" ${flags} ${verboseFlag}`.trim();
143
-
144
- if (verbose) {
145
- console.log(`šŸ“„ Running command in ${repoPath}`);
146
- console.log(`Command: ${command}`);
147
- }
148
-
149
- try {
150
- execSync(command, {
151
- cwd: repoPath,
152
- stdio: verbose ? 'inherit' : 'pipe',
153
- encoding: 'utf8',
154
- });
155
-
156
- return { exitCode: 0, error: null };
157
- } catch (error) {
158
- // Extract exit code from error
159
- const exitCode = error.status || 1;
160
- return { exitCode, error: error.message };
161
- }
162
- }
163
-
164
- /**
165
- * Run initial prepare-only phase for all repositories
166
- */
167
- async function runPreparePhase(
168
- mergeBranch,
169
- repositories,
170
- reposDir,
171
- verbose = false,
172
- ) {
173
- const { from: fromConfig, to: toConfig } = mergeBranch;
174
- console.log(`\nšŸ”„ Phase 1: Running prepare-only for all repositories`);
175
-
176
- const results = [];
177
-
178
- for (let i = 0; i < repositories.length; i++) {
179
- const repository = repositories[i];
180
- const { repoName, repoPath, buildScript, configName } = setupRepository(
181
- repository,
182
- reposDir,
183
- verbose,
184
- );
185
-
186
- // Get the specific branches for this repository
187
- const fromBranch = getBranchForRepo(fromConfig, configName);
188
- const toBranch = getBranchForRepo(toConfig, configName);
189
-
190
- console.log(
191
- `\nšŸ“¦ [${i + 1}/${repositories.length}] Testing ${configName} (${repoName}): ${fromBranch} → ${toBranch}`,
192
- );
193
-
194
- const result = runMergeCommand(
195
- repoPath,
196
- fromBranch,
197
- toBranch,
198
- buildScript,
199
- '--prepare-only',
200
- verbose,
201
- );
202
-
203
- results.push({
204
- repository: repoName,
205
- configName,
206
- url: repository.url,
207
- buildScript,
208
- fromBranch,
209
- toBranch,
210
- exitCode: result.exitCode,
211
- error: result.error,
212
- repoPath,
213
- });
214
-
215
- // Log result based on exit code
216
- switch (result.exitCode) {
217
- case 0:
218
- console.log(`āœ… ${configName}: Merge and build successful`);
219
- break;
220
- case 2:
221
- console.log(`āš ļø ${configName}: Merge has unresolved conflicts`);
222
- break;
223
- case 3:
224
- console.log(`āš ļø ${configName}: Build failed`);
225
- break;
226
- case 4:
227
- console.log(`āœ… ${configName}: No Changes`);
228
- break;
229
- default:
230
- console.log(
231
- `āŒ ${configName}: Unknown error (exit code: ${result.exitCode})`,
232
- );
233
- }
234
- }
235
-
236
- return results;
237
- }
238
-
239
- /**
240
- * Run follow-up phase based on prepare phase results
241
- */
242
- async function runFollowUpPhase(prepareResults, verbose = false) {
243
- console.log(`\nšŸ”„ Phase 2: Running follow-up based on prepare phase results`);
244
-
245
- // Check if all repositories passed
246
- const allPassed = prepareResults.every(
247
- (r) => r.exitCode === 0 || r.exitCode === 4,
248
- );
249
-
250
- for (let i = 0; i < prepareResults.length; i++) {
251
- const result = prepareResults[i];
252
- console.log(
253
- `\nšŸ“¦ [${i + 1}/${prepareResults.length}] Processing ${result.configName}`,
254
- );
255
-
256
- let flags = '';
257
- let action = '';
258
-
259
- if (allPassed) {
260
- // All passed, merge successfully
261
- flags = '--merge-success';
262
- action = 'Merging successfully';
263
- } else {
264
- // At least one failed, determine the appropriate action
265
- switch (result.exitCode) {
266
- case 0:
267
- // This one passed but others failed
268
- flags = '--merge-fail "Waiting for success for other failed merges"';
269
- action = 'Creating PR (waiting for other repos)';
270
- break;
271
- case 2:
272
- // Merge failed
273
- flags = '--merge-fail "Merge Failed"';
274
- action = 'Creating PR (merge conflicts)';
275
- break;
276
- case 3:
277
- // Build failed
278
- flags = '--merge-fail "Build Failed"';
279
- action = 'Creating PR (build failed)';
280
- break;
281
- default:
282
- // Unknown error
283
- flags = '--merge-fail "Unknown Error"';
284
- action = 'Creating PR (unknown error)';
285
- }
286
- }
287
-
288
- console.log(` Action: ${action}`);
289
-
290
- if (result.exitCode !== 4) {
291
- const followUpResult = runMergeCommand(
292
- result.repoPath,
293
- result.fromBranch,
294
- result.toBranch,
295
- result.buildScript,
296
- flags,
297
- verbose,
298
- );
299
-
300
- // Update the result with follow-up information
301
- result.followUpExitCode = followUpResult.exitCode;
302
- result.followUpError = followUpResult.error;
303
- result.followUpAction = action;
304
-
305
- if (followUpResult.exitCode === 0) {
306
- console.log(`āœ… ${result.configName}: ${action} completed`);
307
- } else {
308
- console.log(`āŒ ${result.configName}: ${action} failed`);
309
- process.exit(1);
310
- }
311
- }
312
- }
313
-
314
- return prepareResults;
315
- }
316
-
317
- /**
318
- * Process a single merge branch across all repositories
319
- */
320
- async function processMergeBranch(
321
- mergeBranch,
322
- repositories,
323
- reposDir,
324
- verbose = false,
325
- ) {
326
- console.log(`\nšŸ”€ Processing merge branches`);
327
-
328
- // Phase 1: Run prepare-only for all repositories
329
- const prepareResults = await runPreparePhase(
330
- mergeBranch,
331
- repositories,
332
- reposDir,
333
- verbose,
334
- );
335
-
336
- // Phase 2: Run follow-up based on results
337
- const finalResults = await runFollowUpPhase(prepareResults, verbose);
338
-
339
- // Summary
340
- const allPassed = prepareResults.every(
341
- (r) => r.exitCode === 0 || r.exitCode === 4,
342
- );
343
- const mergeFailures = prepareResults.filter((r) => r.exitCode === 2);
344
- const buildFailures = prepareResults.filter((r) => r.exitCode === 3);
345
- const successes = prepareResults.filter((r) => r.exitCode === 0);
346
-
347
- console.log(`\nšŸ“Š Merge summary:`);
348
- console.log(` āœ… Successful: ${successes.length}/${repositories.length}`);
349
- console.log(` āš ļø Merge conflicts: ${mergeFailures.length}`);
350
- console.log(` āš ļø Build failures: ${buildFailures.length}`);
351
-
352
- if (allPassed) {
353
- console.log(` šŸŽ‰ All repositories merged successfully!`);
354
- } else {
355
- console.log(` āš ļø Some repositories require manual intervention via PRs`);
356
- }
357
-
358
- return {
359
- success: allPassed,
360
- results: finalResults,
361
- summary: {
362
- total: repositories.length,
363
- successes: successes.length,
364
- mergeFailures: mergeFailures.length,
365
- buildFailures: buildFailures.length,
366
- },
367
- };
368
- }
369
-
370
- /**
371
- * Main merge-all function
372
- */
373
- async function mergeAll(verbose = false) {
374
- try {
375
- // Read configuration
376
- const config = readMergeConfig(verbose);
377
-
378
- // Check for existing PRs with "Automated merge with changeset" description
379
- if (verbose) console.log('šŸ” Checking for existing automated merge PRs...');
380
- const existingPRs = await getExistingPRsWithDescription(
381
- 'Automated merge with changeset',
382
- config.repositories,
383
- );
384
-
385
- if (existingPRs.length > 0) {
386
- console.log(
387
- `āŒ Found ${existingPRs.length} existing pull request(s) with "Automated merge with changeset" description. Exiting to prevent conflicts.`,
388
- );
389
- if (verbose) {
390
- existingPRs.forEach((pr) => {
391
- console.log(
392
- ` - PR #${pr.id}: ${pr.title} in ${pr.repositoryName || 'unknown repo'} (${pr.links.html.href})`,
393
- );
394
- });
395
- }
396
- process.exit(1);
397
- }
398
-
399
- if (verbose)
400
- console.log('āœ… No existing automated merge PRs found. Proceeding...');
401
-
402
- // Setup repos directory
403
- const reposDir = join(process.cwd(), 'repos');
404
- if (!existsSync(reposDir)) {
405
- mkdirSync(reposDir, { recursive: true });
406
- if (verbose) console.log(`šŸ“ Created repos directory: ${reposDir}`);
407
- }
408
-
409
- console.log(`šŸš€ Starting merge-all process`);
410
- console.log(`Repositories: ${config.repositories.length}`);
411
- console.log(`Merge branches: ${config.mergeBranches.length}`);
412
-
413
- const allResults = [];
414
-
415
- // Process each merge branch in sequence
416
- for (let i = 0; i < config.mergeBranches.length; i++) {
417
- const mergeBranch = config.mergeBranches[i];
418
- console.log(`\n═══════════════════════════════════════`);
419
- console.log(
420
- `Processing merge branch set ${i + 1}/${config.mergeBranches.length}`,
421
- );
422
- console.log(`═══════════════════════════════════════`);
423
-
424
- const branchResult = await processMergeBranch(
425
- mergeBranch,
426
- config.repositories,
427
- reposDir,
428
- verbose,
429
- );
430
-
431
- if (!branchResult.success) {
432
- console.log(`Exiting due to failure of one or more merges`);
433
- process.exit(1);
434
- }
435
-
436
- allResults.push({
437
- mergeBranch,
438
- ...branchResult,
439
- });
440
- }
441
-
442
- // Final summary
443
- console.log(`\n═══════════════════════════════════════`);
444
- console.log(`šŸ“ˆ Final Summary:`);
445
- console.log(`═══════════════════════════════════════`);
446
-
447
- allResults.forEach((result, index) => {
448
- const { from, to } = result.mergeBranch;
449
- const status = result.success ? 'āœ…' : 'āš ļø';
450
- console.log(`\n${status} Merge branch set ${index + 1}:`);
451
-
452
- if (result.summary) {
453
- console.log(` Total repositories: ${result.summary.total}`);
454
- console.log(` Successful merges: ${result.summary.successes}`);
455
- console.log(` Merge conflicts: ${result.summary.mergeFailures}`);
456
- console.log(` Build failures: ${result.summary.buildFailures}`);
457
- }
458
-
459
- if (result.results && verbose) {
460
- console.log(`\n Repository details:`);
461
- result.results.forEach((repo) => {
462
- let repoStatus = 'ā“';
463
- if (repo.exitCode === 0) repoStatus = 'āœ…';
464
- else if (repo.exitCode === 2) repoStatus = 'āš ļø (merge)';
465
- else if (repo.exitCode === 3) repoStatus = 'āš ļø (build)';
466
-
467
- console.log(
468
- ` ${repoStatus} ${repo.configName}: ${repo.fromBranch} → ${repo.toBranch}`,
469
- );
470
- });
471
- }
472
- });
473
-
474
- const overallSuccess = allResults.every((r) => r.success);
475
- if (overallSuccess) {
476
- console.log(`\nšŸŽ‰ All merge operations completed successfully!`);
477
- } else {
478
- console.log(
479
- `\nāš ļø Some merge operations created PRs. Check the repositories for details.`,
480
- );
481
- }
482
-
483
- return { success: overallSuccess, results: allResults };
484
- } catch (error) {
485
- console.error(`āŒ Merge-all failed: ${error.message}`);
486
- throw error;
487
- }
488
- }
489
-
490
- export function setupMergeAll(program) {
491
- program
492
- .command('merge-all')
493
- .description(
494
- 'Merge multiple branches across multiple repositories based on configuration',
495
- )
496
- .option('-v, --verbose', 'Verbose output')
497
- .action(async (options) => {
498
- try {
499
- const { verbose } = options;
500
-
501
- await mergeAll(verbose);
502
- } catch (error) {
503
- console.error(`āŒ merge-all command failed: ${error.message}`);
504
- process.exit(1);
505
- }
506
- });
507
- }
1
+ import { execSync } from 'child_process';
2
+ import { readFileSync, existsSync, mkdirSync } from 'fs';
3
+ import { join, dirname } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { getExistingPRsWithDescription } from './pr.js';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+
9
+ /**
10
+ * Read merge configuration from current directory
11
+ */
12
+ function readMergeConfig(verbose = false) {
13
+ const configPath = join(process.cwd(), 'merge-config.json');
14
+
15
+ if (!existsSync(configPath)) {
16
+ throw new Error('merge-config.json not found in current directory');
17
+ }
18
+
19
+ try {
20
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
21
+
22
+ if (!config.repositories || !Array.isArray(config.repositories)) {
23
+ throw new Error('Invalid config: repositories array is required');
24
+ }
25
+
26
+ // Validate repository objects
27
+ config.repositories.forEach((repo, index) => {
28
+ if (!repo.url) {
29
+ throw new Error(`Repository at index ${index} is missing 'url' field`);
30
+ }
31
+ if (!repo.buildScript) {
32
+ throw new Error(
33
+ `Repository at index ${index} is missing 'buildScript' field`,
34
+ );
35
+ }
36
+ if (!repo.name) {
37
+ throw new Error(`Repository at index ${index} is missing 'name' field`);
38
+ }
39
+ });
40
+
41
+ if (!config.mergeBranches || !Array.isArray(config.mergeBranches)) {
42
+ throw new Error('Invalid config: mergeBranches array is required');
43
+ }
44
+
45
+ // Validate merge branches
46
+ config.mergeBranches.forEach((branch, index) => {
47
+ if (!branch.from || !branch.to) {
48
+ throw new Error(
49
+ `Merge branch at index ${index} is missing 'from' or 'to' field`,
50
+ );
51
+ }
52
+ if (typeof branch.from !== 'object' || typeof branch.to !== 'object') {
53
+ throw new Error(
54
+ `Merge branch at index ${index}: 'from' and 'to' must be objects`,
55
+ );
56
+ }
57
+ if (!branch.from['*'] || !branch.to['*']) {
58
+ throw new Error(
59
+ `Merge branch at index ${index}: 'from' and 'to' must have '*' default value`,
60
+ );
61
+ }
62
+ });
63
+
64
+ if (verbose) {
65
+ console.log('šŸ“‹ Configuration loaded:');
66
+ console.log(` Repositories: ${config.repositories.length}`);
67
+ console.log(` Merge branches: ${config.mergeBranches.length}`);
68
+ }
69
+
70
+ return config;
71
+ } catch (error) {
72
+ throw new Error(`Failed to parse merge-config.json: ${error.message}`);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Extract repository name from git URL
78
+ */
79
+ function getRepoName(gitUrl) {
80
+ const match = gitUrl.match(/\/([^\/]+)\.git$/);
81
+ return match ? match[1] : gitUrl.split('/').pop().replace('.git', '');
82
+ }
83
+
84
+ /**
85
+ * Get branch name for a specific repository
86
+ */
87
+ function getBranchForRepo(branchConfig, repoName) {
88
+ // Check if there's a specific override for this repository
89
+ if (branchConfig[repoName]) {
90
+ return branchConfig[repoName];
91
+ }
92
+ // Fall back to the default branch
93
+ return branchConfig['*'];
94
+ }
95
+
96
+ /**
97
+ * Clone or update repository
98
+ */
99
+ function setupRepository(repository, reposDir, verbose = false) {
100
+ const repoName = getRepoName(repository.url);
101
+ const repoPath = join(reposDir, repoName);
102
+
103
+ if (existsSync(repoPath)) {
104
+ if (verbose) console.log(`šŸ“ Repository ${repoName} exists, updating...`);
105
+
106
+ execSync('git fetch --all', {
107
+ cwd: repoPath,
108
+ stdio: verbose ? 'inherit' : 'pipe',
109
+ });
110
+ } else {
111
+ if (verbose) console.log(`šŸ“„ Cloning repository ${repoName}...`);
112
+
113
+ execSync(`git clone ${repository.url} ${repoName}`, {
114
+ cwd: reposDir,
115
+ stdio: verbose ? 'inherit' : 'pipe',
116
+ });
117
+ }
118
+
119
+ return {
120
+ repoName,
121
+ repoPath,
122
+ buildScript: repository.buildScript,
123
+ configName: repository.name,
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Run merge command in repository with specific flags
129
+ */
130
+ function runMergeCommand(
131
+ repoPath,
132
+ fromBranch,
133
+ toBranch,
134
+ buildScript,
135
+ flags = '',
136
+ verbose = false,
137
+ ) {
138
+ const cliPath = join(__dirname, 'cli.js');
139
+ const verboseFlag = verbose ? '--verbose' : '';
140
+
141
+ const command =
142
+ `node ${cliPath} merge ${fromBranch} ${toBranch} --build "${buildScript}" ${flags} ${verboseFlag}`.trim();
143
+
144
+ if (verbose) {
145
+ console.log(`šŸ“„ Running command in ${repoPath}`);
146
+ console.log(`Command: ${command}`);
147
+ }
148
+
149
+ try {
150
+ execSync(command, {
151
+ cwd: repoPath,
152
+ stdio: verbose ? 'inherit' : 'pipe',
153
+ encoding: 'utf8',
154
+ });
155
+
156
+ return { exitCode: 0, error: null };
157
+ } catch (error) {
158
+ // Extract exit code from error
159
+ const exitCode = error.status || 1;
160
+ return { exitCode, error: error.message };
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Run initial prepare-only phase for all repositories
166
+ */
167
+ async function runPreparePhase(
168
+ mergeBranch,
169
+ repositories,
170
+ reposDir,
171
+ verbose = false,
172
+ ) {
173
+ const { from: fromConfig, to: toConfig } = mergeBranch;
174
+ console.log(`\nšŸ”„ Phase 1: Running prepare-only for all repositories`);
175
+
176
+ const results = [];
177
+
178
+ for (let i = 0; i < repositories.length; i++) {
179
+ const repository = repositories[i];
180
+ const { repoName, repoPath, buildScript, configName } = setupRepository(
181
+ repository,
182
+ reposDir,
183
+ verbose,
184
+ );
185
+
186
+ // Get the specific branches for this repository
187
+ const fromBranch = getBranchForRepo(fromConfig, configName);
188
+ const toBranch = getBranchForRepo(toConfig, configName);
189
+
190
+ console.log(
191
+ `\nšŸ“¦ [${i + 1}/${repositories.length}] Testing ${configName} (${repoName}): ${fromBranch} → ${toBranch}`,
192
+ );
193
+
194
+ const result = runMergeCommand(
195
+ repoPath,
196
+ fromBranch,
197
+ toBranch,
198
+ buildScript,
199
+ '--prepare-only',
200
+ verbose,
201
+ );
202
+
203
+ results.push({
204
+ repository: repoName,
205
+ configName,
206
+ url: repository.url,
207
+ buildScript,
208
+ fromBranch,
209
+ toBranch,
210
+ exitCode: result.exitCode,
211
+ error: result.error,
212
+ repoPath,
213
+ });
214
+
215
+ // Log result based on exit code
216
+ switch (result.exitCode) {
217
+ case 0:
218
+ console.log(`āœ… ${configName}: Merge and build successful`);
219
+ break;
220
+ case 2:
221
+ console.log(`āš ļø ${configName}: Merge has unresolved conflicts`);
222
+ break;
223
+ case 3:
224
+ console.log(`āš ļø ${configName}: Build failed`);
225
+ break;
226
+ case 4:
227
+ console.log(`āœ… ${configName}: No Changes`);
228
+ break;
229
+ default:
230
+ console.log(
231
+ `āŒ ${configName}: Unknown error (exit code: ${result.exitCode})`,
232
+ );
233
+ }
234
+ }
235
+
236
+ return results;
237
+ }
238
+
239
+ /**
240
+ * Run follow-up phase based on prepare phase results
241
+ */
242
+ async function runFollowUpPhase(prepareResults, verbose = false) {
243
+ console.log(`\nšŸ”„ Phase 2: Running follow-up based on prepare phase results`);
244
+
245
+ // Check if all repositories passed
246
+ const allPassed = prepareResults.every(
247
+ (r) => r.exitCode === 0 || r.exitCode === 4,
248
+ );
249
+
250
+ for (let i = 0; i < prepareResults.length; i++) {
251
+ const result = prepareResults[i];
252
+ console.log(
253
+ `\nšŸ“¦ [${i + 1}/${prepareResults.length}] Processing ${result.configName}`,
254
+ );
255
+
256
+ let flags = '';
257
+ let action = '';
258
+
259
+ if (allPassed) {
260
+ // All passed, merge successfully
261
+ flags = '--merge-success';
262
+ action = 'Merging successfully';
263
+ } else {
264
+ // At least one failed, determine the appropriate action
265
+ switch (result.exitCode) {
266
+ case 0:
267
+ // This one passed but others failed
268
+ flags = '--merge-fail "Waiting for success for other failed merges"';
269
+ action = 'Creating PR (waiting for other repos)';
270
+ break;
271
+ case 2:
272
+ // Merge failed
273
+ flags = '--merge-fail "Merge Failed"';
274
+ action = 'Creating PR (merge conflicts)';
275
+ break;
276
+ case 3:
277
+ // Build failed
278
+ flags = '--merge-fail "Build Failed"';
279
+ action = 'Creating PR (build failed)';
280
+ break;
281
+ default:
282
+ // Unknown error
283
+ flags = '--merge-fail "Unknown Error"';
284
+ action = 'Creating PR (unknown error)';
285
+ }
286
+ }
287
+
288
+ console.log(` Action: ${action}`);
289
+
290
+ if (result.exitCode !== 4) {
291
+ const followUpResult = runMergeCommand(
292
+ result.repoPath,
293
+ result.fromBranch,
294
+ result.toBranch,
295
+ result.buildScript,
296
+ flags,
297
+ verbose,
298
+ );
299
+
300
+ // Update the result with follow-up information
301
+ result.followUpExitCode = followUpResult.exitCode;
302
+ result.followUpError = followUpResult.error;
303
+ result.followUpAction = action;
304
+
305
+ if (followUpResult.exitCode === 0) {
306
+ console.log(`āœ… ${result.configName}: ${action} completed`);
307
+ } else {
308
+ console.log(`āŒ ${result.configName}: ${action} failed`);
309
+ process.exit(1);
310
+ }
311
+ }
312
+ }
313
+
314
+ return prepareResults;
315
+ }
316
+
317
+ /**
318
+ * Process a single merge branch across all repositories
319
+ */
320
+ async function processMergeBranch(
321
+ mergeBranch,
322
+ repositories,
323
+ reposDir,
324
+ verbose = false,
325
+ ) {
326
+ console.log(`\nšŸ”€ Processing merge branches`);
327
+
328
+ // Phase 1: Run prepare-only for all repositories
329
+ const prepareResults = await runPreparePhase(
330
+ mergeBranch,
331
+ repositories,
332
+ reposDir,
333
+ verbose,
334
+ );
335
+
336
+ // Phase 2: Run follow-up based on results
337
+ const finalResults = await runFollowUpPhase(prepareResults, verbose);
338
+
339
+ // Summary
340
+ const allPassed = prepareResults.every(
341
+ (r) => r.exitCode === 0 || r.exitCode === 4,
342
+ );
343
+ const mergeFailures = prepareResults.filter((r) => r.exitCode === 2);
344
+ const buildFailures = prepareResults.filter((r) => r.exitCode === 3);
345
+ const successes = prepareResults.filter((r) => r.exitCode === 0);
346
+
347
+ console.log(`\nšŸ“Š Merge summary:`);
348
+ console.log(` āœ… Successful: ${successes.length}/${repositories.length}`);
349
+ console.log(` āš ļø Merge conflicts: ${mergeFailures.length}`);
350
+ console.log(` āš ļø Build failures: ${buildFailures.length}`);
351
+
352
+ if (allPassed) {
353
+ console.log(` šŸŽ‰ All repositories merged successfully!`);
354
+ } else {
355
+ console.log(` āš ļø Some repositories require manual intervention via PRs`);
356
+ }
357
+
358
+ return {
359
+ success: allPassed,
360
+ results: finalResults,
361
+ summary: {
362
+ total: repositories.length,
363
+ successes: successes.length,
364
+ mergeFailures: mergeFailures.length,
365
+ buildFailures: buildFailures.length,
366
+ },
367
+ };
368
+ }
369
+
370
+ /**
371
+ * Main merge-all function
372
+ */
373
+ async function mergeAll(verbose = false) {
374
+ try {
375
+ // Read configuration
376
+ const config = readMergeConfig(verbose);
377
+
378
+ // Check for existing PRs with "Automated merge with changeset" description
379
+ if (verbose) console.log('šŸ” Checking for existing automated merge PRs...');
380
+ const existingPRs = await getExistingPRsWithDescription(
381
+ 'Automated merge with changeset',
382
+ config.repositories,
383
+ );
384
+
385
+ if (existingPRs.length > 0) {
386
+ console.log(
387
+ `āŒ Found ${existingPRs.length} existing pull request(s) with "Automated merge with changeset" description. Exiting to prevent conflicts.`,
388
+ );
389
+ if (verbose) {
390
+ existingPRs.forEach((pr) => {
391
+ console.log(
392
+ ` - PR #${pr.id}: ${pr.title} in ${pr.repositoryName || 'unknown repo'} (${pr.links.html.href})`,
393
+ );
394
+ });
395
+ }
396
+ process.exit(1);
397
+ }
398
+
399
+ if (verbose)
400
+ console.log('āœ… No existing automated merge PRs found. Proceeding...');
401
+
402
+ // Setup repos directory
403
+ const reposDir = join(process.cwd(), 'repos');
404
+ if (!existsSync(reposDir)) {
405
+ mkdirSync(reposDir, { recursive: true });
406
+ if (verbose) console.log(`šŸ“ Created repos directory: ${reposDir}`);
407
+ }
408
+
409
+ console.log(`šŸš€ Starting merge-all process`);
410
+ console.log(`Repositories: ${config.repositories.length}`);
411
+ console.log(`Merge branches: ${config.mergeBranches.length}`);
412
+
413
+ const allResults = [];
414
+
415
+ // Process each merge branch in sequence
416
+ for (let i = 0; i < config.mergeBranches.length; i++) {
417
+ const mergeBranch = config.mergeBranches[i];
418
+ console.log(`\n═══════════════════════════════════════`);
419
+ console.log(
420
+ `Processing merge branch set ${i + 1}/${config.mergeBranches.length}`,
421
+ );
422
+ console.log(`═══════════════════════════════════════`);
423
+
424
+ const branchResult = await processMergeBranch(
425
+ mergeBranch,
426
+ config.repositories,
427
+ reposDir,
428
+ verbose,
429
+ );
430
+
431
+ if (!branchResult.success) {
432
+ console.log(`Exiting due to failure of one or more merges`);
433
+ process.exit(1);
434
+ }
435
+
436
+ allResults.push({
437
+ mergeBranch,
438
+ ...branchResult,
439
+ });
440
+ }
441
+
442
+ // Final summary
443
+ console.log(`\n═══════════════════════════════════════`);
444
+ console.log(`šŸ“ˆ Final Summary:`);
445
+ console.log(`═══════════════════════════════════════`);
446
+
447
+ allResults.forEach((result, index) => {
448
+ const { from, to } = result.mergeBranch;
449
+ const status = result.success ? 'āœ…' : 'āš ļø';
450
+ console.log(`\n${status} Merge branch set ${index + 1}:`);
451
+
452
+ if (result.summary) {
453
+ console.log(` Total repositories: ${result.summary.total}`);
454
+ console.log(` Successful merges: ${result.summary.successes}`);
455
+ console.log(` Merge conflicts: ${result.summary.mergeFailures}`);
456
+ console.log(` Build failures: ${result.summary.buildFailures}`);
457
+ }
458
+
459
+ if (result.results && verbose) {
460
+ console.log(`\n Repository details:`);
461
+ result.results.forEach((repo) => {
462
+ let repoStatus = 'ā“';
463
+ if (repo.exitCode === 0) repoStatus = 'āœ…';
464
+ else if (repo.exitCode === 2) repoStatus = 'āš ļø (merge)';
465
+ else if (repo.exitCode === 3) repoStatus = 'āš ļø (build)';
466
+
467
+ console.log(
468
+ ` ${repoStatus} ${repo.configName}: ${repo.fromBranch} → ${repo.toBranch}`,
469
+ );
470
+ });
471
+ }
472
+ });
473
+
474
+ const overallSuccess = allResults.every((r) => r.success);
475
+ if (overallSuccess) {
476
+ console.log(`\nšŸŽ‰ All merge operations completed successfully!`);
477
+ } else {
478
+ console.log(
479
+ `\nāš ļø Some merge operations created PRs. Check the repositories for details.`,
480
+ );
481
+ }
482
+
483
+ return { success: overallSuccess, results: allResults };
484
+ } catch (error) {
485
+ console.error(`āŒ Merge-all failed: ${error.message}`);
486
+ throw error;
487
+ }
488
+ }
489
+
490
+ export function setupMergeAll(program) {
491
+ program
492
+ .command('merge-all')
493
+ .description(
494
+ 'Merge multiple branches across multiple repositories based on configuration',
495
+ )
496
+ .option('-v, --verbose', 'Verbose output')
497
+ .action(async (options) => {
498
+ try {
499
+ const { verbose } = options;
500
+
501
+ await mergeAll(verbose);
502
+ } catch (error) {
503
+ console.error(`āŒ merge-all command failed: ${error.message}`);
504
+ process.exit(1);
505
+ }
506
+ });
507
+ }