Add app source and tests
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
180
lib/Peach/see/deploy/batch/ViewBatchDeploy.dart
Normal file
180
lib/Peach/see/deploy/batch/ViewBatchDeploy.dart
Normal file
@@ -0,0 +1,180 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../../../about/internal/application/DeploymentRecord.dart';
|
||||
import '../../../about/internal/application/ServerProfile.dart';
|
||||
import '../../../about/internal/application/ServiceConfig.dart';
|
||||
import '../../../about/internal/application/StepTracker.dart';
|
||||
import '../../../comms/Comms.dart' show DeployResult, LogLine;
|
||||
import '../../../utils/CommonUtils.dart';
|
||||
import '../../parent/ParentViewModel.dart';
|
||||
import 'ConnectBatchDeploy.dart';
|
||||
|
||||
class ViewBatchDeploy extends ParentViewModel {
|
||||
ConnectBatchDeploy connection;
|
||||
|
||||
ViewBatchDeploy(super.context, this.connection);
|
||||
|
||||
bool _cancelRequested = false;
|
||||
|
||||
Stream<LogLine> get logs => getDataManager().logs;
|
||||
|
||||
Stream<LogLine> get buildLogs => getDataManager().buildLogs;
|
||||
|
||||
Stream<List<DeployStep>> get steps => getDataManager().stepStream;
|
||||
|
||||
Stream<List<DeployStep>> get buildSteps => getDataManager().buildStepStream;
|
||||
|
||||
/// Builds (and optionally deploys) each service in [batch], in order. One
|
||||
/// service failing doesn't stop the rest — the run reports per-service.
|
||||
void runBatch(
|
||||
ServerProfile server,
|
||||
List<ServiceConfig> batch, {
|
||||
required bool deployAfterBuild,
|
||||
}) async {
|
||||
if (deployAfterBuild &&
|
||||
!await canReach(
|
||||
server,
|
||||
() => runBatch(server, batch, deployAfterBuild: deployAfterBuild),
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_cancelRequested = false;
|
||||
connection.onBatchStarted(batch.map((s) => s.id).toList());
|
||||
|
||||
for (final service in batch) {
|
||||
if (_cancelRequested) {
|
||||
connection.onServiceState(service.id, BatchState.skipped);
|
||||
continue;
|
||||
}
|
||||
await _runOne(server, service, deployAfterBuild);
|
||||
}
|
||||
|
||||
connection.onBatchFinished();
|
||||
}
|
||||
|
||||
Future<void> _runOne(
|
||||
ServerProfile server,
|
||||
ServiceConfig service,
|
||||
bool deployAfterBuild,
|
||||
) async {
|
||||
connection.onServiceState(service.id, BatchState.building);
|
||||
|
||||
final built = service.isWeb
|
||||
? await getDataManager().buildWeb(service)
|
||||
: await getDataManager().build(service);
|
||||
|
||||
if (!built.ok) {
|
||||
connection.onServiceState(
|
||||
service.id,
|
||||
BatchState.failed,
|
||||
detail: built.message,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!deployAfterBuild) {
|
||||
connection.onServiceState(service.id, BatchState.done, detail: 'Built');
|
||||
return;
|
||||
}
|
||||
|
||||
connection.onServiceState(service.id, BatchState.deploying);
|
||||
|
||||
DeployResult result;
|
||||
String artifact;
|
||||
try {
|
||||
if (service.isWeb) {
|
||||
final directory = built.dir!.path;
|
||||
if (!File('$directory/index.html').existsSync()) {
|
||||
connection.onServiceState(
|
||||
service.id,
|
||||
BatchState.failed,
|
||||
detail: 'No index.html in build output.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
final (bytes, count) = await getDataManager().packageWeb(directory);
|
||||
artifact = service.framework.label;
|
||||
result = await getDataManager().deployWeb(
|
||||
server: server,
|
||||
service: service,
|
||||
archiveBytes: bytes,
|
||||
fileCount: count,
|
||||
);
|
||||
} else {
|
||||
final jar = built.jar!;
|
||||
artifact = baseName(jar.path);
|
||||
result = await getDataManager().deploy(
|
||||
server: server,
|
||||
service: service,
|
||||
jarBytes: await jar.readAsBytes(),
|
||||
jarLabel: artifact,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
connection.onServiceState(
|
||||
service.id,
|
||||
BatchState.failed,
|
||||
detail: 'Error: $error',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await _record(server, service, result, artifact);
|
||||
connection.onServiceState(
|
||||
service.id,
|
||||
result.ok ? BatchState.done : BatchState.failed,
|
||||
detail: result.message,
|
||||
);
|
||||
}
|
||||
|
||||
/// Stops after the service currently running finishes.
|
||||
void requestCancel() {
|
||||
_cancelRequested = true;
|
||||
getDataManager().requestCancel();
|
||||
getDataManager().cancelBuild();
|
||||
}
|
||||
|
||||
/// Remembers the order services should be deployed in next time.
|
||||
void saveDeployOrder(ServerProfile server, List<String> ids) async {
|
||||
try {
|
||||
final servers = await getDataManager().getServers();
|
||||
final index = servers.indexWhere((s) => s.id == server.id);
|
||||
if (index < 0) return;
|
||||
servers[index].deployOrder = ids;
|
||||
await getDataManager().setServers(servers);
|
||||
} catch (error) {
|
||||
handleError(error, () => saveDeployOrder(server, ids), () {}, "Retry");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _record(
|
||||
ServerProfile server,
|
||||
ServiceConfig service,
|
||||
DeployResult result,
|
||||
String artifact,
|
||||
) async {
|
||||
try {
|
||||
await getDataManager().addDeployment(
|
||||
DeploymentRecord(
|
||||
id: const Uuid().v4(),
|
||||
serverId: server.id,
|
||||
serverName: server.name,
|
||||
serviceId: service.id,
|
||||
serviceLabel: service.name,
|
||||
jarName: artifact,
|
||||
remotePath: service.isWeb
|
||||
? service.remoteRoot
|
||||
: service.remotePath(artifact),
|
||||
time: DateTime.now(),
|
||||
status: result.ok ? DeployStatus.success : DeployStatus.failure,
|
||||
message: result.message,
|
||||
),
|
||||
);
|
||||
} catch (_) {
|
||||
// A missing history row must not change what the batch reports.
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user