1
0
forked from boos/BoosAPI

Merge PR #495: auto-discover recent Docker Hub dev tags

Merged after resolving conflicts with current main.\n\nValidation:\n- GitHub checks all passing: Test Core, Typecheck, Build Web/Server/Desktop, Repo Drift Check, Schema checks, CodeQL, CodeRabbit.\n- Local: npx vitest run --root . src/web/pages/settings/UpdateCenterSection.test.tsx src/server/services/updateCenterVersionService.test.ts src/server/routes/api/updateCenter.test.ts src/server/services/updateCenterStatusService.test.ts\n- Local: npm run typecheck\n- Local: npm run repo:drift-check
This commit is contained in:
Cita
2026-05-08 16:27:26 +08:00
committed by GitHub
2 changed files with 22 additions and 3 deletions
@@ -88,6 +88,12 @@ describe('UpdateCenterSection', () => {
displayVersion: 'dev-20260417-f67ade2 @ sha256:bbbbbbbbbbbb', displayVersion: 'dev-20260417-f67ade2 @ sha256:bbbbbbbbbbbb',
publishedAt: '2026-03-30T10:54:35.591877Z', publishedAt: '2026-03-30T10:54:35.591877Z',
}, },
{
normalizedVersion: 'feature-without-raw-tag',
digest: 'sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
displayVersion: 'feature-without-raw-tag @ sha256:cccccccccccc',
publishedAt: '2026-03-30T09:54:35.591877Z',
},
], ],
helper: { helper: {
ok: true, ok: true,
@@ -470,6 +476,8 @@ describe('UpdateCenterSection', () => {
}); });
await flushMicrotasks(); await flushMicrotasks();
expect(collectText(root.root)).toContain('Digest:sha256:bbbbbbbbbbbb');
expect(collectText(root.root)).not.toContain('feature-without-raw-tag');
expect(collectText(root.root)).toContain('dev-20260417-f67ade2 @ sha256:bbbbbbbbbbbb'); expect(collectText(root.root)).toContain('dev-20260417-f67ade2 @ sha256:bbbbbbbbbbbb');
const deployRecentButton = root.root.find((node) => ( const deployRecentButton = root.root.find((node) => (
+14 -3
View File
@@ -221,9 +221,15 @@ function formatImageTarget(tag?: string | null, digest?: string | null) {
return ''; return '';
} }
function normalizeRecentDockerCandidates(input?: UpdateCenterStatus['dockerHubRecentTags'] | null) { type RecentDockerCandidate = NonNullable<NonNullable<UpdateCenterStatus['dockerHubRecentTags']>[number]>;
function normalizeRecentDockerCandidates(
input?: UpdateCenterStatus['dockerHubRecentTags'] | null,
): Array<RecentDockerCandidate & { tagName: string }> {
if (!Array.isArray(input)) return []; if (!Array.isArray(input)) return [];
return input.filter((entry) => String(entry?.tagName || entry?.normalizedVersion || '').trim()); return input.filter(
(entry): entry is RecentDockerCandidate & { tagName: string } => !!String(entry?.tagName || '').trim(),
);
} }
export default function UpdateCenterSection() { export default function UpdateCenterSection() {
@@ -796,7 +802,7 @@ export default function UpdateCenterSection() {
{recentDockerCandidates.length ? ( {recentDockerCandidates.length ? (
<div style={{ display: 'grid', gap: 8 }}> <div style={{ display: 'grid', gap: 8 }}>
{recentDockerCandidates.map((candidate) => { {recentDockerCandidates.map((candidate) => {
const candidateTag = String(candidate.tagName || candidate.normalizedVersion || '').trim(); const candidateTag = String(candidate.tagName || '').trim();
const candidateDigest = String(candidate.digest || '').trim(); const candidateDigest = String(candidate.digest || '').trim();
const candidateLabel = candidate.displayVersion || candidate.normalizedVersion || candidateTag; const candidateLabel = candidate.displayVersion || candidate.normalizedVersion || candidateTag;
const candidateDeployState = describeDockerDeployState({ const candidateDeployState = describeDockerDeployState({
@@ -825,6 +831,11 @@ export default function UpdateCenterSection() {
<div style={fieldHintStyle}> <div style={fieldHintStyle}>
最近推送:{formatTaskTime(candidate.publishedAt)} 最近推送:{formatTaskTime(candidate.publishedAt)}
</div> </div>
{candidateDigest ? (
<div style={{ ...fieldHintStyle, fontFamily: 'var(--font-mono)' }} title={candidateDigest}>
Digest:{formatShortDigest(candidateDigest)}
</div>
) : null}
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}> <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
<button <button
type="button" type="button"