Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> git-subtree-dir: maquina-comercial-pessoal git-subtree-mainline:f5af038e19git-subtree-split:a9ef4d776d
234 lines
8.5 KiB
C#
234 lines
8.5 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|