@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 +19 -0
- package/MANUAL-INSTALACAO.md +29 -27
- package/README.md +40 -24
- package/app/main.py +15 -2
- package/app/services/database.py +92 -12
- package/package.json +1 -1
- package/scripts/cli.mjs +40 -18
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.**
|
package/MANUAL-INSTALACAO.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# Manual de
|
|
1
|
+
# Manual de Instala????o ??? ProdMaker
|
|
2
2
|
|
|
3
|
-
## Pr
|
|
3
|
+
## Pr??-requisitos
|
|
4
4
|
|
|
5
|
-
- **Node.js 20 ou superior**
|
|
6
|
-
- **Python 3.10 ou superior**
|
|
7
|
-
- Internet para download do pacote npm e das
|
|
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
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
49
|
-
no login, use qualquer e-mail + esse
|
|
50
|
-
|
|
51
|
-
Se a porta 8188 estiver ocupada, o launcher
|
|
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 **
|
|
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
|
-
##
|
|
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,
|
|
62
|
+
Verifica Node, Python, venv, depend??ncias, storage, backend e porta.
|
|
63
63
|
|
|
64
|
-
##
|
|
64
|
+
## Solu????o de problemas
|
|
65
65
|
|
|
66
|
-
| Problema |
|
|
66
|
+
| Problema | Solu????o |
|
|
67
67
|
|---|---|
|
|
68
|
-
| "Python
|
|
69
|
-
| Porta 8188 ocupada | O launcher migra para 8189
|
|
70
|
-
| `settings.json corrompido` | O sistema preserva a
|
|
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'
|
|
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`**
|
|
77
|
-
- O ProdMaker **nunca abre o navegador** (REGRA ZERO):
|
|
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
|
-
|
|
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
|
-
>
|
|
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
|
-
##
|
|
7
|
+
## Instala????o
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
# CMD/MobaXterm (Windows)
|
|
11
|
-
npx.cmd --yes --prefer-online @danhachuel/prodmaker@0.1.
|
|
12
|
-
npx.cmd --yes --prefer-online @danhachuel/prodmaker@0.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.
|
|
16
|
-
npx --yes @danhachuel/prodmaker@0.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
|
|
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**,
|
|
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
|
-
##
|
|
23
|
+
## Se o servidor fechar sozinho no Windows
|
|
24
24
|
|
|
25
|
-
|
|
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
|
|
31
|
-
| `/legal` (index, privacidade LGPD, termos) |
|
|
32
|
-
| Workflow de
|
|
33
|
-
| Frontend React + Vite + Tailwind (login + painel AdsX) |
|
|
34
|
-
| Seus Produtos (CRUD real:
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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)
|
package/app/services/database.py
CHANGED
|
@@ -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
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
package/scripts/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/* ProdMaker launcher
|
|
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.
|
|
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}
|
|
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)
|
|
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+
|
|
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
|
|
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
|
|
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("
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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}
|
|
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(`
|
|
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 || "
|
|
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
|
+
|