@danhachuel/prodmaker 0.1.16 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,25 @@
3
3
  Todas as versões seguem a etiqueta visual `MAJOR.MINOR.PATCH` com patch de dois dígitos
4
4
  (o registry NPM normaliza `0.1.00` → `0.1.0`).
5
5
 
6
+ ## 0.1.17 — 2026-11-01
7
+
8
+ - **HOTFIX — servidor encerrando sozinho no Windows (CMD/MobaXterm/PowerShell).**
9
+ - Launcher: no Windows o processo Python é iniciado com `shell: true` além de
10
+ `stdio: 'inherit'`, mantendo o terminal anexado; nova mensagem explícita
11
+ "O servidor está rodando. Mantenha esta janela aberta. Pressione CTRL+C para encerrar.";
12
+ se o filho morrer em menos de 3 segundos com código ≠ 0, o launcher imprime o
13
+ traceback hint e o comando manual em vez de sair em silêncio; `child.on("error")`
14
+ também diagnosticado.
15
+ - **database.py à prova de crash:** `ensure_db()` recriado com try/except por
16
+ instrução (CREATE TABLE IF NOT EXISTS + ALTERs de migração) e recuperação de
17
+ arquivo SQLite corrompido — o banco ruim é movido para
18
+ `prodmaker.db.corrupt-AAAAMMDD-HHMMSS` e um novo é criado; o Uvicorn nunca cai
19
+ na inicialização por causa do banco.
20
+ - **Fallback manual:** `app/main.py` agora tem entry point (`python -m app.main`
21
+ sobe o uvicorn na porta 8188) para ver o traceback completo no terminal.
22
+ - README/MANual: seção "Se o servidor fechar sozinho" com o comando manual
23
+ `.\venv\Scripts\python.exe -m app.main` (na pasta do ProdMaker).
24
+
6
25
  ## 0.1.16 — 2026-10-31
7
26
 
8
27
  - **Fase 12 — Billing Stripe + E-mail Marketing (AdsAcademy) + Supabase multi-tenant pronto.**
@@ -1,10 +1,10 @@
1
- # Manual de Instalação — ProdMaker
1
+ # Manual de Instala????o ??? ProdMaker
2
2
 
3
- ## Pré-requisitos
3
+ ## Pr??-requisitos
4
4
 
5
- - **Node.js 20 ou superior** — https://nodejs.org
6
- - **Python 3.10 ou superior** — https://python.org (marcar "Add to PATH" no Windows)
7
- - Internet para download do pacote npm e das dependências
5
+ - **Node.js 20 ou superior** ??? https://nodejs.org
6
+ - **Python 3.10 ou superior** ??? https://python.org (marcar "Add to PATH" no Windows)
7
+ - Internet para download do pacote npm e das depend??ncias
8
8
 
9
9
  ## Instalar
10
10
 
@@ -20,7 +20,7 @@ npx --yes @danhachuel/prodmaker@0.1.1 install
20
20
 
21
21
  O que o `install` faz:
22
22
 
23
- 1. Cria a pasta de dados **PRODMAKER_HOME** (ordem de resolução: variável `PRODMAKER_HOME` → `C:\Users\danha\Documents\REPOSITORIO\ProdMaker` → `%USERPROFILE%\ProdMaker`).
23
+ 1. Cria a pasta de dados **PRODMAKER_HOME** (ordem de resolu????o: vari??vel `PRODMAKER_HOME` ??? `C:\Users\danha\Documents\REPOSITORIO\ProdMaker` ??? `%USERPROFILE%\ProdMaker`).
24
24
  2. Cria o storage local: `storage/{state,pages,produtos,artifacts,downloads,logs}` + `settings.json`.
25
25
  3. Cria um ambiente virtual Python (`venv`) dentro do PRODMAKER_HOME e instala o `requirements.txt`.
26
26
 
@@ -34,44 +34,46 @@ npx.cmd --yes --prefer-online @danhachuel/prodmaker@0.1.1
34
34
  npx --yes @danhachuel/prodmaker@0.1.1
35
35
  ```
36
36
 
37
- O terminal imprimirá:
37
+ O terminal imprimir??:
38
38
 
39
39
  ```
