feat: 환경에서 비 Windows 플랫폼의 SPT 설치 경로 확인 및 Git/LFS 설치 시뮬레이션 기능 추가

This commit is contained in:
이정수 2026-02-05 12:34:01 +09:00
parent c78159d442
commit ce6d43f597
1 changed files with 137 additions and 68 deletions

View File

@ -31,7 +31,7 @@ const CLIENT_PROCESS_NAMES = [
const SERVER_PROCESS_NAMES = [
"Aki.Server.exe",
"SPT.Server.exe"
"SPT.Server.exe"
];
const GAME_PROCESS_NAMES = [...CLIENT_PROCESS_NAMES, ...SERVER_PROCESS_NAMES];
@ -247,28 +247,28 @@ const checkGameRunning = async (): Promise<{ running: boolean; serverRunning: bo
resolve({ running: false, serverRunning: false, clientRunning: false, processes: [] });
return;
}
const output = stdout.toString().toLowerCase();
const runningProcesses: string[] = [];
let serverRunning = false;
let clientRunning = false;
for (const procName of GAME_PROCESS_NAMES) {
if (output.includes(procName.toLowerCase())) {
runningProcesses.push(procName);
if (SERVER_PROCESS_NAMES.includes(procName)) {
serverRunning = true;
} else if (CLIENT_PROCESS_NAMES.includes(procName)) {
clientRunning = true;
}
}
if (output.includes(procName.toLowerCase())) {
runningProcesses.push(procName);
if (SERVER_PROCESS_NAMES.includes(procName)) {
serverRunning = true;
} else if (CLIENT_PROCESS_NAMES.includes(procName)) {
clientRunning = true;
}
}
}
resolve({
running: runningProcesses.length > 0,
serverRunning,
clientRunning,
processes: runningProcesses
resolve({
running: runningProcesses.length > 0,
serverRunning,
clientRunning,
processes: runningProcesses
});
});
});
@ -670,7 +670,8 @@ const findSptInstallPath = async (
};
const resolveSptInstall = async (): Promise<SptInstallInfo> => {
if (process.platform !== "win32") {
const isDevUi = process.env.VITE_DEV_UI === "1";
if (process.platform !== "win32" && !isDevUi) {
return {
ok: false,
checkedAt: Date.now(),
@ -956,7 +957,7 @@ const syncDirectories = async (
await moveDir(sourcePath, destPath, preservePrefixes, targetDir);
}
}
// Final progress event for completion
const result = { ok: true, command: "copy", args: [] };
steps?.push({
@ -1007,11 +1008,79 @@ const runModSync = async (
const installGitTools = async (
onProgress?: (step: InstallStep) => void,
): Promise<InstallGitToolsResult> => {
const isDevUi = process.env.VITE_DEV_UI === "1";
if (process.platform !== "win32") {
if (!isDevUi) {
return {
ok: false,
error: "Windows에서만 자동 설치가 가능합니다.",
steps: [],
};
}
// Mac/Linux Dev UI Simulation
const steps: InstallStep[] = [];
const totalSteps = 3;
const emitProgress = (
name: string,
current: number,
status: InstallStep["status"],
) => {
const percent = Math.round((current / totalSteps) * 100);
onProgress?.({
name,
ok: status !== "error",
status,
progress: { current, total: totalSteps, percent },
result: { ok: true, command: "simulated", args: [] }
});
};
emitProgress("winget 확인", 1, "running");
await new Promise(r => setTimeout(r, 500));
const wingetStep: InstallStep = {
name: "winget 확인",
ok: true,
status: "done",
progress: { current: 1, total: totalSteps, percent: 33 },
result: { ok: true, command: "simulated", args: [] }
};
steps.push(wingetStep);
onProgress?.(wingetStep);
emitProgress("Git 설치", 2, "running");
await new Promise(r => setTimeout(r, 800));
const gitInstallStep: InstallStep = {
name: "Git 설치",
ok: true,
status: "done",
progress: { current: 2, total: totalSteps, percent: 67 },
result: { ok: true, command: "simulated", args: [] }
};
steps.push(gitInstallStep);
onProgress?.(gitInstallStep);
emitProgress("Git LFS 설치", 3, "running");
await new Promise(r => setTimeout(r, 800));
const lfsInstallStep: InstallStep = {
name: "Git LFS 설치",
ok: true,
status: "done",
progress: { current: 3, total: totalSteps, percent: 100 },
result: { ok: true, command: "simulated", args: [] }
};
steps.push(lfsInstallStep);
onProgress?.(lfsInstallStep);
return {
ok: false,
error: "Windows에서만 자동 설치가 가능합니다.",
steps: [],
ok: true,
steps,
check: {
git: { ok: true, command: "git", version: "simulated" },
lfs: { ok: true, command: "git", lfs: "simulated" } as any,
checkedAt: Date.now()
}
};
}
@ -1123,7 +1192,7 @@ const installGitTools = async (
"--accept-source-agreements",
"--accept-package-agreements",
"--silent",
"--disable-interactivity"
"--disable-interactivity"
],
INSTALL_TIMEOUT_MS,
);
@ -1428,7 +1497,7 @@ app.whenReady().then(() => {
ipcMain.handle("spt:launchGame", async (event) => {
const mainWindow = BrowserWindow.fromWebContents(event.sender);
const installInfo = await resolveSptInstall();
if (!installInfo.path) {
return { ok: false, error: "no_spt_path" };
}
@ -1436,13 +1505,13 @@ app.whenReady().then(() => {
// Double check running processes
const running = await checkGameRunning();
if (running.clientRunning) {
return { ok: false, error: "already_running", details: running.processes };
return { ok: false, error: "already_running", details: running.processes };
}
// Double check mod version (optional but safer)
const modStatus = await getModVersionStatus();
if (!modStatus.ok) {
return { ok: false, error: "mod_verification_failed" };
return { ok: false, error: "mod_verification_failed" };
}
// Launch Server if not running (Local only)
@ -1450,45 +1519,45 @@ app.whenReady().then(() => {
const isLocal = serverUrl.hostname === "127.0.0.1" || serverUrl.hostname === "localhost";
if (isLocal && !running.serverRunning) {
let serverExePath: string | undefined;
for (const exe of SERVER_PROCESS_NAMES) {
// Check direct path or SPT subdirectory
const direct = path.join(installInfo.path, exe);
if (await pathExists(direct)) {
serverExePath = direct;
break;
}
const nested = path.join(installInfo.path, "SPT", exe);
if (await pathExists(nested)) {
serverExePath = nested;
break;
}
let serverExePath: string | undefined;
for (const exe of SERVER_PROCESS_NAMES) {
// Check direct path or SPT subdirectory
const direct = path.join(installInfo.path, exe);
if (await pathExists(direct)) {
serverExePath = direct;
break;
}
if (serverExePath) {
console.log(`[spt-launcher] Starting Server: ${serverExePath}`);
const serverBat = stripExe(serverExePath) + ".bat"; // Sometimes users use bat, but exe should work if arguments aren't complex
// Prefer EXE for now.
const child = require("child_process").spawn(serverExePath, [], {
cwd: path.dirname(serverExePath),
detached: true,
stdio: "ignore"
});
child.unref();
// Wait for server health
let attempts = 0;
const maxAttempts = 30; // 30 seconds approx
while(attempts < maxAttempts) {
const health = await checkServerHealth();
if (health.ok) {
break;
}
await new Promise(r => setTimeout(r, 1000));
attempts++;
}
const nested = path.join(installInfo.path, "SPT", exe);
if (await pathExists(nested)) {
serverExePath = nested;
break;
}
}
if (serverExePath) {
console.log(`[spt-launcher] Starting Server: ${serverExePath}`);
const serverBat = stripExe(serverExePath) + ".bat"; // Sometimes users use bat, but exe should work if arguments aren't complex
// Prefer EXE for now.
const child = require("child_process").spawn(serverExePath, [], {
cwd: path.dirname(serverExePath),
detached: true,
stdio: "ignore"
});
child.unref();
// Wait for server health
let attempts = 0;
const maxAttempts = 30; // 30 seconds approx
while (attempts < maxAttempts) {
const health = await checkServerHealth();
if (health.ok) {
break;
}
await new Promise(r => setTimeout(r, 1000));
attempts++;
}
}
}
// Launch Client
@ -1500,7 +1569,7 @@ app.whenReady().then(() => {
"SPT/EscapeFromTarkov.exe",
"SPT/EscapeFromTarkov_BE.exe"
];
let executablePath: string | undefined;
for (const exe of candidates) {
const fullPath = path.join(installInfo.path, exe);
@ -1511,7 +1580,7 @@ app.whenReady().then(() => {
}
if (!executablePath) {
return { ok: false, error: "executable_not_found" };
return { ok: false, error: "executable_not_found" };
}
// Get Session ID for args
@ -1527,13 +1596,13 @@ app.whenReady().then(() => {
});
const args = [`-token=${session.sessionId}`, `-config=${configArg}`];
console.log(`[spt-launcher] Launching: ${executablePath} with args`, args);
// Sanitize Environment
// Strip Electron/Node specifics to prevent BepInEx/Plugin conflicts
const sanitizedEnv = { ...process.env };
for (const key of Object.keys(sanitizedEnv)) {
if (key.startsWith("ELECTRON_") || key.startsWith("NODE_") || key === "VITE_DEV_SERVER_URL") {
delete sanitizedEnv[key];
delete sanitizedEnv[key];
}
}
@ -1554,9 +1623,9 @@ app.whenReady().then(() => {
}
});
function stripExe(filename: string) {
function stripExe(filename: string) {
return filename.replace(/\.exe$/i, "");
}
}
ipcMain.handle("spt:checkGameRunning", async () => {
return await checkGameRunning();