Skip to content

Commit 04c3872

Browse files
author
serverpod_cloud
committed
feat: ece12e21d7fe6a4fb9cad530f8949576647beeb6
1 parent 3203875 commit 04c3872

11 files changed

Lines changed: 586 additions & 329 deletions

File tree

serverpod_cloud_cli/lib/commands/launch/launch.dart

Lines changed: 79 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@ import 'package:serverpod_cloud_cli/constants.dart';
1414
import 'package:serverpod_cloud_cli/shared/user_interaction/user_confirmations.dart';
1515
import 'package:serverpod_cloud_cli/util/common.dart';
1616
import 'package:serverpod_cloud_cli/util/printers/table_printer.dart';
17+
import 'package:serverpod_cloud_cli/util/project_files_writer.dart';
1718
import 'package:serverpod_cloud_cli/util/project_id_validator.dart';
18-
import 'package:serverpod_cloud_cli/util/pubspec_validator.dart';
19+
import 'package:serverpod_cloud_cli/util/pubspec_validator.dart'
20+
show TenantProjectPubspec;
21+
import 'package:serverpod_cloud_cli/util/scloud_config/scloud_config_io.dart';
22+
import 'package:serverpod_cloud_cli/util/scloud_config/scloud_config_model.dart';
1923

2024
abstract class Launch {
2125
static Future<void> launch(
@@ -57,6 +61,17 @@ abstract class Launch {
5761

5862
await selectPerformDeploy(logger, projectSetup);
5963

64+
final configFilePath = projectSetup.configFilePath;
65+
if (configFilePath == null) {
66+
throw StateError('ConfigFilePath must be set.');
67+
}
68+
69+
await suggestFlutterBuildPreDeployHook(
70+
logger,
71+
projectSetup,
72+
configFilePath,
73+
);
74+
6075
await confirmSetupAndContinue(logger, projectSetup);
6176

6277
await performLaunch(
@@ -341,6 +356,44 @@ The default API domain will be: <project-id>.api.serverpod.space
341356
projectSetup.performDeploy = performDeploy;
342357
}
343358

359+
static Future<void> suggestFlutterBuildPreDeployHook(
360+
final CommandLogger logger,
361+
final ProjectLaunch projectSetup,
362+
final String configFilePath,
363+
) async {
364+
final projectDir = projectSetup.projectDir;
365+
if (projectDir == null) {
366+
return;
367+
}
368+
369+
final pubspecValidator = TenantProjectPubspec.fromProjectDir(
370+
Directory(projectDir),
371+
);
372+
373+
if (!pubspecValidator.hasFlutterBuildScript()) return;
374+
375+
ScloudConfig? existingConfig;
376+
try {
377+
existingConfig = ScloudConfigIO.readFromFile(configFilePath);
378+
} catch (_) {
379+
logger.debug('Failed to read config file at $configFilePath');
380+
return;
381+
}
382+
383+
final flutterBuildHook = 'serverpod run flutter_build';
384+
385+
final existingPreDeploy = existingConfig?.scripts.preDeploy ?? [];
386+
if (existingPreDeploy.contains(flutterBuildHook)) return;
387+
388+
final shouldAdd = await logger.confirm(
389+
"Detected 'flutter_build' script. Add it as a pre-deploy hook?",
390+
defaultValue: true,
391+
);
392+
393+
if (!shouldAdd) return;
394+
projectSetup.suggestedPreDeployScripts.add(flutterBuildHook);
395+
}
396+
344397
static Future<void> confirmSetupAndContinue(
345398
final CommandLogger logger,
346399
final ProjectLaunch projectSetup,
@@ -399,9 +452,22 @@ The default API domain will be: <project-id>.api.serverpod.space
399452
);
400453
}
401454

455+
await logger.progress(
456+
'Writing cloud project configuration files.',
457+
() async {
458+
ProjectFilesWriter.writeFiles(
459+
projectId: projectId,
460+
preDeployScripts: projectSetup.suggestedPreDeployScripts,
461+
configFilePath: configFilePath,
462+
projectDirectory: projectDir,
463+
);
464+
return true;
465+
},
466+
);
467+
402468
if (!performDeploy) {
403469
logger.terminalCommand(
404-
'scloud deploy -d $projectDir $projectId',
470+
'scloud deploy',
405471
message: 'Run this command to deploy the project to the cloud:',
406472
);
407473
return;
@@ -436,19 +502,18 @@ The default API domain will be: <project-id>.api.serverpod.space
436502

437503
const tenantHost = 'serverpod.space';
438504

439-
logger.success(
440-
'When the server has started, you can access it at:\n',
441-
trailingRocket: true,
505+
logger.info(
506+
'When the server has started, you can access it at:\n'
507+
' Web: https://$projectId.$tenantHost/\n'
508+
' API: https://$projectId.api.$tenantHost/\n'
509+
' Insights: https://$projectId.insights.$tenantHost/',
442510
newParagraph: true,
443-
followUp:
444-
' Web: https://$projectId.$tenantHost/\n'
445-
' API: https://$projectId.api.$tenantHost/\n'
446-
' Insights: https://$projectId.insights.$tenantHost/',
447511
);
448512

449513
logger.terminalCommand(
450514
'scloud deployment show',
451515
message: 'View the deployment status:',
516+
newParagraph: true,
452517
);
453518
}
454519

@@ -494,14 +559,17 @@ class ProjectLaunch {
494559
bool? enableDb;
495560
bool? preexistingProject;
496561
bool? performDeploy;
562+
List<String> suggestedPreDeployScripts;
497563

498564
ProjectLaunch({
499565
final String? projectDir,
500566
this.projectId,
501567
this.enableDb,
502568
this.preexistingProject,
503569
this.performDeploy,
504-
}) : _projectDir = projectDir {
570+
final List<String>? suggestedPreDeployScripts,
571+
}) : _projectDir = projectDir,
572+
suggestedPreDeployScripts = suggestedPreDeployScripts ?? [] {
505573
if (projectDir != null) {
506574
configFilePath = _constructConfigFilePath(projectDir);
507575
}
@@ -529,6 +597,7 @@ class ProjectLaunch {
529597
] else
530598
['Existing project id', projectId],
531599
['Perform deploy', performDeploy == true ? 'yes' : 'no'],
600+
['Add build hook', suggestedPreDeployScripts.isNotEmpty ? 'yes' : 'no'],
532601
],
533602
columnSeparator: ' ',
534603
).toString();

serverpod_cloud_cli/lib/commands/project/project.dart

Lines changed: 7 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
1-
import 'dart:io';
2-
31
import 'package:collection/collection.dart';
42
import 'package:ground_control_client/ground_control_client.dart';
5-
import 'package:path/path.dart' as p;
63
import 'package:serverpod_cloud_cli/command_logger/command_logger.dart';
74
import 'package:serverpod_cloud_cli/shared/exceptions/exit_exceptions.dart';
8-
import 'package:serverpod_cloud_cli/commands/deploy/prepare_workspace.dart'
9-
show WorkspaceProject;
105
import 'package:serverpod_cloud_cli/shared/user_interaction/user_confirmations.dart';
116
import 'package:serverpod_cloud_cli/util/printers/table_printer.dart';
12-
import 'package:serverpod_cloud_cli/util/pubspec_validator.dart';
13-
import 'package:serverpod_cloud_cli/util/scloud_config/scloud_config_model.dart';
14-
import 'package:serverpod_cloud_cli/util/scloud_config/scloud_config_io.dart';
15-
import 'package:serverpod_cloud_cli/util/scloudignore.dart';
7+
import 'package:serverpod_cloud_cli/util/project_files_writer.dart';
168

179
abstract class ProjectCommands {
1810
static const defaultPlanName = 'early-access';
@@ -97,40 +89,6 @@ abstract class ProjectCommands {
9789
});
9890
}
9991

100-
if (isServerpodServerDirectory(Directory(projectDir))) {
101-
// write scloud project files unless the config file already exists
102-
103-
final scloudYamlFile = File(configFilePath);
104-
if (scloudYamlFile.existsSync()) {
105-
logger.success('Serverpod Cloud project created.', newParagraph: true);
106-
107-
return;
108-
}
109-
110-
final config = ScloudConfig(
111-
projectId: projectId,
112-
scripts: ScloudScripts.empty(),
113-
);
114-
115-
await logger.progress(
116-
'Writing cloud project configuration files.',
117-
() async {
118-
_writeProjectFiles(logger, config, projectDir, configFilePath);
119-
return true;
120-
},
121-
);
122-
} else {
123-
logger.terminalCommand(
124-
message:
125-
'Since no Serverpod server directory was identified, '
126-
'an scloud.yaml configuration file has not been created. '
127-
'Use the link command to create it in the server '
128-
'directory of this project:',
129-
newParagraph: true,
130-
'scloud project link --project $projectId',
131-
);
132-
}
133-
13492
logger.success('Serverpod Cloud project created.', newParagraph: true);
13593
}
13694

