using System; using System.Diagnostics; using System.IO; using System.Net; using System.Threading; // Launcher da Maquina Comercial Pessoal: // 1. localiza a pasta do projeto (procura package.json + scripts/servidor.mjs); // 2. se o servidor ja responde em http://127.0.0.1:3910, so abre o navegador; // 3. senao, inicia "node scripts/servidor.mjs" escondido (log em data/server.log), // espera o /api/health responder (ate 30s) e abre o navegador. // // Compilavel tanto pelo dotnet SDK (net8.0) quanto pelo csc.exe do // .NET Framework 4 (C# 5): sem interpolacao de string, sem APIs novas. namespace MaquinaComercialPessoalLauncher { internal static class Program { private static void LoadDotEnv(string root) { string envPath = Path.Combine(root, ".env"); if (!File.Exists(envPath)) return; foreach (string rawLine in File.ReadAllLines(envPath)) { string line = rawLine.Trim(); if (line.Length == 0 || line.StartsWith("#")) continue; int separator = line.IndexOf('='); if (separator <= 0) continue; string key = line.Substring(0, separator).Trim(); string value = line.Substring(separator + 1).Trim(); if (key.Length > 0 && Environment.GetEnvironmentVariable(key) == null) { Environment.SetEnvironmentVariable(key, value); } } } private static string BaseUrl(string root) { LoadDotEnv(root); string port = Environment.GetEnvironmentVariable("APP_PORT"); int parsedPort; if (!int.TryParse(port, out parsedPort) || parsedPort < 1 || parsedPort > 65535) { port = "3910"; } else { port = parsedPort.ToString(); } return "http://127.0.0.1:" + port; } private static int Main() { Console.Title = "Maquina Comercial Pessoal"; Console.WriteLine("=== Maquina Comercial Pessoal ==="); string root = LocateProjectDir(); if (root == null) { return Fail("Pasta do projeto nao encontrada (procurei package.json + scripts/servidor.mjs).\n" + "Deixe o Maquina-Comercial-Pessoal.exe dentro da pasta do projeto."); } string url = BaseUrl(root); if (IsHealthy(url)) { Console.WriteLine("Servidor ja esta rodando. Abrindo o navegador..."); OpenBrowser(url); return 0; } string node = FindNode(); if (node == null) { return Fail("Node.js nao encontrado no PATH. Instale em https://nodejs.org (versao 24 ou superior)."); } Console.WriteLine("Iniciando o servidor (aguarde alguns segundos)..."); if (!StartServer(node, root)) { return Fail("Nao foi possivel iniciar o servidor. Veja o log em data\\server.log"); } for (int i = 0; i < 60; i++) { Thread.Sleep(500); if (IsHealthy(url)) { Console.WriteLine("Pronto! Abrindo " + url + " no navegador..."); OpenBrowser(url); Thread.Sleep(1500); return 0; } } return Fail("O servidor nao respondeu em 30 segundos.\n" + "Veja o log em data\\server.log ou rode manualmente: node scripts\\servidor.mjs"); } private static string LocateProjectDir() { string[] candidates = new string[] { AppDomain.CurrentDomain.BaseDirectory, Directory.GetCurrentDirectory() }; foreach (string start in candidates) { DirectoryInfo dir = new DirectoryInfo(start); for (int depth = 0; dir != null && depth < 5; depth++) { if (File.Exists(Path.Combine(dir.FullName, "package.json")) && File.Exists(Path.Combine(Path.Combine(dir.FullName, "scripts"), "servidor.mjs"))) { return dir.FullName; } dir = dir.Parent; } } return null; } private static string FindNode() { try { ProcessStartInfo psi = new ProcessStartInfo("where.exe", "node"); psi.UseShellExecute = false; psi.RedirectStandardOutput = true; psi.CreateNoWindow = true; using (Process p = Process.Start(psi)) { string output = p.StandardOutput.ReadToEnd(); p.WaitForExit(5000); if (!string.IsNullOrEmpty(output)) { string first = output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)[0].Trim(); if (File.Exists(first)) return first; } } } catch (Exception) { // continua para o retorno nulo } return null; } private static bool StartServer(string node, string root) { try { string dataDir = Path.Combine(root, "data"); Directory.CreateDirectory(dataDir); string logPath = Path.Combine(dataDir, "server.log"); File.AppendAllText(logPath, "---- " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " launcher iniciou o servidor ----\r\n"); // O redirecionamento para o log e feito pelo proprio cmd.exe: assim o // launcher pode encerrar sem deixar o servidor escrevendo num pipe // sem leitor (o que derrubaria o node com EPIPE mais tarde). string comando = "/c \"\"" + node + "\" scripts\\servidor.mjs >> \"" + logPath + "\" 2>&1\""; ProcessStartInfo psi = new ProcessStartInfo("cmd.exe", comando); psi.WorkingDirectory = root; psi.UseShellExecute = false; psi.CreateNoWindow = true; Process.Start(psi); return true; } catch (Exception ex) { Console.WriteLine("Falha ao iniciar: " + ex.Message); return false; } } private static bool IsHealthy(string baseUrl) { try { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(baseUrl + "/api/health"); request.Timeout = 1500; request.Method = "GET"; using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { if ((int)response.StatusCode != 200) return false; using (StreamReader reader = new StreamReader(response.GetResponseStream())) { string body = reader.ReadToEnd(); string compact = body.Replace(" ", "").Replace("\r", "").Replace("\n", "").Replace("\t", ""); return (int)response.StatusCode == 200 && compact.IndexOf("\"app\":\"maquina-comercial-pessoal\"", StringComparison.Ordinal) >= 0; } } } catch (Exception) { return false; } } private static void OpenBrowser(string url) { try { ProcessStartInfo psi = new ProcessStartInfo(url); psi.UseShellExecute = true; Process.Start(psi); } catch (Exception) { Console.WriteLine("Abra manualmente no navegador: " + url); } } private static int Fail(string message) { Console.WriteLine(); Console.WriteLine("ERRO: " + message); Console.WriteLine(); Console.WriteLine("Pressione qualquer tecla para fechar..."); try { Console.ReadKey(true); } catch (Exception) { Thread.Sleep(8000); } return 1; } } }