Skip to content

Commit 24b53c2

Browse files
author
serverpod_cloud
committed
feat(cli): 96e0969916661f30eaffb284d7b1d27a7427e4a9
1 parent 319e060 commit 24b53c2

3 files changed

Lines changed: 104 additions & 0 deletions

File tree

serverpod_cloud_cli/lib/command_runner/commands/deploy_command.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:serverpod_cloud_cli/command_runner/cloud_cli_command.dart';
44
import 'package:serverpod_cloud_cli/command_runner/helpers/command_options.dart';
55
import 'package:serverpod_cloud_cli/commands/deploy/deploy.dart';
66
import 'package:serverpod_cloud_cli/constants.dart';
7+
import 'package:serverpod_cloud_cli/shared/exceptions/exit_exceptions.dart';
78

89
import 'categories.dart';
910

@@ -34,6 +35,14 @@ enum DeployCommandOption<V> implements OptionDefinition<V> {
3435
defaultsTo: false,
3536
negatable: false,
3637
),
38+
),
39+
output(
40+
StringOption(
41+
argName: 'output',
42+
argAbbrev: 'o',
43+
helpText:
44+
'Save the deployment zip file to the specified path. Must end with .zip',
45+
),
3746
);
3847

3948
const DeployCommandOption(this.option);
@@ -70,6 +79,14 @@ Examples
7079
7180
\$ scloud deploy --dry-run --show-files
7281
82+
Save the deployment zip file locally
83+
84+
\$ scloud deploy --output deployment.zip --dry-run
85+
86+
Save the deployment zip and still upload it (unless --dry-run is set)
87+
88+
\$ scloud deploy --output deployment.zip
89+
7390
''';
7491

7592
CloudDeployCommand({required super.logger})
@@ -83,6 +100,11 @@ Examples
83100
final concurrency = commandConfig.value(DeployCommandOption.concurrency);
84101
final dryRun = commandConfig.value(DeployCommandOption.dryRun);
85102
final showFiles = commandConfig.value(DeployCommandOption.showFiles);
103+
final outputPath = commandConfig.optionalValue(DeployCommandOption.output);
104+
105+
if (outputPath != null && !outputPath.endsWith('.zip')) {
106+
throw FailureException(errors: ['The --output path must end with .zip']);
107+
}
86108

87109
final projectDirectory = runner.verifiedProjectDirectory();
88110
logger.debug('Using project directory `${projectDirectory.path}`');
@@ -103,6 +125,7 @@ Examples
103125
concurrency: concurrency,
104126
dryRun: dryRun,
105127
showFiles: showFiles,
128+
outputPath: outputPath,
106129
);
107130
}
108131
}

serverpod_cloud_cli/lib/commands/deploy/deploy.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ abstract class Deploy {
2525
required final int concurrency,
2626
required final bool dryRun,
2727
required final bool showFiles,
28+
final String? outputPath,
2829
}) async {
2930
logger.init('Deploying Serverpod Cloud project "$projectId".');
3031

@@ -130,6 +131,22 @@ abstract class Deploy {
130131

131132
if (!isZipped) throw ErrorExitException('Failed to zip project.');
132133

134+
if (outputPath != null) {
135+
await logger.progress('Writing zip file to $outputPath...', () async {
136+
try {
137+
final file = File(outputPath);
138+
await file.writeAsBytes(projectZip);
139+
return true;
140+
} on Exception catch (e, stackTrace) {
141+
throw FailureException.nested(
142+
e,
143+
stackTrace,
144+
'Failed to write zip file to $outputPath',
145+
);
146+
}
147+
});
148+
}
149+
133150
if (dryRun) {
134151
await logger.progress('Dry run, skipping upload.', () async {
135152
return true;

serverpod_cloud_cli/test_integration/commands/deploy_command_test.dart

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,40 @@ void main() {
128128
},
129129
);
130130

131+
group('and invalid output option value when running deploy command', () {
132+
late Future cliCommandFuture;
133+
setUp(() async {
134+
await ProjectFactory.serverpodServerDir().create();
135+
final testProjectDir = p.join(
136+
d.sandbox,
137+
ProjectFactory.defaultDirectoryName,
138+
);
139+
140+
cliCommandFuture = cli.run([
141+
'deploy',
142+
'--output',
143+
'deployment.txt',
144+
'--project',
145+
'123',
146+
'--project-dir',
147+
testProjectDir,
148+
]);
149+
});
150+
151+
test('then ErrorExitException is thrown.', () async {
152+
await expectLater(cliCommandFuture, throwsA(isA<ErrorExitException>()));
153+
});
154+
155+
test('then error message is logged', () async {
156+
await cliCommandFuture.catchError((final _) {});
157+
expect(logger.errorCalls, isNotEmpty);
158+
expect(
159+
logger.errorCalls.first.message,
160+
contains('The --output path must end with .zip'),
161+
);
162+
});
163+
});
164+
131165
group(
132166
'and current directory is not Serverpod server directory when running deploy command',
133167
() {
@@ -515,6 +549,36 @@ dependencies:
515549
});
516550
});
517551

552+
group('when deploying through CLI with --output and --dry-run', () {
553+
late Future cliCommandFuture;
554+
late String outputZipPath;
555+
setUp(() async {
556+
outputZipPath = p.join(d.sandbox, 'deployment.zip');
557+
cliCommandFuture = cli.run([
558+
'deploy',
559+
'--dry-run',
560+
'--output',
561+
outputZipPath,
562+
'--project',
563+
BucketUploadDescription.projectId,
564+
'--project-dir',
565+
testProjectDir,
566+
]);
567+
});
568+
569+
test('then command completes successfully.', () async {
570+
await expectLater(cliCommandFuture, completes);
571+
});
572+
573+
test('then zip file is created at output path.', () async {
574+
await cliCommandFuture;
575+
576+
final outputFile = File(outputZipPath);
577+
expect(outputFile.existsSync(), isTrue);
578+
expect(outputFile.lengthSync(), greaterThan(0));
579+
});
580+
});
581+
518582
group('when deploying through CLI with --show-files and --dry-run', () {
519583
late Future cliCommandFuture;
520584
setUp(() async {

0 commit comments

Comments
 (0)