Skip to content

Commit 7e84d57

Browse files
authored
feat(cli): Add celest status command (#320)
- Adds command to get the status of the Celest project if it's deployed to Celest Cloud. - Fixes the ability to delete project environments via `celest project-environments delete`.
1 parent 29cae22 commit 7e84d57

File tree

10 files changed

+295
-34
lines changed

10 files changed

+295
-34
lines changed

apps/cli/bin/celest.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:celest_cli/src/commands/deploy_command.dart';
44
import 'package:celest_cli/src/commands/organizations/organizations_command.dart';
55
import 'package:celest_cli/src/commands/project_environments/project_environments_command.dart';
66
import 'package:celest_cli/src/commands/projects/projects_command.dart';
7+
import 'package:celest_cli/src/commands/status_command.dart';
78

89
void main(List<String> args) async {
910
final cli = Cli(
@@ -19,7 +20,8 @@ void main(List<String> args) async {
1920
..addCommand(UninstallCommand())
2021
..addCommand(PrecacheCommand())
2122
..addCommand(DeployCommand())
22-
..addCommand(AuthCommand());
23+
..addCommand(AuthCommand())
24+
..addCommand(StatusCommand());
2325

2426
// Cloud API commands
2527
cli
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import 'package:celest_cli/src/commands/authenticate.dart';
2+
import 'package:celest_cli/src/commands/celest_command.dart';
3+
import 'package:celest_cli/src/context.dart';
4+
import 'package:celest_cli/src/init/project_init.dart';
5+
import 'package:mason_logger/mason_logger.dart';
6+
7+
final class StatusCommand extends CelestCommand with Configure, Authenticate {
8+
StatusCommand();
9+
10+
@override
11+
String get description => 'Gets the status of the Celest Cloud project.';
12+
13+
@override
14+
String get name => 'status';
15+
16+
@override
17+
String get category => 'Project';
18+
19+
@override
20+
Progress? currentProgress;
21+
22+
@override
23+
Future<int> run() async {
24+
await super.run();
25+
26+
await assertAuthenticated();
27+
await configure();
28+
29+
final projectName = celestProject.projectName;
30+
final projectEnvironment = await cloud.projects.environments
31+
.get('projects/$projectName/environments/production');
32+
if (projectEnvironment == null) {
33+
cliLogger.warn(
34+
'This project has not been deployed yet. '
35+
'Run `celest deploy` to deploy it.',
36+
);
37+
return 1;
38+
}
39+
40+
cliLogger
41+
..success('🚀 Project is live on Celest Cloud!')
42+
..info('Project: $projectName')
43+
..info('Environment: production')
44+
..info('URL: ${projectEnvironment.uri}');
45+
46+
return 0;
47+
}
48+
}

packages/celest_cloud/lib/src/proto/celest/cloud/v1alpha1/project_environments.pb.dart

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/celest_cloud/lib/src/proto/celest/cloud/v1alpha1/project_environments.pbjson.dart

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

proto/celest/cloud/v1alpha1/project_environments.proto

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ message ProjectEnvironment {
163163

164164
// Output only. The current state of the environment.
165165
LifecycleState state = 12 [(google.api.field_behavior) = OUTPUT_ONLY];
166+
167+
// Output only. The hosted URI of the environment.
168+
//
169+
// Will be empty if the environment is not yet deployed.
170+
string uri = 13 [(google.api.field_behavior) = OUTPUT_ONLY];
166171
}
167172

168173
// Request message for the `CreateProjectEnvironment` method.

services/celest_cloud_hub/.dockerignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
Dockerfile
33
build/
44
example/
5+
examples/
6+
fixtures/
57
.dart_tool/
68
.git/
79
.github/

services/celest_cloud_hub/lib/src/database/schema/operations.drift

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,6 @@ END;
166166
-------------- /Cedar Triggers ---------------
167167
----------------------------------------------
168168

169-
getOperation:
170-
SELECT * FROM operations
171-
WHERE id = :id
172-
LIMIT 1;
173-
174169
createOperation:
175170
INSERT INTO operations (
176171
id,
@@ -195,6 +190,25 @@ createOperation:
195190
)
196191
RETURNING *;
197192

193+
getOperation:
194+
SELECT * FROM operations
195+
WHERE id = :id
196+
LIMIT 1;
197+
198+
findOperationsByOwner:
199+
SELECT * FROM operations
200+
WHERE owner_type = :owner_type
201+
AND owner_id = :owner_id
202+
ORDER BY $orderBy
203+
LIMIT :limit;
204+
205+
findOperationsByResource:
206+
SELECT * FROM operations
207+
WHERE resource_type = :resource_type
208+
AND resource_id = :resource_id
209+
ORDER BY $orderBy
210+
LIMIT :limit;
211+
198212
listOperations(
199213
:owner_type AS TEXT OR NULL,
200214
:owner_id AS TEXT OR NULL,

services/celest_cloud_hub/lib/src/database/schema/operations.drift.dart

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,14 +1091,6 @@ i0.Trigger get operationsTriggerDelete => i0.Trigger(
10911091

10921092
class OperationsDrift extends i2.ModularAccessor {
10931093
OperationsDrift(i0.GeneratedDatabase db) : super(db);
1094-
i0.Selectable<i1.Operation> getOperation({required String id}) {
1095-
return customSelect(
1096-
'SELECT * FROM operations WHERE id = ?1 LIMIT 1',
1097-
variables: [i0.Variable<String>(id)],
1098-
readsFrom: {operations},
1099-
).asyncMap(operations.mapFromRow);
1100-
}
1101-
11021094
i3.Future<List<i1.Operation>> createOperation({
11031095
required String id,
11041096
String? metadata,
@@ -1127,6 +1119,62 @@ class OperationsDrift extends i2.ModularAccessor {
11271119
).then((rows) => Future.wait(rows.map(operations.mapFromRow)));
11281120
}
11291121

1122+
i0.Selectable<i1.Operation> getOperation({required String id}) {
1123+
return customSelect(
1124+
'SELECT * FROM operations WHERE id = ?1 LIMIT 1',
1125+
variables: [i0.Variable<String>(id)],
1126+
readsFrom: {operations},
1127+
).asyncMap(operations.mapFromRow);
1128+
}
1129+
1130+
i0.Selectable<i1.Operation> findOperationsByOwner({
1131+
String? ownerType,
1132+
String? ownerId,
1133+
FindOperationsByOwner$orderBy? orderBy,
1134+
required int limit,
1135+
}) {
1136+
var $arrayStartIndex = 4;
1137+
final generatedorderBy = $write(
1138+
orderBy?.call(this.operations) ?? const i0.OrderBy.nothing(),
1139+
startIndex: $arrayStartIndex,
1140+
);
1141+
$arrayStartIndex += generatedorderBy.amountOfVariables;
1142+
return customSelect(
1143+
'SELECT * FROM operations WHERE owner_type = ?1 AND owner_id = ?2 ${generatedorderBy.sql} LIMIT ?3',
1144+
variables: [
1145+
i0.Variable<String>(ownerType),
1146+
i0.Variable<String>(ownerId),
1147+
i0.Variable<int>(limit),
1148+
...generatedorderBy.introducedVariables,
1149+
],
1150+
readsFrom: {operations, ...generatedorderBy.watchedTables},
1151+
).asyncMap(operations.mapFromRow);
1152+
}
1153+
1154+
i0.Selectable<i1.Operation> findOperationsByResource({
1155+
String? resourceType,
1156+
String? resourceId,
1157+
FindOperationsByResource$orderBy? orderBy,
1158+
required int limit,
1159+
}) {
1160+
var $arrayStartIndex = 4;
1161+
final generatedorderBy = $write(
1162+
orderBy?.call(this.operations) ?? const i0.OrderBy.nothing(),
1163+
startIndex: $arrayStartIndex,
1164+
);
1165+
$arrayStartIndex += generatedorderBy.amountOfVariables;
1166+
return customSelect(
1167+
'SELECT * FROM operations WHERE resource_type = ?1 AND resource_id = ?2 ${generatedorderBy.sql} LIMIT ?3',
1168+
variables: [
1169+
i0.Variable<String>(resourceType),
1170+
i0.Variable<String>(resourceId),
1171+
i0.Variable<int>(limit),
1172+
...generatedorderBy.introducedVariables,
1173+
],
1174+
readsFrom: {operations, ...generatedorderBy.watchedTables},
1175+
).asyncMap(operations.mapFromRow);
1176+
}
1177+
11301178
i0.Selectable<ListOperationsResult> listOperations({
11311179
String? ownerType,
11321180
String? ownerId,
@@ -1200,6 +1248,11 @@ class OperationsDrift extends i2.ModularAccessor {
12001248
i4.CedarDrift get cedarDrift => this.accessor(i4.CedarDrift.new);
12011249
}
12021250

1251+
typedef FindOperationsByOwner$orderBy =
1252+
i0.OrderBy Function(i1.Operations operations);
1253+
typedef FindOperationsByResource$orderBy =
1254+
i0.OrderBy Function(i1.Operations operations);
1255+
12031256
class ListOperationsResult {
12041257
final int rowNum;
12051258
final i1.Operation operations;

services/celest_cloud_hub/lib/src/model/interop.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ extension ProjectToProto on dto.Project {
108108
}
109109

110110
extension ProjectEnvironmentToProto on dto.ProjectEnvironment {
111-
pb.ProjectEnvironment toProto() {
111+
pb.ProjectEnvironment toProto({dto.ProjectEnvironmentState? state}) {
112112
return pb.ProjectEnvironment(
113113
name: 'projects/$parentId/environments/$id',
114114
parent: 'projects/$parentId',
@@ -117,7 +117,7 @@ extension ProjectEnvironmentToProto on dto.ProjectEnvironment {
117117
displayName: displayName,
118118
etag: etag,
119119
reconciling: reconciling,
120-
state: pb.LifecycleState.values.firstWhere((s) => s.name == state),
120+
state: pb.LifecycleState.values.firstWhere((s) => s.name == this.state),
121121
createTime: createTime.toProto(),
122122
updateTime: updateTime.toProto(),
123123
deleteTime: deleteTime?.toProto(),
@@ -126,6 +126,10 @@ extension ProjectEnvironmentToProto on dto.ProjectEnvironment {
126126
(jsonDecode(annotations) as Map<String, Object?>).cast(),
127127
_ => null,
128128
},
129+
uri: switch (state?.domainName) {
130+
final domainName? => 'https://$domainName',
131+
_ => null,
132+
},
129133
);
130134
}
131135
}

0 commit comments

Comments
 (0)