40
- ┌─────────────────────────────────────────────┐
41
- │ ProdMaker rodando em: │
42
- │ >>> http://localhost:8188 <<< │
43
- │ Abra esta URL MANUALMENTE no navegador. │
44
- └─────────────────────────────────────────────┘
40
+ ?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
41
+ ??? ProdMaker rodando em: ???
42
+ ??? >>> http://localhost:8188 <<< ???
43
+ ??? Abra esta URL MANUALMENTE no navegador. ???
44
+ ?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
45
45
  ```
46
46
 
47
47
  **Abra a URL manualmente.** O ProdMaker nunca abre o navegador sozinho (REGRA ZERO).
48
- O box de inicialização também imprime o **Código de Ativação Mestre** (default: `1234`) —
49
- no login, use qualquer e-mail + esse código (ou digite `admin` como código).
50
- Código customizado: variável de ambiente `PRODMAKER_MASTER_CODE`.
51
- Se a porta 8188 estiver ocupada, o launcher usará automaticamente 8189–8199 e informará.
48
+ O box de inicializa????o tamb??m imprime o **C??digo de Ativa????o Mestre** (default: `1234`) ???
49
+ no login, use qualquer e-mail + esse c??digo (ou digite `admin` como c??digo).
50
+ C??digo customizado: vari??vel de ambiente `PRODMAKER_MASTER_CODE`.
51
+ Se a porta 8188 estiver ocupada, o launcher usar?? automaticamente 8189???8199 e informar??.
52
52
 
53
- - Login: informe qualquer e-mail e o **Código de Ativação Mestre** impresso no terminal ao iniciar (default: `1234`; ou digite `admin`).
53
+ - Login: informe qualquer e-mail e o **C??digo de Ativa????o Mestre** impresso no terminal ao iniciar (default: `1234`; ou digite `admin`).
54
54
  - Para encerrar: `Ctrl+C` no terminal.
55
55
 
56
- ## Diagnóstico
56
+ ## Diagn??stico
57
57
 
58
58
  ```bash
59
59
  npx.cmd --yes @danhachuel/prodmaker@0.1.0 doctor
