Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d9b5183b8 |
@@ -641,13 +641,17 @@ function renderSubStep32(host, back, done) {
|
|||||||
autoBtn.addEventListener('click', async function () {
|
autoBtn.addEventListener('click', async function () {
|
||||||
if (installing) return
|
if (installing) return
|
||||||
var detect = await installerApi.detectJdk(recommendedJdk)
|
var detect = await installerApi.detectJdk(recommendedJdk)
|
||||||
if (detect.found) {
|
if (detect.found && detect.match) {
|
||||||
input.value = detect.path
|
input.value = detect.path
|
||||||
msg.textContent = detect.source === 'recommended'
|
msg.textContent = tt('step3.sub32.foundRecommended', { path: detect.path, major: recommendedJdk })
|
||||||
? tt('step3.sub32.foundRecommended', { path: detect.path, major: recommendedJdk })
|
|
||||||
: tt('step3.sub32.found', { path: detect.path })
|
|
||||||
msg.classList.remove('error')
|
msg.classList.remove('error')
|
||||||
msg.classList.add('success')
|
msg.classList.add('success')
|
||||||
|
} else if (detect.found) {
|
||||||
|
// 권장 버전이 아닌 다른 JDK 를 찾음 → 경고와 함께 채워 넣는다.
|
||||||
|
input.value = detect.path
|
||||||
|
msg.textContent = tt('step3.sub32.foundMismatch', { path: detect.path, major: detect.major, required: recommendedJdk })
|
||||||
|
msg.classList.remove('success')
|
||||||
|
msg.classList.add('error')
|
||||||
} else {
|
} else {
|
||||||
msg.textContent = tt('step3.sub32.notFound', { major: recommendedJdk })
|
msg.textContent = tt('step3.sub32.notFound', { major: recommendedJdk })
|
||||||
msg.classList.remove('success')
|
msg.classList.remove('success')
|
||||||
@@ -703,21 +707,31 @@ function renderSubStep32(host, back, done) {
|
|||||||
msg.classList.add('error')
|
msg.classList.add('error')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 직접 입력/선택한 JDK 도 서버 실행에 충분한 버전(Java 25+)인지 검증한다.
|
// 직접 입력/선택한 JDK 를 확인한다. 자바를 못 읽으면 진행을 막고, 권장과 다른
|
||||||
// 너무 낮으면 run.bat 이 그 자바를 써도 UnsupportedClassVersionError 로 서버가 안 뜬다.
|
// 버전이면 "정상 실행이 안 될 수 있다"는 경고를 띄운 뒤 사용자가 동의하면 진행한다.
|
||||||
nextBtn.disabled = true
|
nextBtn.disabled = true
|
||||||
msg.classList.remove('success', 'error')
|
msg.classList.remove('success', 'error')
|
||||||
msg.textContent = tt('step3.sub32.verifying')
|
msg.textContent = tt('step3.sub32.verifying')
|
||||||
|
var required = recommendedJdk
|
||||||
try {
|
try {
|
||||||
var v = await installerApi.verifyJdk(val, recommendedJdk)
|
var v = await installerApi.verifyJdk(val, recommendedJdk)
|
||||||
if (!v || !v.ok) {
|
required = (v && v.required) || recommendedJdk
|
||||||
|
if (!v || !v.major) {
|
||||||
msg.classList.add('error')
|
msg.classList.add('error')
|
||||||
msg.textContent = (v && v.major)
|
msg.textContent = tt('step3.sub32.versionUnknown')
|
||||||
? tt('step3.sub32.versionTooLow', { major: v.major, required: (v && v.required) || recommendedJdk })
|
|
||||||
: tt('step3.sub32.versionUnknown')
|
|
||||||
nextBtn.disabled = false
|
nextBtn.disabled = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (!v.match) {
|
||||||
|
// 권장 버전과 다름 → 경고 후 동의 시에만 계속.
|
||||||
|
var proceed = window.confirm(tt('step3.sub32.mismatchConfirm', { major: v.major, required: required }))
|
||||||
|
if (!proceed) {
|
||||||
|
msg.classList.add('error')
|
||||||
|
msg.textContent = tt('step3.sub32.foundMismatch', { path: val, major: v.major, required: required })
|
||||||
|
nextBtn.disabled = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
msg.classList.add('error')
|
msg.classList.add('error')
|
||||||
msg.textContent = tt('step3.sub32.versionUnknown')
|
msg.textContent = tt('step3.sub32.versionUnknown')
|
||||||
@@ -732,10 +746,13 @@ function renderSubStep32(host, back, done) {
|
|||||||
var detect = await installerApi.detectJdk(recommendedJdk)
|
var detect = await installerApi.detectJdk(recommendedJdk)
|
||||||
if (detect.found && !input.value) {
|
if (detect.found && !input.value) {
|
||||||
input.value = detect.path
|
input.value = detect.path
|
||||||
msg.textContent = detect.source === 'recommended'
|
if (detect.match) {
|
||||||
? tt('step3.sub32.foundRecommended', { path: detect.path, major: recommendedJdk })
|
msg.textContent = tt('step3.sub32.foundRecommended', { path: detect.path, major: recommendedJdk })
|
||||||
: tt('step3.sub32.autoDetected', { path: detect.path })
|
|
||||||
msg.classList.add('success')
|
msg.classList.add('success')
|
||||||
|
} else {
|
||||||
|
msg.textContent = tt('step3.sub32.foundMismatch', { path: detect.path, major: detect.major, required: recommendedJdk })
|
||||||
|
msg.classList.add('error')
|
||||||
|
}
|
||||||
} else if (!detect.found) {
|
} else if (!detect.found) {
|
||||||
msg.textContent = tt('step3.sub32.notFoundHint', { major: recommendedJdk })
|
msg.textContent = tt('step3.sub32.notFoundHint', { major: recommendedJdk })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,8 @@
|
|||||||
"recommended": "권장 JDK: Java {{major}} (이 음악퀴즈 서버 실행용)",
|
"recommended": "권장 JDK: Java {{major}} (이 음악퀴즈 서버 실행용)",
|
||||||
"found": "JDK 발견: {{path}}",
|
"found": "JDK 발견: {{path}}",
|
||||||
"foundRecommended": "권장 JDK(Java {{major}}) 발견: {{path}}",
|
"foundRecommended": "권장 JDK(Java {{major}}) 발견: {{path}}",
|
||||||
|
"foundMismatch": "⚠ 권장(Java {{required}})이 아닌 Java {{major}} 를 찾았습니다: {{path}} — 권장 버전과 달라 서버가 정상 실행되지 않을 수 있습니다. 권장 버전을 쓰려면 \"자동 설치\" 를 누르세요.",
|
||||||
|
"mismatchConfirm": "선택한 Java {{major}} 는 권장 버전(Java {{required}})과 다릅니다. 권장 버전과 달라 서버가 정상 실행되지 않을 수 있습니다. 이대로 계속하시겠습니까?",
|
||||||
"autoDetected": "JDK 자동 탐색됨: {{path}}",
|
"autoDetected": "JDK 자동 탐색됨: {{path}}",
|
||||||
"notFound": "서버 실행에 필요한 Java {{major}} 이상을 찾지 못했습니다(낮은 버전이 설치돼 있어도 서버가 뜨지 않습니다). \"자동 설치\" 를 눌러 설치하거나 직접 선택해 주세요.",
|
"notFound": "서버 실행에 필요한 Java {{major}} 이상을 찾지 못했습니다(낮은 버전이 설치돼 있어도 서버가 뜨지 않습니다). \"자동 설치\" 를 눌러 설치하거나 직접 선택해 주세요.",
|
||||||
"notFoundHint": "Java {{major}} 이상을 찾지 못했습니다. \"자동 설치\" 를 누르면 Temurin {{major}} 를 받아 설치합니다.",
|
"notFoundHint": "Java {{major}} 이상을 찾지 못했습니다. \"자동 설치\" 를 누르면 Temurin {{major}} 를 받아 설치합니다.",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "minecraft-music-quiz-installer",
|
"name": "minecraft-music-quiz-installer",
|
||||||
"version": "0.4.6",
|
"version": "0.4.7",
|
||||||
"description": "마인크래프트 음악퀴즈 간편설치기 + 관리 사이트",
|
"description": "마인크래프트 음악퀴즈 간편설치기 + 관리 사이트",
|
||||||
"main": "dist/installer/main.js",
|
"main": "dist/installer/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -297,51 +297,95 @@ function findJavaExeInHome(candidate: string): string {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
/** javaExe 로부터 JDK 홈 디렉터리를 되돌린다(<home>/bin/java.exe → <home>). */
|
/** %appdata%/<.mc_custom>. 자동 설치 JDK 는 이 폴더 아래 jdk/ 에 둔다. */
|
||||||
function homeFromJavaExe(javaExe: string, fallbackHome: string): string {
|
function customRootDir(): string {
|
||||||
return path.basename(path.dirname(javaExe)) === 'bin'
|
return path.join(getAppDataDir(), getMcCustomDirName())
|
||||||
? path.dirname(path.dirname(javaExe))
|
|
||||||
: fallbackHome
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 설치기 자동 설치 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) => {
|
ipcMain.handle('jdk:detect', async (_event, recommendedInput?: number) => {
|
||||||
const required = normalizeRecommendedJdk(recommendedInput)
|
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 exact = found.find((x) => x.major === required)
|
||||||
const recommendedHome = path.join(getAppDataDir(), 'jdk', jdkDirName(required))
|
if (exact) return { found: true, path: exact.home, major: required, match: true }
|
||||||
const recJava = findJavaExeInHome(recommendedHome)
|
if (found.length > 0) {
|
||||||
if (recJava && getJavaMajor(recJava) >= required) {
|
const best = found.slice().sort((a, b) => b.major - a.major)[0]
|
||||||
return { found: true, path: homeFromJavaExe(recJava, recommendedHome), source: 'recommended', major: required }
|
return { found: true, path: best.home, major: best.major, match: false }
|
||||||
}
|
}
|
||||||
|
return { found: false, path: '', major: required, 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 }
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 사용자가 직접 입력/선택한 JDK 경로가 서버 실행에 충분한 버전인지 확인한다.
|
// 사용자가 직접 입력/선택한 JDK 경로의 자바 버전을 확인한다.
|
||||||
// ok=true → major >= 요구 버전(25).
|
// major=0 → 경로에서 java 를 못 찾거나 버전을 못 읽음(진행 차단용).
|
||||||
// major=0 → 경로에서 java 를 못 찾거나 버전을 못 읽음(실행 실패 등).
|
// match=true → 권장 버전과 정확히 일치.
|
||||||
ipcMain.handle('jdk:verify', async (_event, jdkPath: string, recommendedInput?: number): Promise<{ ok: boolean; major: number; required: number }> => {
|
// 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 required = normalizeRecommendedJdk(recommendedInput)
|
||||||
const javaExe = jdkPath ? findJavaExeInHome(jdkPath) : ''
|
const javaExe = jdkPath ? findJavaExeInHome(jdkPath) : ''
|
||||||
const major = javaExe ? getJavaMajor(javaExe) : 0
|
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 {
|
interface JdkInstallState {
|
||||||
controller: AbortController | null
|
controller: AbortController | null
|
||||||
destDir: string | null
|
destDir: string | null
|
||||||
@@ -423,10 +467,11 @@ ipcMain.handle('jdk:install', async (_event, recommendedInput?: number): Promise
|
|||||||
jdkInstall.inProgress = true
|
jdkInstall.inProgress = true
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
jdkInstall.controller = controller
|
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 })
|
await fsp.mkdir(tmpRoot, { recursive: true })
|
||||||
const tempZip = path.join(tmpRoot, `${jdkDirName(required)}-${Date.now()}.zip`)
|
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
|
jdkInstall.destDir = destDir
|
||||||
try {
|
try {
|
||||||
// Adoptium API v3: latest GA JDK(Windows x64). 본문은 307 로 GitHub 릴리즈로 리다이렉트.
|
// Adoptium API v3: latest GA JDK(Windows x64). 본문은 307 로 GitHub 릴리즈로 리다이렉트.
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ const api = {
|
|||||||
ipcRenderer.invoke('install:validatePath', target),
|
ipcRenderer.invoke('install:validatePath', target),
|
||||||
|
|
||||||
// 3-2
|
// 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),
|
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),
|
ipcRenderer.invoke('jdk:verify', jdkPath, recommendedJdk),
|
||||||
installJdk: (recommendedJdk?: number): Promise<{ ok: boolean; path?: string; message?: string }> =>
|
installJdk: (recommendedJdk?: number): Promise<{ ok: boolean; path?: string; message?: string }> =>
|
||||||
ipcRenderer.invoke('jdk:install', recommendedJdk),
|
ipcRenderer.invoke('jdk:install', recommendedJdk),
|
||||||
|
|||||||
Reference in New Issue
Block a user