@@ -212,15 +170,15 @@ abstract class ProjectCommands {
212170
required final String projectDirectory,
213171
required final String configFilePath,
214172
}) async {
215-
final config = ScloudConfig(
216-
projectId: projectId,
217-
scripts: ScloudScripts.empty(),
218-
);
219-
220173
await logger.progress(
221174
'Writing cloud project configuration files.',
222175
() async {
223-
_writeProjectFiles(logger, config, projectDirectory, configFilePath);
176+
ProjectFilesWriter.writeFiles(
177+
projectId: projectId,
178+
preDeployScripts: [],
179+
configFilePath: configFilePath,
180+
projectDirectory: projectDirectory,
181+
);
224182
return true;
225183
},
226184
);
@@ -290,96 +248,4 @@ abstract class ProjectCommands {
290248
);
291249
}
292250
}
293-
294-
static void _writeProjectFiles(
295-
final CommandLogger logger,
296-
final ScloudConfig config,
297-
final String projectDirectory,
298-
final String configFilePath,
299-
) {
300-
final workspaceRootDir = _findWorkspaceRootDir(
301-
logger,
302-
Directory(projectDirectory),
303-
);
304-
305-
try {
306-
ScloudConfigIO.writeToFile(config, configFilePath);
307-
final relativePath = p.relative(configFilePath);
308-
logger.debug(
309-
"Wrote the '$relativePath' configuration file for '${config.projectId}'.",
310-
);
311-
} on Exception catch (e, s) {
312-
throw FailureException.nested(
313-
e,
314-
s,
315-
'Failed to write to the $configFilePath file',
316-
);
317-
}
318-
319-
try {
320-
ScloudIgnore.writeTemplateIfNotExists(
321-
rootFolder: workspaceRootDir?.path ?? projectDirectory,
322-
);
323-
logger.debug("Wrote the '${ScloudIgnore.fileName}' file.");
324-
} on Exception catch (e, s) {
325-
throw FailureException.nested(
326-
e,
327-
s,
328-
'Failed to write to ${ScloudIgnore.fileName} file',
329-
);
330-
}
331-
332-
if (workspaceRootDir != null) {
333-
try {
334-
final updated = _updateGitIgnore(workspaceRootDir);
335-
if (updated) {
336-
logger.debug(
337-
"Added '${ScloudIgnore.scloudDirName}/' to '.gitignore' in the workspace directory.",
338-
);
339-
}
340-
} on Exception catch (e, s) {
341-
throw FailureException.nested(
342-
e,
343-
s,
344-
'Failed to write to the .gitignore file',
345-
);
346-
}
347-
}
348-
}
349-
350-
static Directory? _findWorkspaceRootDir(
351-
final CommandLogger logger,
352-
final Directory projectDir,
353-
) {
354-
final projectPubspec = TenantProjectPubspec.fromProjectDir(projectDir);
355-
356-
if (projectPubspec.isWorkspaceResolved()) {
357-
final (workspaceRootDir, workspacePubspec) =
358-
WorkspaceProject.findWorkspaceRoot(projectDir);
359-
return workspaceRootDir;
360-
}
361-
362-
return null;
363-
}
364-
365-
static bool _updateGitIgnore(final Directory workspaceRootDir) {
366-
const scloudIgnoreTemplate =
367-
'''
368-
# scloud deployment generated files should not be committed to git
369-
**/${ScloudIgnore.scloudDirName}/
370-
''';
371-
final gitIgnoreFile = File(p.join(workspaceRootDir.path, '.gitignore'));
372-
final String content;
373-
if (gitIgnoreFile.existsSync()) {
374-
final read = gitIgnoreFile.readAsStringSync();
375-
if (read.contains('${ScloudIgnore.scloudDirName}/')) {
376-
return false;
377-
}
378-
content = read.endsWith('\n') ? '$read\n' : '$read\n\n';
379-
} else {
380-
content = '';
381-
}
382-
gitIgnoreFile.writeAsStringSync('$content$scloudIgnoreTemplate');
383-
return true;
384-
}
385251
}

0 commit comments

Comments
 (0)