60
60
  ```
61
61
 
62
- Verifica Node, Python, venv, dependências, storage, backend e porta.
62
+ Verifica Node, Python, venv, depend??ncias, storage, backend e porta.
63
63
 
64
- ## Solução de problemas
64
+ ## Solu????o de problemas
65
65
 
66
- | Problema | Solução |
66
+ | Problema | Solu????o |
67
67
  |---|---|
68
- | "Python não encontrado" | Instale o Python 3.10+ e rode `prodmaker install` novamente. |
69
- | Porta 8188 ocupada | O launcher migra para 8189–8199 automaticamente; confira a URL impressa. |
70
- | `settings.json corrompido` | O sistema preserva a cópia em `storage/state/settings.corrupt.json` e não sobrescreve. Restaure manualmente. |
68
+ | "Python n??o encontrado" | Instale o Python 3.10+ e rode `prodmaker install` novamente. |
69
+ | Porta 8188 ocupada | O launcher migra para 8189???8199 automaticamente; confira a URL impressa. |
70
+ | `settings.json corrompido` | O sistema preserva a c??pia em `storage/state/settings.corrupt.json` e n??o sobrescreve. Restaure manualmente. |
71
71
  | Erro no Windows sobre scripts desabilitados | Use `npx.cmd` (CMD/MobaXterm) em vez do `npx` do PowerShell. |
72
- | `'prodmaker' não é reconhecido` ao rodar o npx | Execute os comandos npx **fora da pasta do repositório** (ex.: `%USERPROFILE%`). Dentro da pasta do projeto, o npx prioriza o pacote local (que não tem `node_modules/.bin`). Para desenvolvimento local use `node scripts\cli.mjs` na raiz do repositório. |
72
+ | `'prodmaker' n??o ?? reconhecido` ao rodar o npx | Execute os comandos npx **fora da pasta do reposit??rio** (ex.: `%USERPROFILE%`). Dentro da pasta do projeto, o npx prioriza o pacote local (que n??o tem `node_modules/.bin`). Para desenvolvimento local use `node scripts\cli.mjs` na raiz do reposit??rio. |
73
+ | **O servidor fecha sozinho / terminal volta ao prompt** | Rode manualmente para ver o traceback completo: `.\venv\Scripts\python.exe -m app.main` (dentro da pasta do ProdMaker). O launcher diagnostica encerramentos em menos de 3 segundos com essa mesma instru????o. Banco corrompido ?? recuperado automaticamente (backup `prodmaker.db.corrupt-*`). |
73
74
 
74
75
  ## Importante
75
76
 
76
- - **Nunca rode os comandos npx dentro de `C:\Users\danha\Documents\REPOSITORIO\ProdMaker`** — o npx entende que você quer executar o projeto local. Use qualquer outra pasta.
77
- - O ProdMaker **nunca abre o navegador** (REGRA ZERO): após iniciar, abra manualmente a URL impressa no terminal.
77
+ - **Nunca rode os comandos npx dentro de `C:\Users\danha\Documents\REPOSITORIO\ProdMaker`** ??? o npx entende que voc?? quer executar o projeto local. Use qualquer outra pasta.
78
+ - O ProdMaker **nunca abre o navegador** (REGRA ZERO): ap??s iniciar, abra manualmente a URL impressa no terminal.
79
+
package/README.md CHANGED
@@ -1,43 +1,59 @@
1
1
  # ProdMaker
2
2
 
3
- 🚀 **SaaS local para criação de infoprodutos low-ticket, landing pages, campanhas de tráfego e tracking** — baseado no sistema AdsX, executado no seu computador via `npx` e acessado manualmente no navegador.
3
+ ???? **SaaS local para cria????o de infoprodutos low-ticket, landing pages, campanhas de tr??fego e tracking** ??? baseado no sistema AdsX, executado no seu computador via `npx` e acessado manualmente no navegador.
4
4
 
5
- > ⚠️ **REGRA ZERO:** o ProdMaker **nunca abre o navegador**. Ao iniciar, ele apenas imprime a URL (`http://localhost:8188`) no terminal — você abre manualmente.
5
+ > ?????? **REGRA ZERO:** o ProdMaker **nunca abre o navegador**. Ao iniciar, ele apenas imprime a URL (`http://localhost:8188`) no terminal ??? voc?? abre manualmente.
6
6
 
7
- ## Instalação
7
+ ## Instala????o
8
8
 
9
9
  ```bash
10
- # CMD/MobaXterm (Windows)
11
- npx.cmd --yes --prefer-online @danhachuel/prodmaker@0.1.1 install
12
- npx.cmd --yes --prefer-online @danhachuel/prodmaker@0.1.1
10
+ # CMD/MobaXterm (Windows) ??? fora da pasta do reposit??rio
11
+ npx.cmd --yes --prefer-online @danhachuel/prodmaker@0.1.17 install
12
+ npx.cmd --yes --prefer-online @danhachuel/prodmaker@0.1.17
13
13
 
14
14
  # Linux/macOS/PowerShell
15
- npx --yes @danhachuel/prodmaker@0.1.1 install
16
- npx --yes @danhachuel/prodmaker@0.1.1
15
+ npx --yes @danhachuel/prodmaker@0.1.17 install
16
+ npx --yes @danhachuel/prodmaker@0.1.17
17
17
  ```
18
18
 
19
- 1. `install` cria a pasta de dados (`PRODMAKER_HOME`), o storage local e instala as dependências Python.
19
+ 1. `install` cria a pasta de dados (`PRODMAKER_HOME`), o storage local e instala as depend??ncias Python.
20
20
  2. O segundo comando inicia o servidor em `http://localhost:8188` e imprime a URL no terminal.
21
- 3. **Abra a URL manualmente no navegador**, faça login (qualquer e-mail + o **Código de Ativação Mestre** impresso no terminal — default `1234` — ou `admin`) e acesse o painel.
21
+ 3. **Abra a URL manualmente no navegador**, fa??a login (qualquer e-mail + o **C??digo de Ativa????o Mestre** impresso no terminal ??? default `1234` ??? ou `admin`) e acesse o painel.
22
22
 
