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:
@@ -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) => (
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user