Skip to content

Commit 1d56425

Browse files
author
serverpod_cloud
committed
feat(scloud): 610a08fc85eb0d6baa53ff27a577202b5462cac7
1 parent eff5bbd commit 1d56425

11 files changed

Lines changed: 268 additions & 86 deletions

File tree

serverpod_cloud_cli/lib/command_logger/command_logger.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ class CommandLogger {
459459
final bool? defaultValue,
460460
}) async {
461461
if (configuration?.skipConfirmation == true) {
462+
info('$message: y');
462463
return true;
463464
}
464465

serverpod_cloud_cli/lib/command_runner/cloud_cli_command_runner.dart

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,15 @@ enum GlobalOption<V> implements OptionDefinition<V> {
419419
helpText: 'The timeout for the connection to the Serverpod Cloud API.',
420420
),
421421
),
422+
skipConfirmation(
423+
FlagOption(
424+
argName: 'skip-confirmation',
425+
helpText: 'Automatically accept confirmation prompts.'
426+
' For use in non-interactive environments.',
427+
negatable: false,
428+
defaultsTo: false,
429+
),
430+
),
422431

423432
// Developer options and flags
424433
apiServer(
@@ -439,15 +448,6 @@ enum GlobalOption<V> implements OptionDefinition<V> {
439448
defaultsTo: HostConstants.serverpodCloudConsole,
440449
),
441450
),
442-
skipConfirmation(
443-
FlagOption(
444-
argName: 'skip-confirmation',
445-
helpText:
446-
'Should be used in CI environment to bypass confirmation prompts.',
447-
hide: true,
448-
defaultsTo: false,
449-
),
450-
),
451451
authToken(
452452
StringOption(
453453
argName: 'auth-token',

serverpod_cloud_cli/lib/command_runner/commands/launch_command.dart

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
import 'package:config/config.dart';
22
import 'package:serverpod_cloud_cli/command_runner/cloud_cli_command.dart';
33
import 'package:serverpod_cloud_cli/command_runner/commands/categories.dart';
4-
import 'package:serverpod_cloud_cli/command_runner/helpers/command_options.dart';
54
import 'package:serverpod_cloud_cli/commands/launch/launch.dart';
65

76
enum LaunchOption<V> implements OptionDefinition<V> {
8-
projectId(ProjectIdOption.nonMandatory()),
7+
projectId(StringOption(
8+
argName: 'project',
9+
helpText: 'The ID of an existing project to use.',
10+
group: _projectGroup,
11+
)),
12+
newProjectId(StringOption(
13+
argName: 'new-project',
14+
helpText: 'The ID of a new project to create.',
15+
group: _projectGroup,
16+
)),
917
enableDb(FlagOption(
1018
argName: 'enable-db',
1119
helpText: 'Flag to enable the database for the project.',
@@ -19,6 +27,9 @@ enum LaunchOption<V> implements OptionDefinition<V> {
1927

2028
@override
2129
final ConfigOptionBase<V> option;
30+
31+
static const _projectGroup =
32+
MutuallyExclusive('Project', mode: MutuallyExclusiveMode.noDefaults);
2233
}
2334

2435
class CloudLaunchCommand extends CloudCliCommand<LaunchOption> {
@@ -41,7 +52,9 @@ class CloudLaunchCommand extends CloudCliCommand<LaunchOption> {
4152
final foundProjectDir =
4253
specifiedProjectDir == null ? runner.selectProjectDirectory() : null;
4354

44-
final projectId = commandConfig.optionalValue(LaunchOption.projectId);
55+
final existingProjectId =
56+
commandConfig.optionalValue(LaunchOption.projectId);
57+
final newProjectId = commandConfig.optionalValue(LaunchOption.newProjectId);
4558
final enableDb = commandConfig.optionalValue(LaunchOption.enableDb);
4659
final deploy = commandConfig.optionalValue(LaunchOption.deploy);
4760

@@ -51,7 +64,8 @@ class CloudLaunchCommand extends CloudCliCommand<LaunchOption> {
5164
logger: logger,
5265
specifiedProjectDir: specifiedProjectDir?.path,
5366
foundProjectDir: foundProjectDir,
54-
projectId: projectId,
67+
newProjectId: newProjectId,
68+
existingProjectId: existingProjectId,
5569
enableDb: enableDb,
5670
performDeploy: deploy,
5771
);

serverpod_cloud_cli/lib/command_runner/completion/completion_script_carapace.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ persistentFlags:
1717
--project-config-file=: "The path to the Serverpod Cloud project configuration file."
1818
--project-config-content=: "Override the scloud project configuration with a YAML string."
1919
--connection-timeout=: "The timeout for the connection to the Serverpod Cloud API."
20+
--skip-confirmation: "Automatically accept confirmation prompts. For use in non-interactive environments."
2021
exclusiveFlags:
2122
- [analytics, no-analytics]
2223
completion:
@@ -246,7 +247,8 @@ commands:
246247
247248
- name: launch
248249
flags:
249-
-p, --project=: "The ID of the project."
250+
--project=: "The ID of an existing project to use."
251+
--new-project=: "The ID of a new project to create."
250252
--enable-db: "Flag to enable the database for the project."
251253
--no-enable-db: "Flag to enable the database for the project."
252254
--deploy: "Flag to immediately deploy the project."

serverpod_cloud_cli/lib/command_runner/completion/completion_script_completely.dart

Lines changed: 41 additions & 41 deletions
Large diffs are not rendered by default.

serverpod_cloud_cli/lib/commands/launch/launch.dart

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:serverpod_cloud_cli/commands/project/project.dart';
1111
import 'package:serverpod_cloud_cli/commands/status/status.dart';
1212
import 'package:serverpod_cloud_cli/commands/status/status_feature.dart';
1313
import 'package:serverpod_cloud_cli/constants.dart';
14+
import 'package:serverpod_cloud_cli/shared/user_interaction/user_confirmations.dart';
1415
import 'package:serverpod_cloud_cli/util/common.dart';
1516
import 'package:serverpod_cloud_cli/util/printers/table_printer.dart';
1617
import 'package:serverpod_cloud_cli/util/project_id_validator.dart';
@@ -23,7 +24,8 @@ abstract class Launch {
2324
required final CommandLogger logger,
2425
required final String? specifiedProjectDir,
2526
required final String? foundProjectDir,
26-
required final String? projectId,
27+
required final String? newProjectId,
28+
required final String? existingProjectId,
2729
required final bool? enableDb,
2830
required final bool? performDeploy,
2931
}) async {
@@ -32,12 +34,19 @@ abstract class Launch {
3234
logger: logger,
3335
);
3436

37+
if (newProjectId != null && existingProjectId != null) {
38+
throw ArgumentError(
39+
'Cannot specify both newProjectId and existingProjectId.',
40+
);
41+
}
42+
3543
logger.init('Launching new Serverpod Cloud project.\n');
3644

3745
final projectSetup = ProjectLaunch(
3846
projectDir: specifiedProjectDir,
39-
projectId: projectId,
47+
projectId: newProjectId ?? existingProjectId,
4048
enableDb: enableDb,
49+
preexistingProject: existingProjectId != null,
4150
performDeploy: performDeploy,
4251
);
4352

@@ -163,8 +172,8 @@ abstract class Launch {
163172

164173
final specifiedProjectId = projectSetup.projectId;
165174
if (specifiedProjectId != null) {
166-
if (isValidProjectIdFormat(specifiedProjectId)) {
167-
projectSetup.projectId = specifiedProjectId;
175+
if (projectSetup.preexistingProject == true ||
176+
isValidProjectIdFormat(specifiedProjectId)) {
168177
return;
169178
}
170179

@@ -178,6 +187,8 @@ abstract class Launch {
178187
return;
179188
}
180189

190+
await UserConfirmations.confirmNewProjectCostAcceptance(logger);
191+
181192
final defaultProjectId = _getDefaultProjectId(projectSetup.projectDir);
182193

183194
logger.raw(
@@ -246,7 +257,6 @@ The default API domain will be: <project-id>.api.serverpod.space
246257
) async {
247258
logger.info(
248259
'Found an existing Cloud project: ${project.cloudProjectId}',
249-
newParagraph: true,
250260
);
251261

252262
final confirm = await logger.confirm(
@@ -264,7 +274,6 @@ The default API domain will be: <project-id>.api.serverpod.space
264274
logger.info(
265275
'Found existing Cloud projects.\n'
266276
'Do you want to deploy to one of them instead of creating a new one?',
267-
newParagraph: true,
268277
);
269278
for (int i = 0; i < existingIds.length; i++) {
270279
logger.info('${i + 1}. ${existingIds[i]}');
@@ -386,6 +395,7 @@ The default API domain will be: <project-id>.api.serverpod.space
386395
projectDir,
387396
ProjectConfigFileConstants.defaultFileName,
388397
),
398+
skipConfirmation: true,
389399
);
390400
}
391401

@@ -457,7 +467,7 @@ class ProjectLaunch {
457467
rows: [
458468
['Project directory', projectDir],
459469
if (preexistingProject != true) ...[
460-
['Project id', projectId],
470+
['New project id', projectId],
461471
['Enable DB', enableDb == true ? 'yes' : 'no'],
462472
] else
463473
['Existing project id', projectId],

serverpod_cloud_cli/lib/commands/project/project.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:serverpod_cloud_cli/command_logger/command_logger.dart';
77
import 'package:serverpod_cloud_cli/shared/exceptions/exit_exceptions.dart';
88
import 'package:serverpod_cloud_cli/commands/deploy/prepare_workspace.dart'
99
show WorkspaceProject;
10+
import 'package:serverpod_cloud_cli/shared/user_interaction/user_confirmations.dart';
1011
import 'package:serverpod_cloud_cli/util/printers/table_printer.dart';
1112
import 'package:serverpod_cloud_cli/util/pubspec_validator.dart';
1213
import 'package:serverpod_cloud_cli/util/scloud_config/scloud_config_file.dart';
@@ -41,7 +42,12 @@ abstract class ProjectCommands {
4142
required final bool enableDb,
4243
required final String projectDir,
4344
required final String configFilePath,
45+
final bool skipConfirmation = false,
4446
}) async {
47+
if (!skipConfirmation) {
48+
await UserConfirmations.confirmNewProjectCostAcceptance(logger);
49+
}
50+
4551
// Check that the user is on a plan and automatically procure one if not.
4652
// This behavior will be changed in the future.
4753
final planNames = await cloudApiClient.plans.listProcuredPlanNames();
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import 'package:serverpod_cloud_cli/command_logger/command_logger.dart';
2+
3+
import '../exceptions/exit_exceptions.dart';
4+
5+
/// User interactions that may be reused across commands.
6+
abstract class UserConfirmations {
7+
/// Asks the user for confirmation to continue with a new project setup
8+
/// that may incur additional costs.
9+
///
10+
/// Throws [UserAbortException] if the user does not confirm.
11+
static Future<void> confirmNewProjectCostAcceptance(
12+
final CommandLogger logger,
13+
) async {
14+
final confirm = await logger.confirm(
15+
'Depending on your subscription, a new project may incur additional costs. Continue?',
16+
defaultValue: true,
17+
);
18+
19+
if (!confirm) {
20+
logger.info('Setup cancelled.');
21+
throw UserAbortException();
22+
}
23+
}
24+
}

serverpod_cloud_cli/test/command_logger/command_logger_confirm_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ void main() {
291291
},
292292
);
293293

294-
expect(stdout.output, isEmpty);
294+
expect(stdout.output, 'Are you sure?: y\n');
295295
expect(result, isTrue);
296296
});
297297
}

0 commit comments

Comments
 (0)