23
- ## O que está incluído (0.1.01 — Parte 2: Frontend)
23
+ ## Se o servidor fechar sozinho no Windows
24
24
 
25
- | Área | Estado |
25
+ Se o terminal voltar para o prompt sem manter o servidor (ou travar), rode manualmente
26
+ para ver o traceback completo:
27
+
28
+ ```bash
29
+ # dentro da pasta do ProdMaker (PRODMAKER_HOME)
30
+ .\venv\Scripts\python.exe -m app.main
31
+ ```
32
+
33
+ O comando acima sobe o servidor na porta 8188 com o log completo no terminal. O launcher
34
+ tamb??m passou a diagnosticar encerramento imediato (menos de 3 segundos) imprimindo essa
35
+ mesma instru????o. Se o banco local estiver corrompido, o sistema faz backup autom??tico
36
+ (`prodmaker.db.corrupt-*`) e recria ??? o servidor n??o cai mais na inicializa????o.
37
+
38
+ ## O que est?? inclu??do (0.1.01 ??? Parte 2: Frontend)
39
+
40
+ | ??rea | Estado |
26
41
  |---|---|
27
- | Launcher npm (`install` / `run` / `doctor`) | ✅ |
28
- | Backend FastAPI (auth local, settings, health) | ✅ |
29
- | Storage persistente local (fora do cache npm) | ✅ |
30
- | Seeds de negócio (prompts, negativas, defaults, decisão, checklist) | ✅ |
31
- | `/legal` (index, privacidade LGPD, termos) | ✅ |
32
- | Workflow de publicação NPM via GitHub Actions | ✅ |
33
- | Frontend React + Vite + Tailwind (login + painel AdsX) | ✅ |
34
- | Seus Produtos (CRUD real: análise → aprovação) | ✅ |
35
- | Tema claro/escuro | ✅ |
42
+ | Launcher npm (`install` / `run` / `doctor`) | ??? |
43
+ | Backend FastAPI (auth local, settings, health) | ??? |
44
+ | Storage persistente local (fora do cache npm) | ??? |
45
+ | Seeds de neg??cio (prompts, negativas, defaults, decis??o, checklist) | ??? |
46
+ | `/legal` (index, privacidade LGPD, termos) | ??? |
47
+ | Workflow de publica????o NPM via GitHub Actions | ??? |
48
+ | Frontend React + Vite + Tailwind (login + painel AdsX) | ??? |
49
+ | Seus Produtos (CRUD real: an??lise ??? aprova????o) | ??? |
50
+ | Tema claro/escuro | ??? |
36
51
 
37
52
  ## Estrutura
38
53
 
39
- Consulte o [Planejamento Único](./PRODMAKER%20—%20PLANEJAMENTO%20ÚNICO%20E%20COMPLETO.md) para a visão completa (arquitetura, UI AdsX, módulos, fases e regras), o [Manual de Instalação](./MANUAL-INSTALACAO.md) e as [Regras do Repositório](./AGENTS.md).
54
+ Consulte o [Planejamento ??nico](./PRODMAKER%20???%20PLANEJAMENTO%20??NICO%20E%20COMPLETO.md) para a vis??o completa (arquitetura, UI AdsX, m??dulos, fases e regras), o [Manual de Instala????o](./MANUAL-INSTALACAO.md) e as [Regras do Reposit??rio](./AGENTS.md).
55
+
56
+ ## Licen??a
40
57
 
41
- ## Licença
58
+ MIT ??? ver [LICENSE](./LICENSE) e [THIRD-PARTY-NOTICES](./THIRD-PARTY-NOTICES.md).
42
59
 
43
- MIT — ver [LICENSE](./LICENSE) e [THIRD-PARTY-NOTICES](./THIRD-PARTY-NOTICES.md).
package/app/main.py CHANGED
@@ -15,7 +15,7 @@ from .services.database import ensure_db, get_db, now_str, row_to_dict
15
15
  from .services.llm import LLMError, analyze_product, list_providers
16
16
  from .services.store import PRODMAKER_HOME, STORAGE, STATE, ensure_state_dir, get_master_code, get_settings, save_settings
17
17
 
