feat: add --confirm flag to release and patch commands (#3628)

This commit is contained in:
Eric Seidel
2026-02-26 16:27:54 -08:00
committed by GitHub
parent 7238e96c61
commit aa36f92dcc
4 changed files with 185 additions and 0 deletions
@@ -108,6 +108,13 @@ To target the latest release (e.g. the release that was most recently updated) u
help: CommonArguments.noConfirmArg.description,
negatable: false,
)
// Added for https://github.com/shorebirdtech/shorebird/issues/3223.
// Can be removed fall 2026 or later.
..addFlag(
'confirm',
negatable: false,
hide: true,
)
..addOption(
CommonArguments.exportOptionsPlistArg.name,
help: CommonArguments.exportOptionsPlistArg.description,
@@ -188,6 +195,9 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl
/// The target script, if provided.
late String? target = results.findOption('target', argParser: argParser);
/// Whether to prompt for confirmation before creating the patch.
bool get confirm => results['confirm'] == true;
/// Whether to allow changes in assets (--allow-asset-diffs).
bool get allowAssetDiffs => results['allow-asset-diffs'] == true;
@@ -608,6 +618,13 @@ ${styleBold.wrap(lightGreen.wrap('🚀 Ready to publish a new patch!'))}
${summary.join('\n')}
''');
if (confirm && shorebirdEnv.canAcceptUserInput) {
if (!logger.confirm('Would you like to continue?', defaultValue: true)) {
logger.info('Aborting.');
throw ProcessExit(ExitCode.success.code);
}
}
}
/// Downloads the given [releaseArtifact].
@@ -129,6 +129,13 @@ Defaults to "latest" which builds using the latest stable Flutter version.''',
help: CommonArguments.noConfirmArg.description,
negatable: false,
)
// Added for https://github.com/shorebirdtech/shorebird/issues/3223.
// Can be removed fall 2026 or later.
..addFlag(
'confirm',
negatable: false,
hide: true,
)
..addOption(
'release-version',
help: '''
@@ -225,6 +232,9 @@ of the iOS app that is using this module. (aar and ios-framework only)''',
}
}
/// Whether to prompt for confirmation before creating the release.
bool get confirm => results['confirm'] == true;
/// The shorebird app ID for the current project.
String get appId => shorebirdEnv.getShorebirdYaml()!.getAppId(flavor: flavor);
@@ -524,6 +534,13 @@ ${styleBold.wrap(lightGreen.wrap('🚀 Ready to create a new release!'))}
${summary.join('\n')}
''');
if (confirm && shorebirdEnv.canAcceptUserInput) {
if (!logger.confirm('Would you like to continue?', defaultValue: true)) {
logger.info('Aborting.');
throw ProcessExit(ExitCode.success.code);
}
}
}
/// Fetches the release with version [version] from the server or creates a
@@ -815,6 +815,94 @@ void main() {
});
});
});
group('when --confirm is passed', () {
setUp(() {
when(() => argResults['confirm']).thenReturn(true);
when(() => shorebirdEnv.canAcceptUserInput).thenReturn(true);
});
group('when user confirms', () {
setUp(() {
when(
() => logger.confirm(
any(),
defaultValue: any(named: 'defaultValue'),
),
).thenReturn(true);
});
test('continues', () async {
await expectLater(
runWithOverrides(
() => command.logPatchSummary(
app: appMetadata,
releaseVersion: releaseVersion,
patcher: patcher,
patchArtifactBundles: patchArtifactBundles,
),
),
completes,
);
verify(
() => logger.confirm(
'Would you like to continue?',
defaultValue: true,
),
).called(1);
});
});
group('when user declines', () {
setUp(() {
when(
() => logger.confirm(
any(),
defaultValue: any(named: 'defaultValue'),
),
).thenReturn(false);
});
test('exits with success and prints Aborting.', () async {
await expectLater(
runWithOverrides(
() => command.logPatchSummary(
app: appMetadata,
releaseVersion: releaseVersion,
patcher: patcher,
patchArtifactBundles: patchArtifactBundles,
),
),
exitsWithCode(ExitCode.success),
);
verify(() => logger.info('Aborting.')).called(1);
});
});
});
group('when --confirm is not passed', () {
setUp(() {
when(() => argResults['confirm']).thenReturn(false);
});
test('does not prompt for confirmation', () async {
await expectLater(
runWithOverrides(
() => command.logPatchSummary(
app: appMetadata,
releaseVersion: releaseVersion,
patcher: patcher,
patchArtifactBundles: patchArtifactBundles,
),
),
completes,
);
verifyNever(
() =>
logger.confirm(any(), defaultValue: any(named: 'defaultValue')),
);
});
});
});
group('when flutter install fails', () {
@@ -414,6 +414,69 @@ void main() {
});
});
group('when --confirm is passed', () {
setUp(() {
when(() => argResults['confirm']).thenReturn(true);
when(() => shorebirdEnv.canAcceptUserInput).thenReturn(true);
});
group('when user confirms', () {
setUp(() {
when(
() => logger.confirm(
any(),
defaultValue: any(named: 'defaultValue'),
),
).thenReturn(true);
});
test('continues', () async {
await runWithOverrides(command.run);
verify(
() => logger.confirm(
'Would you like to continue?',
defaultValue: true,
),
).called(1);
});
});
group('when user declines', () {
setUp(() {
when(
() => logger.confirm(
any(),
defaultValue: any(named: 'defaultValue'),
),
).thenReturn(false);
});
test('exits with success and prints Aborting.', () async {
await expectLater(
runWithOverrides(command.run),
exitsWithCode(ExitCode.success),
);
verify(() => logger.info('Aborting.')).called(1);
});
});
});
group('when --confirm is not passed', () {
setUp(() {
when(() => argResults['confirm']).thenReturn(false);
});
test('does not prompt for confirmation', () async {
await runWithOverrides(command.run);
verifyNever(
() => logger.confirm(
any(),
defaultValue: any(named: 'defaultValue'),
),
);
});
});
group('when flavor and target are provided', () {
const flavor = 'test-flavor';
const target = 'test-target';