using System; using System.Diagnostics; using System.IO; using System.Text.Json; using System.Threading.Tasks; namespace ElectronizerWPF { public class ElectronizerLogic { public event Action OnLog; private void Log(string message) { OnLog?.Invoke(message); } public async Task RunConversionAsync(string inputPath, string outputDir, bool skipBuild, bool buildExe) { try { Log($"Starting conversion for: {inputPath}"); if (skipBuild) Log("Skipping build steps (using existing output)..."); // 1. Verify Input string packageJsonPath = Path.Combine(inputPath, "package.json"); if (!File.Exists(packageJsonPath)) { throw new Exception("package.json not found in input directory."); } // Read input package.json for name string jsonContent = await File.ReadAllTextAsync(packageJsonPath); using JsonDocument doc = JsonDocument.Parse(jsonContent); string projectName = "app"; if(doc.RootElement.TryGetProperty("name", out var nameElement)) { projectName = nameElement.GetString() ?? "app"; } string author = "Electronizer"; if(doc.RootElement.TryGetProperty("author", out var authorElement)) { author = authorElement.ToString(); } if (!skipBuild) { // 2. NPM Install Log("Running npm install..."); await RunCommandAsync(inputPath, "npm install"); // 3. NPM Build Log("Running npm run build..."); await RunCommandAsync(inputPath, "npm run build"); } // Verify build output string distPath = null; string[] candidates = { "dist", "dist-react", "build" }; foreach (var candidate in candidates) { string candidatePath = Path.Combine(inputPath, candidate); if (Directory.Exists(candidatePath)) { distPath = candidatePath; Log($"Found build output directory: {candidate}"); break; } } if (distPath == null) { throw new Exception("Could not find build output. Checked: dist, dist-react, build."); } // 4. Scaffold string electronAppName = $"{projectName}-electron"; // If outputDir is just a root folder (like Desktop), append app name. // However, user selected specific output folder. Let's assume outputDir IS the target folder. if (Directory.Exists(outputDir)) { Log($"Cleaning output directory: {outputDir}"); try { Directory.Delete(outputDir, true); } catch { /* best effort */ } } Directory.CreateDirectory(outputDir); // Copy Dist Log("Copying dist folder..."); string targetDist = Path.Combine(outputDir, "dist"); CopyDirectory(distPath, targetDist, true); // 5. Generate package.json Log("Generating package.json..."); var newPkg = new { name = electronAppName, version = "1.0.0", description = $"Electron wrapper for {projectName}", main = "main.cjs", author = author, scripts = new { start = "electron .", build = "electron-builder --win" }, build = new { appId = $"com.electronizer.{projectName}", productName = projectName, directories = new { output = "release" }, files = new[] { "dist/**/*", "main.cjs", "package.json" }, win = new { target = "nsis" } }, devDependencies = new { electron = "latest", @electron_builder = "latest" // Handle hyphen with standard json serialization options or string replacement if needed } }; // Fix for electron-builder key which works in anonymous object but property name logic needed // Using Dictionary for safer key names var pkgDict = new System.Collections.Generic.Dictionary { ["name"] = electronAppName, ["version"] = "1.0.0", ["description"] = $"Electron wrapper for {projectName}", ["main"] = "main.cjs", ["author"] = author, ["scripts"] = new { start = "electron .", build = "electron-builder --win" }, ["build"] = new { appId = $"com.electronizer.{projectName}", productName = projectName, directories = new { output = "release" }, files = new[] { "dist/**/*", "main.cjs", "package.json" }, win = new { target = "nsis" } }, ["devDependencies"] = new Dictionary { ["electron"] = "latest", ["electron-builder"] = "latest" } }; // Serialize var options = new JsonSerializerOptions { WriteIndented = true }; string pkgJsonString = JsonSerializer.Serialize(pkgDict, options); await File.WriteAllTextAsync(Path.Combine(outputDir, "package.json"), pkgJsonString); // 6. Generate main.cjs Log("Generating main.cjs..."); string mainCjsContent = @" const { app, BrowserWindow } = require('electron'); const path = require('path'); function createWindow() { const win = new BrowserWindow({ width: 1200, height: 800, webPreferences: { nodeIntegration: false, contextIsolation: true } }); win.loadFile(path.join(__dirname, 'dist', 'index.html')); } app.whenReady().then(() => { createWindow(); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); }); "; await File.WriteAllTextAsync(Path.Combine(outputDir, "main.cjs"), mainCjsContent.Trim()); // 7. Auto-Build Exe (Optional) if (buildExe) { Log("\n--- Final Step: Building Windows Exe ---"); Log("Installing Electron dependencies (this may take a minute)..."); await RunCommandAsync(outputDir, "npm install"); Log("Running Electron Builder..."); await RunCommandAsync(outputDir, "npm run build"); Log($"\nSUCCESS! Exe should be in: {Path.Combine(outputDir, "release")}"); } else { Log("--- Success ---"); Log($"Electron app created at: {outputDir}"); Log("Run 'npm install' then 'npm start' or 'npm run build' inside that folder."); } } catch (Exception ex) { Log($"ERROR: {ex.Message}"); throw; } } private async Task RunCommandAsync(string workingDir, string command) { var tcs = new TaskCompletionSource(); var psi = new ProcessStartInfo("cmd.exe", $"/c {command}") { WorkingDirectory = workingDir, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using var process = new Process { StartInfo = psi }; process.OutputDataReceived += (s, e) => { if (e.Data != null) Log(e.Data); }; process.ErrorDataReceived += (s, e) => { if (e.Data != null) Log($"ERR: {e.Data}"); }; process.EnableRaisingEvents = true; process.Exited += (s, e) => { if (process.ExitCode == 0) tcs.SetResult(true); else tcs.SetException(new Exception($"Command '{command}' failed with exit code {process.ExitCode}")); }; process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); await tcs.Task; } private static void CopyDirectory(string sourceDir, string destinationDir, bool recursive) { var dir = new DirectoryInfo(sourceDir); if (!dir.Exists) throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}"); DirectoryInfo[] dirs = dir.GetDirectories(); Directory.CreateDirectory(destinationDir); foreach (FileInfo file in dir.GetFiles()) { string targetFilePath = Path.Combine(destinationDir, file.Name); file.CopyTo(targetFilePath, true); } if (recursive) { foreach (DirectoryInfo subDir in dirs) { string newDestinationDir = Path.Combine(destinationDir, subDir.Name); CopyDirectory(subDir.FullName, newDestinationDir, true); } } } } }