feat(server): 권장 JDK 탐색을 PF/환경변수 중심으로 + 불일치 경고 + .mc_custom/jdk 설치
- JDK 탐색을 C:\Program Files\Java → 환경변수(JAVA_HOME/JDK_HOME) → 설치기 자동설치 위치 순으로 훑어 권장 버전을 우선 선택. 권장이 없으면 가장 높은 버전을 대신 고르되 match=false 로 표시. - 권장과 다른 버전을 찾거나 직접 선택하면 "권장과 달라 정상 실행이 안 될 수 있음" 경고를 띄우고, 동의(확인) 시에만 진행(무조건 차단 대신 경고+동의). - 자동 설치 경로를 %APPDATA%/jdk → .mc_custom/jdk 로 변경(파일제거기가 함께 정리). Adoptium zip 의 중첩 jdk-* 폴더까지 탐색하도록 resolveJdkHome 보강. - 버전 0.4.6→0.4.7. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -297,51 +297,95 @@ function findJavaExeInHome(candidate: string): string {
|
||||
return ''
|
||||
}
|
||||
|
||||
/** javaExe 로부터 JDK 홈 디렉터리를 되돌린다(<home>/bin/java.exe → <home>). */
|
||||
function homeFromJavaExe(javaExe: string, fallbackHome: string): string {
|
||||
return path.basename(path.dirname(javaExe)) === 'bin'
|
||||
? path.dirname(path.dirname(javaExe))
|
||||
: fallbackHome
|
||||
/** %appdata%/<.mc_custom>. 자동 설치 JDK 는 이 폴더 아래 jdk/ 에 둔다. */
|
||||
function customRootDir(): string {
|
||||
return path.join(getAppDataDir(), getMcCustomDirName())
|
||||
}
|
||||
|
||||
/** 설치기 자동 설치 JDK 의 루트(.mc_custom/jdk). */
|
||||
function installerJdkRoot(): string {
|
||||
return path.join(customRootDir(), 'jdk')
|
||||
}
|
||||
|
||||
/** dir(또는 그 한 단계 하위 jdk-* 폴더)에서 java 를 찾아 실제 JDK 홈(<home>/bin/java)을 돌려준다. 없으면 ''. */
|
||||
function resolveJdkHome(dir: string): string {
|
||||
const javaExe = findJavaExeInHome(dir)
|
||||
if (!javaExe) return ''
|
||||
// findJavaExeInHome 은 항상 <home>/bin/java(.exe) 를 돌려준다 → 상위 두 단계가 홈.
|
||||
return path.basename(path.dirname(javaExe)) === 'bin' ? path.dirname(path.dirname(javaExe)) : dir
|
||||
}
|
||||
|
||||
/**
|
||||
* parent 아래에서 JDK 홈들을 모두 찾는다. parent 자신, 그리고 각 하위 폴더를 검사하되,
|
||||
* Adoptium zip 처럼 한 단계 더 감싼 `jdk-*` 하위까지 resolveJdkHome 으로 풀어낸다.
|
||||
* (예: .mc_custom/jdk/temurin-25/jdk-25.0.3+9/bin/java.exe)
|
||||
*/
|
||||
function jdkHomesUnder(parent: string): string[] {
|
||||
const out: string[] = []
|
||||
const add = (h: string): void => { if (h) out.push(h) }
|
||||
add(resolveJdkHome(parent))
|
||||
try {
|
||||
for (const entry of fs.readdirSync(parent)) {
|
||||
add(resolveJdkHome(path.join(parent, entry)))
|
||||
}
|
||||
} catch {
|
||||
/* 폴더 없음 등 무시 */
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* 컴퓨터에 설치된 JDK 홈 목록을 우선순위대로 모은다(중복 제거).
|
||||
* 순서: ① 기본 자바 설치 위치 C:\Program Files\Java → ② 환경변수(JAVA_HOME/JDK_HOME)
|
||||
* → ③ 설치기 자동 설치 위치(.mc_custom/jdk, 구버전 %APPDATA%/jdk 호환).
|
||||
*/
|
||||
function collectJdkHomes(): string[] {
|
||||
const homes: string[] = []
|
||||
const push = (h: string): void => {
|
||||
if (h && !homes.some((x) => x.toLowerCase() === h.toLowerCase())) homes.push(h)
|
||||
}
|
||||
for (const h of jdkHomesUnder('C:\\Program Files\\Java')) push(h)
|
||||
for (const env of [process.env.JAVA_HOME, process.env.JDK_HOME]) {
|
||||
if (env) push(resolveJdkHome(env))
|
||||
}
|
||||
for (const base of [installerJdkRoot(), path.join(getAppDataDir(), 'jdk')]) {
|
||||
for (const h of jdkHomesUnder(base)) push(h)
|
||||
}
|
||||
return homes
|
||||
}
|
||||
|
||||
// 권장 JDK 를 우선 찾는다. C:\Program Files\Java → 환경변수 → 설치기 자동설치 위치를
|
||||
// 훑어, ① 권장 버전과 정확히 일치하는 JDK 가 있으면 그걸 쓰고(match=true),
|
||||
// ② 없으면 가장 높은 버전을 대신 고르되 "권장과 다름"(match=false)으로 표시해 렌더러가
|
||||
// 경고를 띄우게 한다. ③ 아무 JDK 도 없으면 not found → 자동 설치 유도.
|
||||
ipcMain.handle('jdk:detect', async (_event, recommendedInput?: number) => {
|
||||
const required = normalizeRecommendedJdk(recommendedInput)
|
||||
const javaName = process.platform === 'win32' ? 'java.exe' : 'java'
|
||||
const found = collectJdkHomes()
|
||||
.map((home) => ({ home, major: getJavaMajor(path.join(home, 'bin', javaName)) }))
|
||||
.filter((x) => x.major > 0)
|
||||
|
||||
// 1) 권장 JDK(설치기가 자동 설치하는 위치)를 가장 먼저 찾는다.
|
||||
const recommendedHome = path.join(getAppDataDir(), 'jdk', jdkDirName(required))
|
||||
const recJava = findJavaExeInHome(recommendedHome)
|
||||
if (recJava && getJavaMajor(recJava) >= required) {
|
||||
return { found: true, path: homeFromJavaExe(recJava, recommendedHome), source: 'recommended', major: required }
|
||||
const exact = found.find((x) => x.major === required)
|
||||
if (exact) return { found: true, path: exact.home, major: required, match: true }
|
||||
if (found.length > 0) {
|
||||
const best = found.slice().sort((a, b) => b.major - a.major)[0]
|
||||
return { found: true, path: best.home, major: best.major, match: false }
|
||||
}
|
||||
|
||||
// 2) 없으면 컴퓨터의 JDK(JAVA_HOME/JDK_HOME/Program Files)를 확인한다.
|
||||
// 권장 버전 미만은 서버 실행에 부족하므로(UnsupportedClassVersionError) "없음" 처리.
|
||||
const candidates: string[] = []
|
||||
if (process.env.JAVA_HOME) candidates.push(process.env.JAVA_HOME)
|
||||
if (process.env.JDK_HOME) candidates.push(process.env.JDK_HOME)
|
||||
candidates.push('C:\\Program Files\\Java')
|
||||
for (const candidate of candidates) {
|
||||
if (!candidate) continue
|
||||
const javaExe = findJavaExeInHome(candidate)
|
||||
if (!javaExe) continue
|
||||
const major = getJavaMajor(javaExe)
|
||||
if (major < required) continue
|
||||
return { found: true, path: homeFromJavaExe(javaExe, candidate), source: 'system', major }
|
||||
}
|
||||
return { found: false, path: '', major: required }
|
||||
return { found: false, path: '', major: required, match: false }
|
||||
})
|
||||
|
||||
// 사용자가 직접 입력/선택한 JDK 경로가 서버 실행에 충분한 버전인지 확인한다.
|
||||
// ok=true → major >= 요구 버전(25).
|
||||
// major=0 → 경로에서 java 를 못 찾거나 버전을 못 읽음(실행 실패 등).
|
||||
ipcMain.handle('jdk:verify', async (_event, jdkPath: string, recommendedInput?: number): Promise<{ ok: boolean; major: number; required: number }> => {
|
||||
// 사용자가 직접 입력/선택한 JDK 경로의 자바 버전을 확인한다.
|
||||
// major=0 → 경로에서 java 를 못 찾거나 버전을 못 읽음(진행 차단용).
|
||||
// match=true → 권장 버전과 정확히 일치.
|
||||
// match=false → 다른 버전(진행은 허용하되 렌더러가 경고를 띄운다).
|
||||
ipcMain.handle('jdk:verify', async (_event, jdkPath: string, recommendedInput?: number): Promise<{ ok: boolean; major: number; required: number; match: boolean }> => {
|
||||
const required = normalizeRecommendedJdk(recommendedInput)
|
||||
const javaExe = jdkPath ? findJavaExeInHome(jdkPath) : ''
|
||||
const major = javaExe ? getJavaMajor(javaExe) : 0
|
||||
return { ok: major >= required, major, required }
|
||||
return { ok: major > 0, major, required, match: major === required }
|
||||
})
|
||||
|
||||
// ── JDK 자동 설치(Temurin 25, 취소 가능) ──────────────────────────────
|
||||
// ── JDK 자동 설치(권장 버전 Temurin, .mc_custom/jdk 에 설치, 취소 가능) ──────
|
||||
interface JdkInstallState {
|
||||
controller: AbortController | null
|
||||
destDir: string | null
|
||||
@@ -423,10 +467,11 @@ ipcMain.handle('jdk:install', async (_event, recommendedInput?: number): Promise
|
||||
jdkInstall.inProgress = true
|
||||
const controller = new AbortController()
|
||||
jdkInstall.controller = controller
|
||||
const tmpRoot = path.join(getAppDataDir(), 'jdk-cache')
|
||||
// 자동 설치 JDK 는 .mc_custom/jdk 아래에 둔다(파일제거기가 .mc_custom 통째로 정리 가능).
|
||||
const tmpRoot = path.join(installerJdkRoot(), '.cache')
|
||||
await fsp.mkdir(tmpRoot, { recursive: true })
|
||||
const tempZip = path.join(tmpRoot, `${jdkDirName(required)}-${Date.now()}.zip`)
|
||||
const destDir = path.join(getAppDataDir(), 'jdk', jdkDirName(required))
|
||||
const destDir = path.join(installerJdkRoot(), jdkDirName(required))
|
||||
jdkInstall.destDir = destDir
|
||||
try {
|
||||
// Adoptium API v3: latest GA JDK(Windows x64). 본문은 307 로 GitHub 릴리즈로 리다이렉트.
|
||||
|
||||
@@ -24,9 +24,9 @@ const api = {
|
||||
ipcRenderer.invoke('install:validatePath', target),
|
||||
|
||||
// 3-2
|
||||
detectJdk: (recommendedJdk?: number): Promise<{ found: boolean; path: string; source?: string; major?: number }> =>
|
||||
detectJdk: (recommendedJdk?: number): Promise<{ found: boolean; path: string; major: number; match: boolean }> =>
|
||||
ipcRenderer.invoke('jdk:detect', recommendedJdk),
|
||||
verifyJdk: (jdkPath: string, recommendedJdk?: number): Promise<{ ok: boolean; major: number; required: number }> =>
|
||||
verifyJdk: (jdkPath: string, recommendedJdk?: number): Promise<{ ok: boolean; major: number; required: number; match: boolean }> =>
|
||||
ipcRenderer.invoke('jdk:verify', jdkPath, recommendedJdk),
|
||||
installJdk: (recommendedJdk?: number): Promise<{ ok: boolean; path?: string; message?: string }> =>
|
||||
ipcRenderer.invoke('jdk:install', recommendedJdk),
|
||||
|
||||
Reference in New Issue
Block a user