just-ship-it 0.0.1

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,752 @@
1
+ import { confirm, select, text, isCancel, cancel } from '@clack/prompts';
2
+ import ora from 'ora';
3
+ import { GitOperations } from './git.js';
4
+ import { GitHubOperations } from './github.js';
5
+ import { logger } from './logger.js';
6
+ import { generateChangesetMessage, suggestReleaseType, analyzePackageChange, generateMultiPackageChangesetMessage } from './ai.js';
7
+ import { analyzePackageChanges, findMaxVersionType } from './workspace.js';
8
+ const TOTAL_STEPS = 10;
9
+ export async function runRelease(config, options = {}) {
10
+ logger.header(`Release: ${config.repo}`);
11
+ const git = new GitOperations(config);
12
+ const github = new GitHubOperations(config);
13
+ let spinner = null;
14
+ try {
15
+ // Clone repository first to detect workspace type
16
+ logger.step(1, TOTAL_STEPS, 'Cloning repository from main...');
17
+ spinner = ora('Cloning...').start();
18
+ await git.clone();
19
+ spinner.succeed('Repository cloned');
20
+ // Detect workspace type
21
+ const workspace = await git.getWorkspaceInfo();
22
+ logger.detail(`Workspace type: ${workspace.type}`);
23
+ if (workspace.type === 'single') {
24
+ return runSinglePackageRelease(git, github, config, options);
25
+ }
26
+ return runMonorepoRelease(workspace, git, github, config, options);
27
+ }
28
+ catch (error) {
29
+ spinner?.fail();
30
+ logger.error(`Release failed: ${error instanceof Error ? error.message : String(error)}`);
31
+ try {
32
+ await git.cleanup();
33
+ }
34
+ catch {
35
+ // Ignore cleanup errors
36
+ }
37
+ process.exit(1);
38
+ }
39
+ }
40
+ /**
41
+ * Single package release flow (original logic)
42
+ */
43
+ async function runSinglePackageRelease(git, github, config, options = {}) {
44
+ let spinner = null;
45
+ try {
46
+ // Step 1 already done (clone)
47
+ const packageName = await git.getPackageName();
48
+ const currentVersion = await git.getPackageVersion();
49
+ logger.detail(`Package: ${packageName} @ ${currentVersion}`);
50
+ // Find the latest version tag and get diff
51
+ spinner = ora('Finding latest version tag...').start();
52
+ const latestTag = await git.getLatestVersionTag();
53
+ if (!latestTag) {
54
+ spinner.fail('No version tags found');
55
+ logger.warn('Cannot find previous version tag. Using recent commits instead.');
56
+ }
57
+ else {
58
+ spinner.succeed(`Latest version: ${latestTag}`);
59
+ }
60
+ // Build diff context
61
+ let diffContext;
62
+ if (latestTag) {
63
+ spinner = ora('Analyzing changes since last release...').start();
64
+ const [commits, diffSummary, diff] = await Promise.all([
65
+ git.getCommitsSinceTag(latestTag),
66
+ git.getDiffSummary(latestTag),
67
+ git.getFullDiffSinceTag(latestTag),
68
+ ]);
69
+ spinner.succeed(`Found ${commits.length} commits, ${diffSummary.files.length} files changed`);
70
+ diffContext = {
71
+ commits,
72
+ diff,
73
+ filesChanged: diffSummary.files,
74
+ insertions: diffSummary.insertions,
75
+ deletions: diffSummary.deletions,
76
+ previousVersion: latestTag,
77
+ };
78
+ logger.detail(`Changes: +${diffContext.insertions}/-${diffContext.deletions} lines`);
79
+ }
80
+ else {
81
+ // Fallback to recent commits if no tag found
82
+ const recentCommits = await git.getRecentCommits(15);
83
+ diffContext = {
84
+ commits: recentCommits,
85
+ diff: '',
86
+ filesChanged: [],
87
+ insertions: 0,
88
+ deletions: 0,
89
+ previousVersion: 'unknown',
90
+ };
91
+ }
92
+ // Determine release type
93
+ let releaseType;
94
+ if (options.type) {
95
+ releaseType = options.type;
96
+ }
97
+ else {
98
+ // Use AI to suggest release type based on diff
99
+ spinner = ora('Analyzing changes...').start();
100
+ const suggestedType = await suggestReleaseType(diffContext);
101
+ spinner.succeed(`AI suggests: ${suggestedType} release`);
102
+ const selectedType = await select({
103
+ message: 'What type of release is this?',
104
+ options: [
105
+ { label: 'patch - Bug fixes, small changes', value: 'patch' },
106
+ { label: 'minor - New features, backwards compatible', value: 'minor' },
107
+ { label: 'major - Breaking changes', value: 'major' },
108
+ ],
109
+ initialValue: suggestedType,
110
+ });
111
+ if (isCancel(selectedType)) {
112
+ cancel('Release cancelled');
113
+ await git.cleanup();
114
+ process.exit(0);
115
+ }
116
+ releaseType = selectedType;
117
+ }
118
+ // Generate release message with AI
119
+ let releaseMessage;
120
+ if (options.message) {
121
+ releaseMessage = options.message;
122
+ }
123
+ else {
124
+ spinner = ora('Generating changeset description with AI...').start();
125
+ try {
126
+ const aiMessage = await generateChangesetMessage(packageName, releaseType, diffContext);
127
+ spinner.succeed('AI generated description');
128
+ logger.blank();
129
+ logger.info('AI-generated changeset description:');
130
+ logger.divider();
131
+ console.log(aiMessage);
132
+ logger.divider();
133
+ logger.blank();
134
+ const useAiMessage = await confirm({
135
+ message: 'Use this description?',
136
+ initialValue: true,
137
+ });
138
+ if (isCancel(useAiMessage)) {
139
+ cancel('Release cancelled');
140
+ await git.cleanup();
141
+ process.exit(0);
142
+ }
143
+ if (useAiMessage) {
144
+ releaseMessage = aiMessage;
145
+ }
146
+ else {
147
+ const customMessage = await text({
148
+ message: 'Enter your own description:',
149
+ validate: (value) => {
150
+ if (!value || value.length === 0)
151
+ return 'Message is required';
152
+ },
153
+ });
154
+ if (isCancel(customMessage)) {
155
+ cancel('Release cancelled');
156
+ await git.cleanup();
157
+ process.exit(0);
158
+ }
159
+ releaseMessage = customMessage;
160
+ }
161
+ }
162
+ catch (error) {
163
+ spinner.fail('AI generation failed');
164
+ logger.warn('Falling back to manual input');
165
+ const fallbackMessage = await text({
166
+ message: 'Describe the changes for this release:',
167
+ validate: (value) => {
168
+ if (!value || value.length === 0)
169
+ return 'Message is required';
170
+ },
171
+ });
172
+ if (isCancel(fallbackMessage)) {
173
+ cancel('Release cancelled');
174
+ await git.cleanup();
175
+ process.exit(0);
176
+ }
177
+ releaseMessage = fallbackMessage;
178
+ }
179
+ }
180
+ const fullOptions = {
181
+ type: releaseType,
182
+ message: releaseMessage,
183
+ skipConfirmations: options.skipConfirmations,
184
+ };
185
+ const branchName = `release/${fullOptions.type}-${Date.now()}`;
186
+ // Step 2: Create branch
187
+ logger.step(2, TOTAL_STEPS, 'Creating release branch...');
188
+ spinner = ora(`Creating branch ${branchName}...`).start();
189
+ await git.createBranch(branchName);
190
+ spinner.succeed(`Branch created: ${branchName}`);
191
+ // Step 3: Generate changeset
192
+ logger.step(3, TOTAL_STEPS, 'Generating changeset...');
193
+ spinner = ora('Generating changeset...').start();
194
+ const changesetId = await git.generateChangeset(fullOptions, { packageName });
195
+ spinner.succeed(`Changeset created: ${changesetId}.md`);
196
+ logger.blank();
197
+ logger.info('Changeset content:');
198
+ logger.divider();
199
+ console.log(`"${packageName}": ${fullOptions.type}`);
200
+ console.log();
201
+ console.log(fullOptions.message);
202
+ logger.divider();
203
+ logger.blank();
204
+ // Confirm before pushing
205
+ if (!fullOptions.skipConfirmations) {
206
+ const shouldContinue = await confirm({
207
+ message: 'Push changes and create PR?',
208
+ initialValue: true,
209
+ });
210
+ if (isCancel(shouldContinue)) {
211
+ cancel('Release cancelled');
212
+ await git.cleanup();
213
+ process.exit(0);
214
+ }
215
+ if (!shouldContinue) {
216
+ logger.warn('Release cancelled by user');
217
+ await git.cleanup();
218
+ return;
219
+ }
220
+ }
221
+ // Step 4: Commit changes
222
+ logger.step(4, TOTAL_STEPS, 'Committing changes...');
223
+ spinner = ora('Committing...').start();
224
+ await git.stageAndCommit(`chore: add ${fullOptions.type} changeset for release`);
225
+ spinner.succeed('Changes committed');
226
+ // Step 5: Push branch
227
+ logger.step(5, TOTAL_STEPS, 'Pushing branch to origin...');
228
+ spinner = ora('Pushing...').start();
229
+ await git.push(branchName);
230
+ spinner.succeed('Branch pushed');
231
+ // Step 6: Create PR
232
+ logger.step(6, TOTAL_STEPS, 'Creating pull request...');
233
+ spinner = ora('Creating PR...').start();
234
+ const pr = await github.createPullRequest(branchName, `chore: ${fullOptions.type} release - ${fullOptions.message.slice(0, 50)}`, `## Release Changeset
235
+
236
+ **Type:** ${fullOptions.type}
237
+
238
+ **Changes:**
239
+ ${fullOptions.message}
240
+
241
+ ---
242
+ *This PR was created automatically by autoship*`);
243
+ spinner.succeed(`PR created: #${pr.number}`);
244
+ logger.info(`PR URL: ${pr.html_url}`);
245
+ // Confirm before waiting for checks
246
+ if (!fullOptions.skipConfirmations) {
247
+ const shouldWait = await confirm({
248
+ message: 'Wait for CI checks to pass?',
249
+ initialValue: true,
250
+ });
251
+ if (isCancel(shouldWait)) {
252
+ cancel('Release cancelled');
253
+ await git.cleanup();
254
+ process.exit(0);
255
+ }
256
+ if (!shouldWait) {
257
+ logger.info('You can manually merge the PR when ready');
258
+ await git.cleanup();
259
+ return;
260
+ }
261
+ }
262
+ // Step 7: Wait for checks
263
+ logger.step(7, TOTAL_STEPS, 'Waiting for CI checks...');
264
+ logger.waiting('This may take several minutes...');
265
+ const { success: checksPass, checks } = await github.waitForChecks(pr.number);
266
+ if (!checksPass) {
267
+ logger.error('CI checks failed!');
268
+ logger.info(`Please check the PR: ${pr.html_url}`);
269
+ const failedChecks = checks.filter(c => c.conclusion === 'failure');
270
+ for (const check of failedChecks) {
271
+ logger.error(` Failed: ${check.name}`);
272
+ }
273
+ await git.cleanup();
274
+ process.exit(1);
275
+ }
276
+ logger.success('All CI checks passed!');
277
+ // Confirm before merging
278
+ if (!fullOptions.skipConfirmations) {
279
+ const shouldMerge = await confirm({
280
+ message: 'Merge the changeset PR?',
281
+ initialValue: true,
282
+ });
283
+ if (isCancel(shouldMerge)) {
284
+ cancel('Release cancelled');
285
+ await git.cleanup();
286
+ process.exit(0);
287
+ }
288
+ if (!shouldMerge) {
289
+ logger.info('You can manually merge the PR when ready');
290
+ await git.cleanup();
291
+ return;
292
+ }
293
+ }
294
+ // Step 8: Merge PR
295
+ logger.step(8, TOTAL_STEPS, 'Merging changeset PR...');
296
+ spinner = ora('Merging...').start();
297
+ await github.mergePullRequest(pr.number);
298
+ await github.deleteBranch(branchName);
299
+ spinner.succeed('Changeset PR merged!');
300
+ // Step 9: Wait for Version Packages PR
301
+ logger.step(9, TOTAL_STEPS, 'Waiting for Version Packages PR...');
302
+ logger.waiting('Changesets action will create a Version Packages PR...');
303
+ const versionPr = await github.waitForVersionPackagesPR();
304
+ logger.success(`Version Packages PR found: #${versionPr.number}`);
305
+ logger.info(`PR URL: ${versionPr.html_url}`);
306
+ // Confirm before continuing
307
+ if (!fullOptions.skipConfirmations) {
308
+ const shouldContinueVersion = await confirm({
309
+ message: 'Wait for checks and merge the Version Packages PR?',
310
+ initialValue: true,
311
+ });
312
+ if (isCancel(shouldContinueVersion)) {
313
+ cancel('Release cancelled');
314
+ await git.cleanup();
315
+ process.exit(0);
316
+ }
317
+ if (!shouldContinueVersion) {
318
+ logger.info('You can manually merge the Version Packages PR when ready');
319
+ await git.cleanup();
320
+ return;
321
+ }
322
+ }
323
+ // Wait for checks on Version Packages PR
324
+ logger.waiting('Waiting for Version Packages PR checks...');
325
+ const { success: versionChecksPass } = await github.waitForChecks(versionPr.number);
326
+ if (!versionChecksPass) {
327
+ logger.error('Version Packages PR checks failed!');
328
+ logger.info(`Please check the PR: ${versionPr.html_url}`);
329
+ await git.cleanup();
330
+ process.exit(1);
331
+ }
332
+ logger.success('Version Packages PR checks passed!');
333
+ // Final confirmation
334
+ if (!fullOptions.skipConfirmations) {
335
+ const shouldMergeVersion = await confirm({
336
+ message: 'Merge the Version Packages PR to publish the release?',
337
+ initialValue: true,
338
+ });
339
+ if (isCancel(shouldMergeVersion)) {
340
+ cancel('Release cancelled');
341
+ await git.cleanup();
342
+ process.exit(0);
343
+ }
344
+ if (!shouldMergeVersion) {
345
+ logger.info('You can manually merge the Version Packages PR when ready');
346
+ await git.cleanup();
347
+ return;
348
+ }
349
+ }
350
+ // Step 10: Merge Version Packages PR
351
+ logger.step(10, TOTAL_STEPS, 'Merging Version Packages PR...');
352
+ spinner = ora('Merging and publishing...').start();
353
+ await github.mergePullRequest(versionPr.number);
354
+ spinner.succeed('Version Packages PR merged!');
355
+ logger.blank();
356
+ logger.header('Release Complete!');
357
+ logger.success(`The ${fullOptions.type} release has been published.`);
358
+ logger.info('The release workflow will now:');
359
+ logger.detail('- Build binaries for all platforms');
360
+ logger.detail('- Publish the package to npm');
361
+ logger.detail('- Create a GitHub release');
362
+ await git.cleanup();
363
+ }
364
+ catch (error) {
365
+ spinner?.fail();
366
+ logger.error(`Single-package release failed: ${error instanceof Error ? error.message : String(error)}`);
367
+ try {
368
+ await git.cleanup();
369
+ }
370
+ catch {
371
+ // Ignore cleanup errors
372
+ }
373
+ process.exit(1);
374
+ }
375
+ }
376
+ /**
377
+ * Monorepo release flow
378
+ */
379
+ async function runMonorepoRelease(workspace, git, github, config, options = {}) {
380
+ let spinner = null;
381
+ try {
382
+ // Log package count
383
+ logger.info(`Found ${workspace.packages.length} packages in workspace`);
384
+ // Find latest tag (required for monorepo)
385
+ spinner = ora('Finding latest version tag...').start();
386
+ const latestTag = await git.getLatestVersionTag();
387
+ if (!latestTag) {
388
+ spinner.fail('No version tags found');
389
+ throw new Error('Monorepo releases require version tags. Please create an initial release first.');
390
+ }
391
+ spinner.succeed(`Latest version: ${latestTag}`);
392
+ // Analyze changes per package
393
+ spinner = ora('Analyzing changes per package...').start();
394
+ const packageChanges = await analyzePackageChanges(workspace, latestTag, git.getGit());
395
+ spinner.succeed(`Found changes in ${packageChanges.length} packages`);
396
+ if (packageChanges.length === 0) {
397
+ logger.info('No changes detected in any package');
398
+ await git.cleanup();
399
+ return;
400
+ }
401
+ // Display changed packages
402
+ for (const pc of packageChanges) {
403
+ logger.detail(`${pc.package.name}: ${pc.filesChanged.length} files (+${pc.insertions}/-${pc.deletions})`);
404
+ }
405
+ // Apply mode logic
406
+ const monorepoConfig = config.monorepo || { mode: 'selective' };
407
+ let packagesToRelease = packageChanges;
408
+ if (monorepoConfig.mode === 'lockstep') {
409
+ logger.info('Using lockstep versioning mode - all packages will be released');
410
+ // Include ALL packages, even ones with no changes
411
+ packagesToRelease = workspace.packages.map((pkg) => {
412
+ const existing = packageChanges.find((pc) => pc.package.name === pkg.name);
413
+ return existing || {
414
+ package: pkg,
415
+ commits: [],
416
+ filesChanged: [],
417
+ insertions: 0,
418
+ deletions: 0,
419
+ diff: '',
420
+ suggestedType: 'patch'
421
+ };
422
+ });
423
+ }
424
+ else {
425
+ logger.info('Using selective versioning mode - only changed packages will be released');
426
+ }
427
+ // AI analysis per package
428
+ spinner = ora('Analyzing version bumps with AI...').start();
429
+ for (const pc of packagesToRelease) {
430
+ if (pc.commits.length > 0) {
431
+ pc.suggestedType = await analyzePackageChange(pc);
432
+ }
433
+ }
434
+ spinner.succeed('AI analysis complete');
435
+ // Interactive selection
436
+ if (monorepoConfig.mode === 'lockstep') {
437
+ // Single version bump for all
438
+ const maxType = findMaxVersionType(packagesToRelease);
439
+ const selectedType = await select({
440
+ message: 'What type of release for all packages?',
441
+ options: [
442
+ { label: 'patch - Bug fixes, small changes', value: 'patch' },
443
+ { label: 'minor - New features, backwards compatible', value: 'minor' },
444
+ { label: 'major - Breaking changes', value: 'major' },
445
+ ],
446
+ initialValue: maxType,
447
+ });
448
+ if (isCancel(selectedType)) {
449
+ cancel('Release cancelled');
450
+ await git.cleanup();
451
+ process.exit(0);
452
+ }
453
+ packagesToRelease.forEach((pc) => pc.suggestedType = selectedType);
454
+ }
455
+ else {
456
+ // Per-package confirmation
457
+ logger.info('Review version bumps per package:');
458
+ const confirmedPackages = [];
459
+ for (const pc of packagesToRelease) {
460
+ logger.blank();
461
+ logger.info(`Package: ${pc.package.name}`);
462
+ logger.detail(`Suggested: ${pc.suggestedType}`);
463
+ logger.detail(`Changes: ${pc.filesChanged.length} files (+${pc.insertions}/-${pc.deletions})`);
464
+ const selectedType = await select({
465
+ message: `Version bump for ${pc.package.name}?`,
466
+ options: [
467
+ { label: 'patch - Bug fixes', value: 'patch' },
468
+ { label: 'minor - New features', value: 'minor' },
469
+ { label: 'major - Breaking changes', value: 'major' },
470
+ { label: 'skip - Don\'t release this package', value: 'skip' },
471
+ ],
472
+ initialValue: pc.suggestedType,
473
+ });
474
+ if (isCancel(selectedType)) {
475
+ cancel('Release cancelled');
476
+ await git.cleanup();
477
+ process.exit(0);
478
+ }
479
+ if (selectedType !== 'skip') {
480
+ pc.suggestedType = selectedType;
481
+ confirmedPackages.push(pc);
482
+ }
483
+ }
484
+ packagesToRelease = confirmedPackages;
485
+ }
486
+ if (packagesToRelease.length === 0) {
487
+ logger.info('No packages selected for release');
488
+ await git.cleanup();
489
+ return;
490
+ }
491
+ // Generate multi-package description
492
+ spinner = ora('Generating changeset description...').start();
493
+ let description;
494
+ if (options.message) {
495
+ description = options.message;
496
+ spinner.succeed('Using provided description');
497
+ }
498
+ else {
499
+ try {
500
+ description = await generateMultiPackageChangesetMessage(packagesToRelease);
501
+ spinner.succeed('AI generated description');
502
+ logger.blank();
503
+ logger.info('AI-generated changeset description:');
504
+ logger.divider();
505
+ console.log(description);
506
+ logger.divider();
507
+ logger.blank();
508
+ const useAiMessage = await confirm({
509
+ message: 'Use this description?',
510
+ initialValue: true,
511
+ });
512
+ if (isCancel(useAiMessage)) {
513
+ cancel('Release cancelled');
514
+ await git.cleanup();
515
+ process.exit(0);
516
+ }
517
+ if (!useAiMessage) {
518
+ const customMessage = await text({
519
+ message: 'Enter your own description:',
520
+ validate: (value) => {
521
+ if (!value || value.length === 0)
522
+ return 'Message is required';
523
+ },
524
+ });
525
+ if (isCancel(customMessage)) {
526
+ cancel('Release cancelled');
527
+ await git.cleanup();
528
+ process.exit(0);
529
+ }
530
+ description = customMessage;
531
+ }
532
+ }
533
+ catch (error) {
534
+ spinner.fail('AI generation failed');
535
+ logger.warn('Falling back to manual input');
536
+ const fallbackMessage = await text({
537
+ message: 'Describe the changes for this release:',
538
+ validate: (value) => {
539
+ if (!value || value.length === 0)
540
+ return 'Message is required';
541
+ },
542
+ });
543
+ if (isCancel(fallbackMessage)) {
544
+ cancel('Release cancelled');
545
+ await git.cleanup();
546
+ process.exit(0);
547
+ }
548
+ description = fallbackMessage;
549
+ }
550
+ }
551
+ // Build changeset data
552
+ const changesetData = {
553
+ packages: new Map(packagesToRelease.map((pc) => [pc.package.name, pc.suggestedType])),
554
+ message: description
555
+ };
556
+ const fullOptions = {
557
+ type: 'patch', // Not used for multi-package
558
+ message: description,
559
+ skipConfirmations: options.skipConfirmations,
560
+ };
561
+ // Create branch and changeset
562
+ const branchName = `release/${monorepoConfig.mode}-${Date.now()}`;
563
+ logger.step(2, TOTAL_STEPS, 'Creating release branch...');
564
+ spinner = ora(`Creating branch ${branchName}...`).start();
565
+ await git.createBranch(branchName);
566
+ spinner.succeed(`Branch created: ${branchName}`);
567
+ logger.step(3, TOTAL_STEPS, 'Generating changeset...');
568
+ spinner = ora('Generating changeset...').start();
569
+ await git.generateChangeset(fullOptions, changesetData);
570
+ spinner.succeed('Changeset created');
571
+ logger.blank();
572
+ logger.info('Changeset content:');
573
+ logger.divider();
574
+ for (const [pkgName, type] of changesetData.packages) {
575
+ console.log(`"${pkgName}": ${type}`);
576
+ }
577
+ console.log();
578
+ console.log(description);
579
+ logger.divider();
580
+ logger.blank();
581
+ // Confirm before pushing
582
+ if (!fullOptions.skipConfirmations) {
583
+ const shouldContinue = await confirm({
584
+ message: 'Push changes and create PR?',
585
+ initialValue: true,
586
+ });
587
+ if (isCancel(shouldContinue)) {
588
+ cancel('Release cancelled');
589
+ await git.cleanup();
590
+ process.exit(0);
591
+ }
592
+ if (!shouldContinue) {
593
+ logger.warn('Release cancelled by user');
594
+ await git.cleanup();
595
+ return;
596
+ }
597
+ }
598
+ // Continue with PR flow (same as single-package)
599
+ logger.step(4, TOTAL_STEPS, 'Committing changes...');
600
+ spinner = ora('Committing...').start();
601
+ await git.stageAndCommit(`chore: add changeset for ${monorepoConfig.mode} release`);
602
+ spinner.succeed('Changes committed');
603
+ logger.step(5, TOTAL_STEPS, 'Pushing branch to origin...');
604
+ spinner = ora('Pushing...').start();
605
+ await git.push(branchName);
606
+ spinner.succeed('Branch pushed');
607
+ logger.step(6, TOTAL_STEPS, 'Creating pull request...');
608
+ spinner = ora('Creating PR...').start();
609
+ const pr = await github.createPullRequest(branchName, `chore: ${monorepoConfig.mode} release - ${description.slice(0, 50)}`, `## Release Changeset
610
+
611
+ **Mode:** ${monorepoConfig.mode}
612
+ **Packages:** ${packagesToRelease.length}
613
+
614
+ **Changes:**
615
+ ${description}
616
+
617
+ ---
618
+ *This PR was created automatically by just-ship-it*`);
619
+ spinner.succeed(`PR created: #${pr.number}`);
620
+ logger.info(`PR URL: ${pr.html_url}`);
621
+ // Confirm before waiting for checks
622
+ if (!fullOptions.skipConfirmations) {
623
+ const shouldWait = await confirm({
624
+ message: 'Wait for CI checks to pass?',
625
+ initialValue: true,
626
+ });
627
+ if (isCancel(shouldWait)) {
628
+ cancel('Release cancelled');
629
+ await git.cleanup();
630
+ process.exit(0);
631
+ }
632
+ if (!shouldWait) {
633
+ logger.info('You can manually merge the PR when ready');
634
+ await git.cleanup();
635
+ return;
636
+ }
637
+ }
638
+ // Wait for checks
639
+ logger.step(7, TOTAL_STEPS, 'Waiting for CI checks...');
640
+ logger.waiting('This may take several minutes...');
641
+ const { success: checksPass, checks } = await github.waitForChecks(pr.number);
642
+ if (!checksPass) {
643
+ logger.error('CI checks failed!');
644
+ logger.info(`Please check the PR: ${pr.html_url}`);
645
+ const failedChecks = checks.filter((c) => c.conclusion === 'failure');
646
+ for (const check of failedChecks) {
647
+ logger.error(` Failed: ${check.name}`);
648
+ }
649
+ await git.cleanup();
650
+ process.exit(1);
651
+ }
652
+ logger.success('All CI checks passed!');
653
+ // Confirm before merging
654
+ if (!fullOptions.skipConfirmations) {
655
+ const shouldMerge = await confirm({
656
+ message: 'Merge the changeset PR?',
657
+ initialValue: true,
658
+ });
659
+ if (isCancel(shouldMerge)) {
660
+ cancel('Release cancelled');
661
+ await git.cleanup();
662
+ process.exit(0);
663
+ }
664
+ if (!shouldMerge) {
665
+ logger.info('You can manually merge the PR when ready');
666
+ await git.cleanup();
667
+ return;
668
+ }
669
+ }
670
+ // Merge PR
671
+ logger.step(8, TOTAL_STEPS, 'Merging changeset PR...');
672
+ spinner = ora('Merging...').start();
673
+ await github.mergePullRequest(pr.number);
674
+ await github.deleteBranch(branchName);
675
+ spinner.succeed('Changeset PR merged!');
676
+ // Wait for Version Packages PR
677
+ logger.step(9, TOTAL_STEPS, 'Waiting for Version Packages PR...');
678
+ logger.waiting('Changesets action will create a Version Packages PR...');
679
+ const versionPr = await github.waitForVersionPackagesPR();
680
+ logger.success(`Version Packages PR found: #${versionPr.number}`);
681
+ logger.info(`PR URL: ${versionPr.html_url}`);
682
+ // Confirm before continuing
683
+ if (!fullOptions.skipConfirmations) {
684
+ const shouldContinueVersion = await confirm({
685
+ message: 'Wait for checks and merge the Version Packages PR?',
686
+ initialValue: true,
687
+ });
688
+ if (isCancel(shouldContinueVersion)) {
689
+ cancel('Release cancelled');
690
+ await git.cleanup();
691
+ process.exit(0);
692
+ }
693
+ if (!shouldContinueVersion) {
694
+ logger.info('You can manually merge the Version Packages PR when ready');
695
+ await git.cleanup();
696
+ return;
697
+ }
698
+ }
699
+ // Wait for checks on Version Packages PR
700
+ logger.waiting('Waiting for Version Packages PR checks...');
701
+ const { success: versionChecksPass } = await github.waitForChecks(versionPr.number);
702
+ if (!versionChecksPass) {
703
+ logger.error('Version Packages PR checks failed!');
704
+ logger.info(`Please check the PR: ${versionPr.html_url}`);
705
+ await git.cleanup();
706
+ process.exit(1);
707
+ }
708
+ logger.success('Version Packages PR checks passed!');
709
+ // Final confirmation
710
+ if (!fullOptions.skipConfirmations) {
711
+ const shouldMergeVersion = await confirm({
712
+ message: 'Merge the Version Packages PR to publish the release?',
713
+ initialValue: true,
714
+ });
715
+ if (isCancel(shouldMergeVersion)) {
716
+ cancel('Release cancelled');
717
+ await git.cleanup();
718
+ process.exit(0);
719
+ }
720
+ if (!shouldMergeVersion) {
721
+ logger.info('You can manually merge the Version Packages PR when ready');
722
+ await git.cleanup();
723
+ return;
724
+ }
725
+ }
726
+ // Merge Version Packages PR
727
+ logger.step(10, TOTAL_STEPS, 'Merging Version Packages PR...');
728
+ spinner = ora('Merging and publishing...').start();
729
+ await github.mergePullRequest(versionPr.number);
730
+ spinner.succeed('Version Packages PR merged!');
731
+ logger.blank();
732
+ logger.header('Release Complete!');
733
+ logger.success(`The ${monorepoConfig.mode} release has been published.`);
734
+ logger.info('The release workflow will now:');
735
+ logger.detail('- Build binaries for all platforms');
736
+ logger.detail('- Publish packages to npm');
737
+ logger.detail('- Create GitHub releases');
738
+ await git.cleanup();
739
+ }
740
+ catch (error) {
741
+ spinner?.fail();
742
+ logger.error(`Monorepo release failed: ${error instanceof Error ? error.message : String(error)}`);
743
+ try {
744
+ await git.cleanup();
745
+ }
746
+ catch {
747
+ // Ignore cleanup errors
748
+ }
749
+ process.exit(1);
750
+ }
751
+ }
752
+ //# sourceMappingURL=release.js.map