Skip to content

Commit e2667bc

Browse files
authored
feat(cli): Add support for auxiliary commands to celest start (#255)
When `--` is passed to `celest start`, run the following command after starting the local environment. For example, you can do `celest start -- flutter test -d macos` to run tests which depend on Celest running.
1 parent 8bc2880 commit e2667bc

File tree

3 files changed

+84
-1
lines changed

3 files changed

+84
-1
lines changed

apps/cli/lib/src/commands/start_command.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:celest_cli/src/commands/project_init.dart';
33
import 'package:celest_cli/src/commands/project_migrate.dart';
44
import 'package:celest_cli/src/context.dart';
55
import 'package:celest_cli/src/frontend/celest_frontend.dart';
6+
import 'package:celest_cli/src/frontend/child_process.dart';
67
import 'package:mason_logger/mason_logger.dart';
78

89
final class StartCommand extends CelestCommand
@@ -30,10 +31,16 @@ final class StartCommand extends CelestCommand
3031

3132
currentProgress ??= cliLogger.progress('Starting local environment');
3233

34+
ChildProcess? childProcess;
35+
if (argResults!.rest case final command when command.isNotEmpty) {
36+
childProcess = ChildProcess(command);
37+
}
38+
3339
// Start the Celest Frontend Loop
3440
return CelestFrontend().run(
3541
migrateProject: needsMigration,
3642
currentProgress: currentProgress,
43+
childProcess: childProcess,
3744
);
3845
}
3946
}

apps/cli/lib/src/frontend/celest_frontend.dart

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import 'package:celest_cli/src/compiler/api/local_api_runner.dart';
1717
import 'package:celest_cli/src/context.dart';
1818
import 'package:celest_cli/src/env/config_value_solver.dart';
1919
import 'package:celest_cli/src/exceptions.dart';
20+
import 'package:celest_cli/src/frontend/child_process.dart';
2021
import 'package:celest_cli/src/project/celest_project.dart';
2122
import 'package:celest_cli/src/project/project_resolver.dart';
2223
import 'package:celest_cli/src/utils/json.dart';
@@ -242,6 +243,7 @@ final class CelestFrontend {
242243
Future<int> run({
243244
required bool migrateProject,
244245
required Progress? currentProgress,
246+
ChildProcess? childProcess,
245247
}) async {
246248
try {
247249
while (!stopped) {
@@ -352,9 +354,26 @@ final class CelestFrontend {
352354
}
353355

354356
currentProgress = null;
357+
358+
if (childProcess case final childProcess?
359+
when !childProcess.isStarted) {
360+
logger.info('Running command: ${childProcess.command.join(' ')}');
361+
await childProcess.start();
362+
unawaited(
363+
_stopSignal.future.then(childProcess.stop),
364+
);
365+
}
355366
}
356367

357-
await _nextChangeSet();
368+
// Wait for the next changeset or for the child process to exit, if
369+
// there is one.
370+
final exitCode = await Future.any([
371+
_nextChangeSet().then((_) => null),
372+
Future.value(childProcess?.exitCode),
373+
]);
374+
if (exitCode != null) {
375+
return exitCode;
376+
}
358377
}
359378
return 0;
360379
} on CancellationException {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import 'dart:io';
2+
3+
import 'package:celest_cli/src/context.dart';
4+
import 'package:celest_cli/src/utils/cli.dart';
5+
6+
/// A process which is a child of a command like `celest start`.
7+
///
8+
/// The parent command is responsible for managing the lifecycle of the child
9+
/// process via this class.
10+
final class ChildProcess {
11+
ChildProcess(this.command);
12+
13+
/// The command to run as a child process.
14+
final List<String> command;
15+
Process? _process;
16+
17+
/// Whether [start] has been called.
18+
bool get isStarted => _process != null;
19+
20+
/// The process's exit code.
21+
Future<int> get exitCode {
22+
if (_process case final process?) {
23+
return process.exitCode;
24+
}
25+
throw StateError('Must call start first');
26+
}
27+
28+
/// Starts the process and streams stdout/stderr to the console.
29+
Future<void> start({
30+
Map<String, String>? environment,
31+
}) async {
32+
if (_process != null) {
33+
return;
34+
}
35+
final process = _process = await processManager.start(
36+
command,
37+
environment: environment,
38+
);
39+
40+
// Capture stdout/stderr
41+
final commandName = command.first;
42+
process.captureStdout(prefix: '[$commandName] ');
43+
process.captureStderr(prefix: '[$commandName] ');
44+
45+
// TODO(dnys1): Handle stdin?
46+
}
47+
48+
/// Kills the process with the given [signal] and waits for the process to
49+
/// exit.
50+
Future<void> stop([ProcessSignal signal = ProcessSignal.sigterm]) async {
51+
if (_process case final process?) {
52+
process.kill(signal);
53+
await process.exitCode;
54+
}
55+
_process = null;
56+
}
57+
}

0 commit comments

Comments
 (0)