composeFile function
Generates a docker-compose.yaml covering every service in a repository.
revali_docker produces a Dockerfile per service, which is enough to ship
one of them and not enough to run the system. Starting five services by
hand is the point at which people stop running the whole thing locally and
start testing one service against staging — which hides exactly the
mismatches that splitting into services creates.
Ports are assigned sequentially from basePort and passed as PORT, which
AppConfig.fromEnv reads. A service with a hard-coded port ignores it, so
the generated file says so rather than silently mapping to a port nothing
is listening on.
Implementation
String composeFile(List<RevaliService> services, {int basePort = 8080}) {
if (services.isEmpty) {
return '# No Revali services found.\n';
}
final buffer = StringBuffer()
..writeln('# Generated by `revali compose`. Safe to edit — regenerating')
..writeln('# overwrites, so keep hand-written additions in an override')
..writeln('# file (compose.override.yaml).')
..writeln('#')
..writeln('# Each service is given its port via the PORT environment')
..writeln('# variable, which `AppConfig.fromEnv()` reads. A service that')
..writeln('# hard-codes its port will ignore this and the mapping below')
..writeln('# will point at nothing.')
..writeln()
..writeln('services:');
final keys = _keysFor(services);
for (final (index, service) in services.indexed) {
final port = basePort + index;
buffer
..writeln(' ${keys[index]}:')
..writeln(' build:')
..writeln(' context: ${service.relativePath}')
..writeln(' dockerfile: .revali/build/Dockerfile');
if (!service.hasDockerfile) {
// Reported rather than omitted: a service silently missing from the
// compose file is harder to notice than one that fails to build.
buffer
..writeln(' # No Dockerfile yet — run `revali build` in')
..writeln(' # ${service.relativePath} before `docker compose up`.');
}
buffer
..writeln(' environment:')
..writeln(" PORT: '$port'")
..writeln(' ports:')
..writeln(" - '$port:$port'")
..writeln(' restart: unless-stopped');
if (index < services.length - 1) {
buffer.writeln();
}
}
return buffer.toString();
}