자동빌드 배포 오류 수정

This commit is contained in:
Macbook
2026-06-15 15:44:43 +09:00
parent 6720a5ed46
commit cab5365b19
14 changed files with 499 additions and 23 deletions
+65 -16
View File
@@ -1,14 +1,16 @@
/**
* 모바일·PC 프로그램 설치 — 탭 UI (모바일 QR / PC dmg·exe + Jenkins 빌드)
*/
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import QRCode from 'qrcode';
import DraggableModalFrame from './DraggableModalFrame';
import {
loadDesktopAppBuildReadiness,
loadDesktopAppBuildStatus,
loadDesktopAppReleaseInfo,
loadMobileAppReleaseInfo,
triggerDesktopAppBuild,
type DesktopAppBuildReadinessDto,
type DesktopAppBuildStatusDto,
type DesktopAppPlatformReleaseDto,
type DesktopAppReleaseInfoDto,
@@ -351,12 +353,14 @@ const DesktopTab: React.FC<{ canBuildDesktop: boolean }> = ({ canBuildDesktop })
const [info, setInfo] = useState<DesktopAppReleaseInfoDto | null>(null);
const [mobileInfo, setMobileInfo] = useState<MobileAppReleaseInfoDto | null>(null);
const [buildStatus, setBuildStatus] = useState<DesktopAppBuildStatusDto | null>(null);
const [readiness, setReadiness] = useState<DesktopAppBuildReadinessDto | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [buildBusy, setBuildBusy] = useState(false);
const [buildHint, setBuildHint] = useState<string | null>(null);
const [buildTracking, setBuildTracking] = useState(false);
const [elapsedTick, setElapsedTick] = useState(0);
const autoTrackStarted = useRef(false);
const reloadRelease = useCallback(async () => {
const [desktop, mobile] = await Promise.all([
@@ -374,19 +378,28 @@ const DesktopTab: React.FC<{ canBuildDesktop: boolean }> = ({ canBuildDesktop })
return st;
}, []);
const reloadReadiness = useCallback(async () => {
const rd = await loadDesktopAppBuildReadiness();
setReadiness(rd);
return rd;
}, []);
useEffect(() => {
let cancelled = false;
void Promise.all([reloadRelease(), reloadBuildStatus()])
void Promise.all([reloadRelease(), reloadBuildStatus(), reloadReadiness()])
.catch(() => { if (!cancelled) setError('PC 프로그램 정보를 불러오지 못했습니다.'); })
.finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; };
}, [reloadRelease, reloadBuildStatus]);
}, [reloadRelease, reloadBuildStatus, reloadReadiness]);
const isBuilding = Boolean(buildStatus?.building || buildStatus?.status === 'queued');
const showBuildProgress = Boolean(
buildBusy || buildTracking || isBuilding
|| (buildStatus?.buildNumber && buildStatus.status !== 'idle' && buildStatus.status !== 'ready'),
);
const showBuildProgress = Boolean(buildBusy || buildTracking || isBuilding);
useEffect(() => {
if (loading || autoTrackStarted.current || !isBuilding) return;
autoTrackStarted.current = true;
setBuildTracking(true);
}, [loading, isBuilding]);
useEffect(() => {
if (!showBuildProgress || buildStatus?.startedAtMs == null) return;
@@ -401,27 +414,38 @@ const DesktopTab: React.FC<{ canBuildDesktop: boolean }> = ({ canBuildDesktop })
if (!st) return;
if (isTerminalBuildStatus(st.status, st.building)) {
setBuildTracking(false);
void reloadRelease();
} else if (!st.building && st.status !== 'queued') {
void reloadRelease();
void Promise.all([reloadRelease(), reloadReadiness()]).then(([desktop]) => {
if (st.status === 'success' && readiness?.nextBuildVersion && desktop?.version) {
if (desktop.version === readiness.nextBuildVersion) {
setBuildHint(`빌드 완료 — v${desktop.version} 설치 파일이 반영되었습니다.`);
} else {
setBuildHint(`빌드 완료 — v${desktop.version} (예상 v${readiness.nextBuildVersion})`);
}
} else if (st.status === 'failure') {
setBuildHint(st.message ?? '빌드 실패 — Jenkins 로그를 확인하세요.');
}
});
}
});
};
poll();
const timer = window.setInterval(poll, BUILD_POLL_MS);
return () => window.clearInterval(timer);
}, [buildTracking, isBuilding, buildBusy, reloadBuildStatus, reloadRelease]);
}, [buildTracking, isBuilding, buildBusy, reloadBuildStatus, reloadRelease, reloadReadiness, readiness?.nextBuildVersion]);
const handleBuild = useCallback(async () => {
if (!canBuildDesktop || buildBusy || isBuilding) return;
setBuildBusy(true);
setBuildTracking(true);
setBuildHint('Jenkins 빌드 요청 중…');
const nextVer = readiness?.nextBuildVersion;
setBuildHint(nextVer
? `Jenkins 빌드 요청 중… (예상 v${nextVer})`
: 'Jenkins 빌드 요청 중…');
try {
const res = await triggerDesktopAppBuild();
setBuildHint(res.message ?? (res.accepted ? '빌드 시작됨' : '빌드 요청 거부'));
if (!res.accepted) setBuildTracking(false);
await reloadBuildStatus();
await Promise.all([reloadBuildStatus(), reloadReadiness()]);
if (res.accepted) {
window.setTimeout(() => { void reloadBuildStatus(); }, 2000);
}
@@ -431,7 +455,7 @@ const DesktopTab: React.FC<{ canBuildDesktop: boolean }> = ({ canBuildDesktop })
} finally {
setBuildBusy(false);
}
}, [canBuildDesktop, buildBusy, isBuilding, reloadBuildStatus]);
}, [canBuildDesktop, buildBusy, isBuilding, reloadBuildStatus, reloadReadiness, readiness?.nextBuildVersion]);
const hasAny = info?.mac?.available || info?.windows?.available || mobileInfo?.available;
@@ -443,10 +467,21 @@ const DesktopTab: React.FC<{ canBuildDesktop: boolean }> = ({ canBuildDesktop })
),
);
const readinessLabel = useMemo(() => {
if (!readiness) return null;
switch (readiness.status) {
case 'up_to_date': return '최신 버전';
case 'web_only': return 'PC 빌드 불필요';
case 'build_required': return '빌드 필요';
case 'building': return '빌드 진행 중';
default: return readiness.buildRequired ? '빌드 필요' : '상태 확인';
}
}, [readiness]);
if (loading) return <p className="app-download-muted">PC </p>;
if (error) return <p className="app-download-error">{error}</p>;
const statusMessage = !showBuildProgress ? (buildHint ?? buildStatus?.message) : null;
const statusMessage = !showBuildProgress ? (buildHint ?? (readiness?.status === 'up_to_date' ? readiness.message : buildStatus?.message)) : null;
return (
<>
@@ -467,7 +502,9 @@ const DesktopTab: React.FC<{ canBuildDesktop: boolean }> = ({ canBuildDesktop })
className={`app-download-build-btn${buildBusy || isBuilding ? ' busy' : ''}`}
onClick={() => { void handleBuild(); }}
disabled={buildBusy || isBuilding}
title="Git main 최신 소스로 macOS·Windows 데스크톱 앱 Jenkins 빌드"
title={readiness?.buildRequired
? `다음 빌드 예상 버전 v${readiness.nextBuildVersion ?? '?'}`
: '현재 최신 소스가 PC 빌드에 반영되어 있습니다'}
aria-label="데스크톱 앱 빌드"
>
<IcDesktopBuild />
@@ -476,6 +513,18 @@ const DesktopTab: React.FC<{ canBuildDesktop: boolean }> = ({ canBuildDesktop })
)}
</div>
{readiness && readinessLabel && (
<div className={`app-download-readiness app-download-readiness--${readiness.status}`}>
<span className="app-download-readiness-badge">{readinessLabel}</span>
<p className="app-download-readiness-msg">{readiness.message}</p>
{readiness.publishedVersion && readiness.nextBuildVersion && readiness.buildRequired && (
<p className="app-download-readiness-ver">
v{readiness.publishedVersion} v{readiness.nextBuildVersion}
</p>
)}
</div>
)}
{desktopVersionMismatch && (
<p className="app-download-error" style={{ marginBottom: 8 }}>
macOS·Windows (v{info?.version}) . .