This commit is contained in:
nuno maduro
2026-05-01 16:42:01 +01:00
parent 0a97d3a288
commit fda515a17f

View File

@ -69,11 +69,6 @@ final readonly class BaselineSync
return false; return false;
} }
$this->output->writeln(sprintf(
' <fg=cyan>TIA</> fetching baseline from <fg=white>%s</>…',
$repo,
));
$payload = $this->download($repo, $projectRoot); $payload = $this->download($repo, $projectRoot);
if ($payload === null) { if ($payload === null) {
@ -322,6 +317,12 @@ YAML;
// id as recently used and doesn't evict it later. // id as recently used and doesn't evict it later.
@touch($runCacheDir); @touch($runCacheDir);
$this->output->writeln(sprintf(
' <fg=cyan>TIA</> using cached baseline from <fg=white>%s</> (run %s).',
$repo,
$runId,
));
return $this->readArtifact($runCacheDir); return $this->readArtifact($runCacheDir);
} }
@ -329,6 +330,19 @@ YAML;
return null; return null;
} }
$artifactSize = $this->artifactSize($repo, $runId);
$this->output->writeln($artifactSize !== null
? sprintf(
' <fg=cyan>TIA</> fetching baseline (%s) from <fg=white>%s</>…',
$this->formatSize($artifactSize),
$repo,
)
: sprintf(
' <fg=cyan>TIA</> fetching baseline from <fg=white>%s</>…',
$repo,
));
$process = new Process([ $process = new Process([
'gh', 'run', 'download', $runId, 'gh', 'run', 'download', $runId,
'-R', $repo, '-R', $repo,
@ -336,7 +350,17 @@ YAML;
'-D', $runCacheDir, '-D', $runCacheDir,
]); ]);
$process->setTimeout(900.0); $process->setTimeout(900.0);
$process->run(); $process->start();
$startedAt = microtime(true);
while ($process->isRunning()) {
$this->renderDownloadProgress($runCacheDir, $artifactSize, $startedAt);
usleep(250_000);
}
$process->wait();
$this->clearProgressLine();
if (! $process->isSuccessful()) { if (! $process->isSuccessful()) {
$this->cleanup($runCacheDir); $this->cleanup($runCacheDir);
@ -357,6 +381,93 @@ YAML;
return $payload; return $payload;
} }
/**
* Looks up the artifact's compressed size so the progress bar has a
* denominator. Returns null on any failure — callers fall back to a
* size-less spinner.
*/
private function artifactSize(string $repo, string $runId): ?int
{
$process = new Process([
'gh', 'api',
sprintf('repos/%s/actions/runs/%s/artifacts', $repo, $runId),
'--jq', sprintf(
'.artifacts[] | select(.name == "%s") | .size_in_bytes',
self::ARTIFACT_NAME,
),
]);
$process->setTimeout(30.0);
$process->run();
if (! $process->isSuccessful()) {
return null;
}
$size = trim($process->getOutput());
return is_numeric($size) ? (int) $size : null;
}
private function renderDownloadProgress(string $dir, ?int $totalBytes, float $startedAt): void
{
$current = $this->dirSize($dir);
$elapsed = max(0.001, microtime(true) - $startedAt);
$speed = (int) ($current / $elapsed);
if ($totalBytes !== null && $totalBytes > 0) {
// gh extracts as it downloads, so disk size can briefly exceed
// the compressed `size_in_bytes` for multi-file artifacts. Cap
// the percentage at 99% until the process actually exits — the
// cleared line + completion message take care of the final
// "100%" message naturally.
$percent = min(99, (int) floor(($current / $totalBytes) * 100));
$message = sprintf(
' <fg=cyan>TIA</> downloading %s / %s (%d%%, %s/s)',
$this->formatSize($current),
$this->formatSize($totalBytes),
$percent,
$this->formatSize($speed),
);
} else {
$message = sprintf(
' <fg=cyan>TIA</> downloading %s (%s/s)',
$this->formatSize($current),
$this->formatSize($speed),
);
}
// \r returns to start of line, \033[K erases from cursor to end —
// safe regardless of message length, no ANSI-aware padding needed.
$this->output->write("\r\033[K".$message);
}
private function clearProgressLine(): void
{
$this->output->write("\r\033[K");
}
private function dirSize(string $dir): int
{
if (! is_dir($dir)) {
return 0;
}
$total = 0;
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS),
);
/** @var \SplFileInfo $entry */
foreach ($iterator as $entry) {
if ($entry->isFile()) {
$total += $entry->getSize();
}
}
return $total;
}
/** /**
* @return array{graph: string, coverage: ?string}|null * @return array{graph: string, coverage: ?string}|null
*/ */