From a4525ad29231d33e4b99234955280248daf9a90e Mon Sep 17 00:00:00 2001
From: Peter Trost
Date: Thu, 9 Oct 2025 20:27:52 +0200
Subject: [PATCH] feat(shorebird_cli): handle missing shorebird.yaml file
gracefully when promoting a patch (#3349)
Co-authored-by: Bryan Oltman
---
.../src/commands/patches/promote_command.dart | 10 ++++++
.../patches/promote_command_test.dart | 33 +++++++++++++++++++
2 files changed, 43 insertions(+)
diff --git a/packages/shorebird_cli/lib/src/commands/patches/promote_command.dart b/packages/shorebird_cli/lib/src/commands/patches/promote_command.dart
index 97b77846..e32f8e4b 100644
--- a/packages/shorebird_cli/lib/src/commands/patches/promote_command.dart
+++ b/packages/shorebird_cli/lib/src/commands/patches/promote_command.dart
@@ -7,6 +7,7 @@ import 'package:shorebird_cli/src/extensions/arg_results.dart';
import 'package:shorebird_cli/src/logging/logging.dart';
import 'package:shorebird_cli/src/shorebird_command.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
+import 'package:shorebird_cli/src/shorebird_validator.dart';
/// {@template promote_command}
/// Promotes a patch to the production channel.
@@ -39,6 +40,15 @@ class PromoteCommand extends ShorebirdCommand {
@override
Future run() async {
+ try {
+ await shorebirdValidator.validatePreconditions(
+ checkUserIsAuthenticated: true,
+ checkShorebirdInitialized: true,
+ );
+ } on PreconditionFailedException catch (error) {
+ return error.exitCode.code;
+ }
+
final releaseVersion = results['release-version'] as String;
final patchNumber = int.parse(results['patch-number'] as String);
final flavor = results.findOption('flavor', argParser: argParser);
diff --git a/packages/shorebird_cli/test/src/commands/patches/promote_command_test.dart b/packages/shorebird_cli/test/src/commands/patches/promote_command_test.dart
index 219c9077..effa6be4 100644
--- a/packages/shorebird_cli/test/src/commands/patches/promote_command_test.dart
+++ b/packages/shorebird_cli/test/src/commands/patches/promote_command_test.dart
@@ -8,6 +8,7 @@ import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/deployment_track.dart';
import 'package:shorebird_cli/src/logging/logging.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
+import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
import 'package:test/test.dart';
@@ -49,6 +50,7 @@ void main() {
late ArgResults argResults;
late CodePushClientWrapper codePushClientWrapper;
late ShorebirdEnv shorebirdEnv;
+ late ShorebirdValidator shorebirdValidator;
late ShorebirdLogger logger;
late PromoteCommand command;
@@ -60,6 +62,7 @@ void main() {
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper),
loggerRef.overrideWith(() => logger),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
+ shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
);
}
@@ -73,6 +76,7 @@ void main() {
codePushClientWrapper = MockCodePushClientWrapper();
logger = MockShorebirdLogger();
shorebirdEnv = MockShorebirdEnv();
+ shorebirdValidator = MockShorebirdValidator();
when(() => argResults.wasParsed(any())).thenReturn(false);
when(() => argResults.rest).thenReturn([]);
@@ -107,6 +111,13 @@ void main() {
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
+ when(
+ () => shorebirdValidator.validatePreconditions(
+ checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
+ checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'),
+ ),
+ ).thenAnswer((_) async {});
+
command = PromoteCommand()..testArgResults = argResults;
});
@@ -114,6 +125,28 @@ void main() {
expect(command.description, isNotEmpty);
});
+ group('when validation fails', () {
+ final exception = ShorebirdNotInitializedException();
+ setUp(() {
+ when(
+ () => shorebirdValidator.validatePreconditions(
+ checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
+ checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'),
+ ),
+ ).thenThrow(exception);
+ });
+ test('exits with exit code from validation error', () async {
+ final result = await runWithOverrides(command.run);
+ expect(result, equals(exception.exitCode.code));
+ verify(
+ () => shorebirdValidator.validatePreconditions(
+ checkUserIsAuthenticated: true,
+ checkShorebirdInitialized: true,
+ ),
+ ).called(1);
+ });
+ });
+
group('when an invalid patch number is provided', () {
setUp(() {
when(() => argResults['patch-number']).thenReturn('5');