Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(globe_cli): Add --logs for globe deploy #28

Merged
merged 4 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/cli/commands/deploy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,17 @@ When successfully built, the deployment will be promoted to production and will

You can learn more about [preview and production deployments](/deployments) in the deployments documentation.

### Build logs

If you want to see realtime build logs for your deployment on the CLI, use the `--logs` flag:

```bash
globe deploy --logs
```

### Flags

The command has flags (in addition to [global flags](/cli#global-flags)) that can be used to modify the behavior of the command, which can be accessed by running `globe deploy --<flag>`:

- `--prod` - Creates a deployment that will be promoted to production once built.
- `--logs` - Streams build logs.
181 changes: 95 additions & 86 deletions packages/globe_cli/lib/src/commands/deploy_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:mason_logger/mason_logger.dart';
import '../command.dart';
import '../utils/api.dart';
import '../utils/archiver.dart';
import '../utils/logs.dart';
import '../utils/prompts.dart';

/// `globe deploy`
Expand All @@ -16,11 +17,16 @@ import '../utils/prompts.dart';
class DeployCommand extends BaseGlobeCommand {
/// {@macro deploy_command}
DeployCommand() {
argParser.addFlag(
'prod',
help: 'Creates a new deployment, '
'and if successful promotes it to the latest production deployment.',
);
argParser
..addFlag(
'prod',
help: 'Creates a new deployment, '
'and if successful promotes it to the latest production deployment.',
)
..addFlag(
'logs',
help: 'Shows build logs for the deployment.',
);
}

@override
Expand Down Expand Up @@ -72,87 +78,90 @@ class DeployCommand extends BaseGlobeCommand {
'🔍 View deployment: ${metadata.endpoint}/${validated.organization.slug}/${validated.project.slug}/deployments/${deployment.id}',
);

// TODO: handle deployment logs - they currently disconnect immediately
// var status = logger.progress(deployment.state.message);
// final completer = Completer<void>();
// Stream<BuildLogEvent>? logs;

// // Check the deployment status every x seconds.
// Timer.periodic(const Duration(milliseconds: 2000), (timer) async {
// final update = await api.getDeployment(
// orgId: validated.organization.id,
// projectId: validated.project.id,
// deploymentId: deployment.id,
// );

// if (update.state == DeploymentState.working ||
// update.state == DeploymentState.deploying) {
// if (logs != null) return;

// status.complete();
// status = logger.progress('Preparing build environment');

// logs = await streamBuildLogs(
// api: api,
// orgId: validated.organization.id,
// projectId: validated.project.id,
// deploymentId: deployment.id,
// );

// unawaited(
// logs!
// .firstWhere((element) => element is! UnknownBuildLogEvent)
// .then((_) => status.complete()),
// );

// unawaited(
// logs!.firstWhere((element) {
// if (element case LogsBuildLogEvent(done: final done)) return done;
// return false;
// }).then((_) {
// status = logger.progress('Deploying...');
// }),
// );

// unawaited(printLogs(logger, logs!));
// }

// if (update.state == DeploymentState.success) {
// status.complete();
// logger.info(
// '${lightGreen.wrap('✓')} Preview: https://${update.url}',
// );
// }

// if (update.state == DeploymentState.error) {
// var message = 'Deployment failed';
// if (update.message.isNotEmpty) {
// message = '$message: ${update.message}';
// }
// status.fail(message);
// }

// if (update.state == DeploymentState.cancelled) {
// status.complete();
// logger.info('Deployment cancelled');
// }

// if (update.state == DeploymentState.invalid) {
// status.complete();
// status = logger.progress(
// 'Invalid Deployment State Received. Waiting for valid state',
// );
// }

// if (update.state == DeploymentState.success ||
// update.state == DeploymentState.cancelled ||
// update.state == DeploymentState.error) {
// timer.cancel();
// completer.complete();
// }
// });

// await completer.future;
if (!(argResults!['logs'] as bool)) {
return ExitCode.success.code;
}

var status = logger.progress(deployment.state.message);
final completer = Completer<void>();
Stream<BuildLogEvent>? logs;

// Check the deployment status every x seconds.
Timer.periodic(const Duration(milliseconds: 2000), (timer) async {
final update = await api.getDeployment(
orgId: validated.organization.id,
projectId: validated.project.id,
deploymentId: deployment.id,
);

if (update.state == DeploymentState.working ||
update.state == DeploymentState.deploying) {
if (logs != null) return;

status.complete();
status = logger.progress('Preparing build environment');

logs = await streamBuildLogs(
api: api,
orgId: validated.organization.id,
projectId: validated.project.id,
deploymentId: deployment.id,
);

unawaited(
logs!
.firstWhere((element) => element is! UnknownBuildLogEvent)
.then((_) => status.complete()),
);

unawaited(
logs!.firstWhere((element) {
if (element case LogsBuildLogEvent(done: final done)) return done;
return false;
}).then((_) {
status = logger.progress('Deploying...');
}),
);

unawaited(printLogs(logger, logs!));
}

if (update.state == DeploymentState.success) {
status.complete();
logger.info(
'${lightGreen.wrap('✓')} Preview: https://${update.url}',
);
}

if (update.state == DeploymentState.error) {
var message = 'Deployment failed';
if (update.message.isNotEmpty) {
message = '$message: ${update.message}';
}
status.fail(message);
}

if (update.state == DeploymentState.cancelled) {
status.complete();
logger.info('Deployment cancelled');
}

if (update.state == DeploymentState.invalid) {
status.complete();
status = logger.progress(
'Invalid Deployment State Received. Waiting for valid state',
);
}

if (update.state == DeploymentState.success ||
update.state == DeploymentState.cancelled ||
update.state == DeploymentState.error) {
timer.cancel();
completer.complete();
}
});

await completer.future;

return ExitCode.success.code;
} on ApiException catch (e) {
Expand Down