18
- APP_VERSION = "0.1.16"
18
+ APP_VERSION = "0.1.17"
19
19
  COOKIE_NAME = "prodmaker_session"
20
20
  SESSION_DAYS = 30
21
21
  MASTER_ALIAS = "admin"
@@ -272,7 +272,7 @@ def health():
272
272
 
273
273
  @app.get("/api/version")
274
274
  def version():
275
- return {"version": APP_VERSION, "canonical": "0.1.16"}
275
+ return {"version": APP_VERSION, "canonical": "0.1.17"}
276
276
 
277
277
 
278
278
  @app.get("/api/settings")
@@ -392,3 +392,16 @@ def spa_fallback(full_path: str):
392
392
  if full_path and candidate.is_file():
393
393
  return FileResponse(candidate)
394
394
  return _spa_index()
395
+
396
+
397
+ # ---------------------------------------------------------------------------
398
+ # Entry point manual — permite rodar sem o launcher (útil para ver tracebacks):
399
+ # .\venv\Scripts\python.exe app/main.py
400
+ # ---------------------------------------------------------------------------
401
+
402
+ if __name__ == "__main__":
403
+ import uvicorn
404
+
405
+ porta = int(os.environ.get("PRODMAKER_PORT") or 8188)
406
+ print(f"[ProdMaker] Iniciando em http://127.0.0.1:{porta} — mantenha esta janela aberta (CTRL+C para encerrar).")
407
+ uvicorn.run(app, host="127.0.0.1", port=porta)
@@ -3,6 +3,9 @@
3
3
  Esquema unico para os dois engines. Todas as tabelas carregam `user_id` (preparacao
4
4
  multi-tenant — hoje vazio). `ensure_db()` cria o que falta e aplica ALTERs de migracao.
