Skip to content

Commit 7253102

Browse files
author
serverpod_cloud
committed
feat(scloud): 4dfd4c2aae5da70fbbd3f12836a8e67efd10d8e5
1 parent 8f97548 commit 7253102

2 files changed

Lines changed: 645 additions & 61 deletions

File tree

serverpod_cloud_cli/lib/commands/launch/launch.dart

Lines changed: 123 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,17 @@ abstract class Launch {
4343
);
4444

4545
await selectProjectId(
46+
cloudApiClient,
4647
logger,
4748
projectSetup,
4849
);
4950

50-
await selectEnableDb(
51-
logger,
52-
projectSetup,
53-
);
51+
if (projectSetup.preexistingProject != true) {
52+
await selectEnableDb(
53+
logger,
54+
projectSetup,
55+
);
56+
}
5457

5558
await selectPerformDeploy(
5659
logger,
@@ -144,6 +147,7 @@ abstract class Launch {
144147
}
145148

146149
static Future<void> selectProjectId(
150+
final Client cloudApiClient,
147151
final CommandLogger logger,
148152
final ProjectLaunch projectSetup,
149153
) async {
@@ -162,6 +166,13 @@ abstract class Launch {
162166
logger.error(invalidProjectIdMessage);
163167
}
164168

169+
final selectedId = await _selectExistingProject(cloudApiClient, logger);
170+
if (selectedId != null) {
171+
projectSetup.projectId = selectedId;
172+
projectSetup.preexistingProject = true;
173+
return;
174+
}
175+
165176
final defaultProjectId = _getDefaultProjectId(projectSetup.projectDir);
166177

167178
logger.raw(
@@ -176,7 +187,7 @@ The default API domain will be: <project-id>.api.serverpod.space
176187
final defaultValue =
177188
defaultProjectId != null ? '$defaultPrefix$defaultProjectId' : null;
178189
var projectId = await logger.input(
179-
'Choose project id',
190+
'Enter a new project id',
180191
defaultValue: defaultValue,
181192
);
182193

@@ -198,6 +209,85 @@ The default API domain will be: <project-id>.api.serverpod.space
198209
} while (true);
199210
}
200211

212+
static Future<String?> _selectExistingProject(
213+
final Client cloudApiClient,
214+
final CommandLogger logger,
215+
) async {
216+
final projects = await _fetchExistingProjects(cloudApiClient);
217+
if (projects.isEmpty) {
218+
return null;
219+
}
220+
if (projects.length == 1) {
221+
return _confirmSingleExistingProject(logger, projects.single);
222+
}
223+
return _selectFromSeveralExistingProjects(logger, projects);
224+
}
225+
226+
static Future<List<Project>> _fetchExistingProjects(
227+
final Client cloudApiClient,
228+
) async {
229+
try {
230+
final projects = await cloudApiClient.projects.listProjects();
231+
final activeProjects = projects.where((final p) => p.archivedAt == null);
232+
return activeProjects.toList();
233+
} on Exception catch (e, s) {
234+
throw FailureException.nested(e, s, 'Request to list projects failed');
235+
}
236+
}
237+
238+
static Future<String?> _confirmSingleExistingProject(
239+
final CommandLogger logger,
240+
final Project project,
241+
) async {
242+
logger.info(
243+
'Found an existing Cloud project: ${project.cloudProjectId}',
244+
newParagraph: true,
245+
);
246+
247+
final confirm = await logger.confirm(
248+
'Continue with ${project.cloudProjectId}?',
249+
);
250+
logger.info(' ');
251+
return confirm ? project.cloudProjectId : null;
252+
}
253+
254+
static Future<String?> _selectFromSeveralExistingProjects(
255+
final CommandLogger logger,
256+
final List<Project> projects,
257+
) async {
258+
final existingIds = projects.map((final p) => p.cloudProjectId).toList();
259+
logger.info(
260+
'Found existing Cloud projects.\n'
261+
'Do you want to deploy to one of them instead of creating a new one?',
262+
newParagraph: true,
263+
);
264+
for (int i = 0; i < existingIds.length; i++) {
265+
logger.info('${i + 1}. ${existingIds[i]}');
266+
}
267+
logger.info('(blank - create a new project)');
268+
269+
do {
270+
final projectNum = await logger.input(
271+
'Enter a project number from the list, or blank',
272+
);
273+
274+
if (projectNum.isEmpty || projectNum == 'q') {
275+
logger.info(' ');
276+
return null;
277+
}
278+
279+
final projectIx = int.tryParse(projectNum);
280+
if (projectIx != null &&
281+
projectIx >= 1 &&
282+
projectIx <= existingIds.length) {
283+
logger.info(' ');
284+
return existingIds[projectIx - 1];
285+
}
286+
287+
logger.error('Value must be a number from the list, or empty to skip.');
288+
} while (true);
289+
}
290+
201291
static String? _getDefaultProjectId(final String? projectDir) {
202292
if (projectDir != null) {
203293
final pubspec = TenantProjectPubspec.fromProjectDir(
@@ -277,20 +367,22 @@ The default API domain will be: <project-id>.api.serverpod.space
277367

278368
final projectId = projectSetup.projectId!;
279369
final projectDir = projectSetup.projectDir!;
280-
final enableDb = projectSetup.enableDb!;
281370
final performDeploy = projectSetup.performDeploy!;
282371

283-
await ProjectCommands.createProject(
284-
cloudApiClient,
285-
logger: logger,
286-
projectId: projectId,
287-
enableDb: enableDb,
288-
projectDir: projectDir,
289-
configFilePath: p.join(
290-
projectDir,
291-
ProjectConfigFileConstants.defaultFileName,
292-
),
293-
);
372+
if (projectSetup.preexistingProject != true) {
373+
final enableDb = projectSetup.enableDb!;
374+
await ProjectCommands.createProject(
375+
cloudApiClient,
376+
logger: logger,
377+
projectId: projectId,
378+
enableDb: enableDb,
379+
projectDir: projectDir,
380+
configFilePath: p.join(
381+
projectDir,
382+
ProjectConfigFileConstants.defaultFileName,
383+
),
384+
);
385+
}
294386

295387
if (!performDeploy) {
296388
logger.terminalCommand(
@@ -343,23 +435,31 @@ class ProjectLaunch {
343435
String? projectDir;
344436
String? projectId;
345437
bool? enableDb;
438+
bool? preexistingProject;
346439
bool? performDeploy;
347440

348441
ProjectLaunch({
349442
this.projectDir,
350443
this.projectId,
351444
this.enableDb,
445+
this.preexistingProject,
352446
this.performDeploy,
353447
});
354448

355449
@override
356450
String toString() {
357-
final text = TablePrinter.columns(rows: [
358-
['Project directory', projectDir],
359-
['Project Id', projectId],
360-
['Enable DB', enableDb == true ? 'yes' : 'no'],
361-
['Perform deploy', performDeploy == true ? 'yes' : 'no'],
362-
]).toString();
451+
final text = TablePrinter.columns(
452+
rows: [
453+
['Project directory', projectDir],
454+
if (preexistingProject != true) ...[
455+
['Project id', projectId],
456+
['Enable DB', enableDb == true ? 'yes' : 'no'],
457+
] else
458+
['Existing project id', projectId],
459+
['Perform deploy', performDeploy == true ? 'yes' : 'no'],
460+
],
461+
columnSeparator: ' ',
462+
).toString();
363463
return text.substring(0, text.length - 1); // trims last newline
364464
}
365465
}

0 commit comments

Comments
 (0)