Add app source and tests

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
alvocool
2026-10-11 10:15:30 +03:00
parent 7e246255c2
commit 38881d1fe1
135 changed files with 21057 additions and 0 deletions

View 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.
}
}
}