5
5
  """
6
+ import sqlite3
7
+ import time
8
+
6
9
  from .dbengine import (
7
10
  add_column_if_missing,
8
11
  is_postgres,
@@ -273,19 +276,96 @@ def get_db():
273
276
  return connect()
274
277
 
275
278
 
276
- def ensure_db() -> None:
277
- with get_db() as conn:
278
- if is_postgres():
279
- for ddl in TABLES.values():
279
+ def _create_tables(conn) -> list[str]:
280
+ """Cria todas as tabelas; erros por instrução são coletados, nunca abortam."""
281
+ erros: list[str] = []
282
+ if is_postgres():
283
+ for nome, ddl in TABLES.items():
284
+ try:
280
285
  conn.execute(_pg_ddl(ddl))
281
- else:
282
- for ddl in TABLES.values():
283
- conn.execute(ddl)
284
- # Migracao: bancos criados antes da coluna user_id / da tabela investimentos
285
- for table in ALL_TABLES:
286
- add_column_if_missing(conn, table, "user_id TEXT NOT NULL DEFAULT ''", "user_id")
287
- conn.execute("CREATE INDEX IF NOT EXISTS idx_vendas_gclid ON vendas (gclid)")
288
- conn.execute("CREATE INDEX IF NOT EXISTS idx_visitas_gclid ON visitas (gclid)")
286
+ except Exception as exc: # noqa: BLE001
287
+ erros.append(f"{nome}: {exc}")
288
+ return erros
289
+ for nome, ddl in TABLES.items():
290
+ try:
291
+ conn.execute(ddl)
292
+ except sqlite3.DatabaseError as exc:
293
+ erros.append(f"{nome}: {exc}")
294
+ # Migracao: bancos criados antes da coluna user_id / das novas tabelas
295
+ for table in ALL_TABLES:
296
+ try:
297
+ add_column_if_missing(conn, table, "user_id TEXT NOT NULL DEFAULT ''", "user_id")
298
+ except Exception as exc: # noqa: BLE001
299
+ erros.append(f"user_id/{table}: {exc}")
300
+ for index in ("CREATE INDEX IF NOT EXISTS idx_vendas_gclid ON vendas (gclid)", "CREATE INDEX IF NOT EXISTS idx_visitas_gclid ON visitas (gclid)"):
301
+ try:
302
+ conn.execute(index)
303
+ except sqlite3.DatabaseError:
304
+ pass
305
+ return erros
306
+
307
+
308
+ def _sqlite_file():
309
+ from .dbengine import _sqlite_path
310
+
311
+ return _sqlite_path()
312
+
313
+
314
+ _CORRUPCAO_MARCADORES = ("not a database", "malformed", "encrypted", "corrupt", "database disk image")
315
+
316
+
317
+ def _parece_corrupcao(exc: Exception) -> bool:
318
+ texto = str(exc).lower()
319
+ return any(marcador in texto for marcador in _CORRUPCAO_MARCADORES)
320
+
321
+
322
+ def _recriar_banco() -> None:
323
+ arquivo = _sqlite_file()
324
+ backup = arquivo.with_name(f"prodmaker.db.corrupt-{time.strftime('%Y%m%d-%H%M%S')}")
325
+ try:
326
+ arquivo.rename(backup)
327
+ print(f"[ProdMaker] Banco SQLite corrompido movido para: {backup.name} — recriando vazio.")
328
+ except OSError:
329
+ try:
330
+ arquivo.unlink(missing_ok=True)
331
+ print("[ProdMaker] Banco SQLite corrompido removido — recriando vazio.")
332
+ except OSError:
333
+ print("[ProdMaker] Banco SQLite corrompido e não pode ser removido.")
334
+ return
335
+ with get_db() as conn:
336
+ _create_tables(conn)
337
+
338
+
339
+ def ensure_db() -> None:
340
+ """Cria/migra o banco. Se o arquivo SQLite estiver CORROMPIDO, faz backup e recria
341
+ (o Uvicorn nunca cai na inicialização por causa do banco)."""
342
+ if is_postgres():
343
+ with get_db() as conn:
344
+ _create_tables(conn)
345
+ return
346
+ conn = get_db()
347
+ try:
348
+ try:
349
+ _create_tables(conn)
350
+ # Probe de integridade: leitura real detecta arquivo corrompido.
351
+ conn.execute("SELECT COUNT(*) FROM produtos").fetchone()
352
+ return
353
+ except sqlite3.DatabaseError as exc:
354
+ if not _parece_corrupcao(exc):
355
+ # Erro de disco/permissão etc: não descarta o arquivo do usuário.
356
+ print(f"[ProdMaker] Aviso na inicialização do banco: {exc}")
357
+ return
358
+ corrompido = True
359
+ finally:
360
+ # IMPORTANTE (Windows): o context manager do sqlite3 NÃO fecha a conexão;
361
+ # sem close() o arquivo fica travado e o rename do recovery falha.
362
+ try:
363
+ conn.close()
364
+ except Exception: # noqa: BLE001
365
+ pass
366
+ if not corrompido:
367
+ return
368
+ _recriar_banco()
289
369
 
290
370
 
291
371
  ensure_db()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/prodmaker",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "ProdMaker — SaaS local para criação de infoprodutos, landing pages, campanhas e tracking (base AdsX).",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/scripts/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- /* ProdMaker launcher — install | run | doctor
2
+ /* ProdMaker launcher ??? install | run | doctor
3
3
  * REGRA ZERO: este launcher NUNCA abre o navegador. Apenas imprime a URL.
4
4
  */
5
5
  import { spawn, spawnSync } from "node:child_process";
@@ -14,7 +14,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
14
  const PKG_ROOT = path.resolve(__dirname, "..");
15
15
  const APP_DIR = path.join(PKG_ROOT, "app");
16
16
  const REQUIREMENTS = path.join(PKG_ROOT, "requirements.txt");
17
- const APP_VERSION = "0.1.16";
17
+ const APP_VERSION = "0.1.17";
18
18
  const DEFAULT_PORT = 8188;
19
19
 
20
20
  const STORAGE_FOLDERS = ["state", "pages", "produtos", "artifacts", "downloads", "logs"];
@@ -117,17 +117,17 @@ function runCommand(cmd, args, options = {}) {
117
117
 
118
118
  async function commandInstall(flags) {
119
119
  const home = resolveHome();
120
- console.log(`ProdMaker ${APP_VERSION} — install`);
120
+ console.log(`ProdMaker ${APP_VERSION} ??? install`);
121
121
  console.log(`Pasta de dados (PRODMAKER_HOME): ${home}`);
122
122
  const settingsPath = ensureInstallSkeleton(home);
123
123
  console.log(`Storage pronto. Settings: ${settingsPath}`);
124
124
 
125
125
  if (flags.skipPythonDeps) {
126
- console.log(" (--skip-python-deps) Dependências Python ignoradas.");
126
+ console.log(" (--skip-python-deps) Depend??ncias Python ignoradas.");
127
127
  } else {
128
128
  const base = findSystemPython();
129
129
  if (!base) {
130
- console.error("Python 3.10+ não encontrado. Instale o Python e rode 'prodmaker install' novamente.");
130
+ console.error("Python 3.10+ n??o encontrado. Instale o Python e rode 'prodmaker install' novamente.");
131
131
  process.exit(1);
132
132
  }
133
133
  const venvDir = path.join(home, "venv");
@@ -141,17 +141,17 @@ async function commandInstall(flags) {
141
141
  }
142
142
  }
143
143
  const python = venvPython(home);
144
- console.log("Instalando dependências Python (requirements.txt)...");
144
+ console.log("Instalando depend??ncias Python (requirements.txt)...");
145
145
  const code = await runCommand(python, ["-m", "pip", "install", "--upgrade", "pip"]);
146
146
  const code2 = await runCommand(python, ["-m", "pip", "install", "-r", REQUIREMENTS]);
147
147
  if (code2 !== 0) {
148
- console.error("Falha ao instalar dependências Python.");
148
+ console.error("Falha ao instalar depend??ncias Python.");
149
149
  process.exit(1);
150
150
  }
151
151
  }
152
152
 
153
153
  console.log("");
154
- console.log("Instalação concluída. Para iniciar:");
154
+ console.log("Instala????o conclu??da. Para iniciar:");
155
155
  console.log(" npx --yes @danhachuel/prodmaker");
156
156
  console.log("Depois abra http://localhost:8188 MANUALMENTE no navegador (o sistema nunca abre o navegador).");
157
157
  }
@@ -175,7 +175,7 @@ async function commandRun() {
175
175
  const home = resolveHome();
176
176
  const settingsPath = storagePath(home, "state", "settings.json");
177
177
  if (!fs.existsSync(settingsPath)) {
178
- console.error("ProdMaker ainda não está instalado. Rode primeiro: prodmaker install");
178
+ console.error("ProdMaker ainda n??o est?? instalado. Rode primeiro: prodmaker install");
179
179
  process.exit(1);
180
180
  }
181
181
  let preferred = DEFAULT_PORT;
@@ -186,20 +186,20 @@ async function commandRun() {
186
186
 
187
187
  const python = venvPython(home) || findSystemPython();
188
188
  if (!python) {
189
- console.error("Python não encontrado. Rode: prodmaker install");
189
+ console.error("Python n??o encontrado. Rode: prodmaker install");
190
190
  process.exit(1);
191
191
  }
192
192
  if (!fs.existsSync(path.join(APP_DIR, "main.py"))) {
193
- console.error("Backend não encontrado (app/main.py).");
193
+ console.error("Backend n??o encontrado (app/main.py).");
194
194
  process.exit(1);
195
195
  }
196
196
 
197
197
  const port = await pickPort(preferred);
198
198
  if (!port) {
199
- console.error("Nenhuma porta disponível entre " + preferred + " e " + (preferred + 11) + ".");
199
+ console.error("Nenhuma porta dispon??vel entre " + preferred + " e " + (preferred + 11) + ".");
200
200
  process.exit(1);
201
201
  }
202
- if (port !== preferred) console.log(`Porta ${preferred} ocupada — usando ${port}.`);
202
+ if (port !== preferred) console.log(`Porta ${preferred} ocupada ??? usando ${port}.`);
203
203
 
204
204
  const pythonCmd = venvPython(home) ? python : python;
205
205
  const pythonArgs = venvPython(home)
@@ -211,28 +211,49 @@ async function commandRun() {
211
211
  console.log("Iniciando ProdMaker...");
212
212
  const masterCode = await fetchMasterCode(pythonCmd, home);
213
213
  printUrlBox(port, masterCode);
214
+ console.log("O servidor est?? rodando. Mantenha esta janela aberta. Pressione CTRL+C para encerrar.");
215
+ console.log("");
214
216
 
215
217
  const env = { ...process.env, PRODMAKER_HOME: home, PRODMAKER_PORT: String(port) };
218
+ // No Windows usamos shell:true para o terminal ficar anexado ao processo
219
+ // (comportamento correto no CMD/MobaXterm/PowerShell).
220
+ const useShell = process.platform === "win32";
216
221
  const child = spawn(pythonCmd, pythonArgs, {
217
222
  cwd: PKG_ROOT,
218
223
  stdio: "inherit",
219
224
  env,
220
- shell: false,
225
+ shell: useShell,
221
226
  });
227
+ const started = Date.now();
222
228
  const stop = () => {
223
229
  child.kill();
224
230
  process.exit(0);
225
231
  };
226
232
  process.on("SIGINT", stop);
227
233
  process.on("SIGTERM", stop);
234
+ child.on("error", (error) => {
235
+ console.error(`Falha ao iniciar o Python (${error.message}).`);
236
+ console.error("Rode manualmente para ver o erro completo:");
237
+ console.error(` ${pythonCmd} -m uvicorn app.main:app --host 127.0.0.1 --port ${port}`);
238
+ process.exit(1);
239
+ });
228
240
  child.on("close", (code) => {
229
- console.log(`ProdMaker encerrou (código ${code ?? 0}).`);
241
+ if (Date.now() - started < 3000 && code !== 0) {
242
+ console.error(`O servidor encerrou imediatamente (c??digo ${code ?? 0}). Poss??vel erro de banco ou depend??ncia.`);
243
+ console.error("Para ver o traceback completo, rode manualmente (dentro da pasta do ProdMaker):");
244
+ console.error(` cd ${home} && .\\venv\\Scripts\\python.exe -m app.main`);
245
+ console.error(" (ou .\\venv\\Scripts\\python.exe -m uvicorn app.main:app --host 127.0.0.1 --port " + port + ")");
246
+ process.exit(code ?? 1);
247
+ }
248
+ console.log(`ProdMaker encerrou (c??digo ${code ?? 0}).`);
230
249
  process.exit(code ?? 0);
231
250
  });
251
+ // Mant??m o processo vivo enquanto o filho estiver rodando
252
+ await new Promise((resolve) => child.on("close", resolve));
232
253
  }
233
254
 
234
255
  async function commandDoctor() {
235
- console.log(`ProdMaker ${APP_VERSION} — doctor`);
256
+ console.log(`ProdMaker ${APP_VERSION} ??? doctor`);
236
257
  console.log(`Node: ${process.version}`);
237
258
  const home = resolveHome();
238
259
  console.log(`PRODMAKER_HOME: ${home}`);
@@ -241,10 +262,10 @@ async function commandDoctor() {
241
262
  console.log(`Venv: ${venv || "ausente"}`);
242
263
  if (venv) {
243
264
  const check = spawnSync(venv, ["-c", "import fastapi, uvicorn"], { encoding: "utf8" });
244
- console.log(`Dependências Python (fastapi/uvicorn): ${check.status === 0 ? "OK" : "FALTANDO (rode install)"}`);
265
+ console.log(`Depend??ncias Python (fastapi/uvicorn): ${check.status === 0 ? "OK" : "FALTANDO (rode install)"}`);
245
266
  } else {
246
267
  const base = findSystemPython();
247
- console.log(`Python do sistema: ${base || "não encontrado"}`);
268
+ console.log(`Python do sistema: ${base || "n??o encontrado"}`);
248
269
  }
249
270
  console.log(`Backend (app/main.py): ${fs.existsSync(path.join(APP_DIR, "main.py")) ? "OK" : "FALTANDO"}`);
250
271
  const dist = path.join(PKG_ROOT, "frontend", "dist", "index.html");
@@ -268,3 +289,4 @@ main().catch((error) => {
268
289
  console.error(error);
269
290
  process.exit(1);
270
291
  });
292
+