@danhachuel/pandanews 0.4.10 → 0.5.0
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 +8 -0
- package/backend/__pycache__/__init__.cpython-314.pyc +0 -0
- package/backend/app/main.py +3 -2
- package/backend/app/routers/noticias.py +42 -1
- package/backend/app/routers/wordpress.py +23 -0
- package/backend/app/schemas.py +3 -0
- package/backend/core/__pycache__/__init__.cpython-314.pyc +0 -0
- package/backend/core/__pycache__/config.cpython-314.pyc +0 -0
- package/backend/core/__pycache__/llm_router.cpython-314.pyc +0 -0
- package/backend/core/database.py +2 -0
- package/backend/core/pipeline.py +6 -2
- package/backend/core/wordpress_publisher.py +167 -0
- package/frontend/dist/assets/{index-ExoYg5_I.css → index-CM6InlUs.css} +1 -1
- package/frontend/dist/assets/index-Dj6FuR5_.js +70 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/scripts/start.js +110 -10
- package/frontend/dist/assets/index-Iz5spM-E.js +0 -70
package/frontend/dist/index.html
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-Dj6FuR5_.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CM6InlUs.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="root"></div>
|
package/package.json
CHANGED
package/scripts/start.js
CHANGED
|
@@ -39,6 +39,53 @@ function venvBin(home) {
|
|
|
39
39
|
: join(home, '.runtime', 'venv', 'bin', 'python');
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// ------------------------------------------------------------------------
|
|
43
|
+
// JANELAS DE CONSOLE NO WINDOWS — porque subimos o python BASE e não o venv
|
|
44
|
+
//
|
|
45
|
+
// Medido neste repositório (spawn com detached + windowsHide, stdio no
|
|
46
|
+
// arquivo): `venv/Scripts/python.exe` das distribuições python-build-
|
|
47
|
+
// standalone é um REDIRECIONADOR — ele re-executa o interpretador real
|
|
48
|
+
// (`runtime/python/python.exe`) e esse NETO nasce SEM as flags de
|
|
49
|
+
// ocultação do pai. O Windows então aloca um console NOVO para o neto:
|
|
50
|
+
// 1 processo filho + 1 hospedeiro de console (conhost/WindowsTerminal)
|
|
51
|
+
// novos, ou seja, uma janela de terminal na cara do usuário.
|
|
52
|
+
//
|
|
53
|
+
// Subindo o interpretador base diretamente, com o site-packages do venv no
|
|
54
|
+
// PYTHONPATH, temos um ÚNICO processo: 0 filhos, 0 console novo. As
|
|
55
|
+
// dependências são exatamente as mesmas (verificado: import do app inteiro
|
|
56
|
+
// com o python base + PYTHONPATH).
|
|
57
|
+
//
|
|
58
|
+
// No Unix o `venv/bin/python` é um symlink para o base — sem redirector,
|
|
59
|
+
// sem neto —, então lá seguimos usando o venv normalmente.
|
|
60
|
+
// ------------------------------------------------------------------------
|
|
61
|
+
function baseBin(home) {
|
|
62
|
+
return process.platform === 'win32'
|
|
63
|
+
? join(home, '.runtime', 'python', 'python.exe')
|
|
64
|
+
: null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function venvSitePackages(home) {
|
|
68
|
+
return join(home, '.runtime', 'venv', 'Lib', 'site-packages');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function pythonBin(home) {
|
|
72
|
+
if (process.platform === 'win32') {
|
|
73
|
+
const base = baseBin(home);
|
|
74
|
+
if (base && existsSync(base)) return base;
|
|
75
|
+
}
|
|
76
|
+
return venvBin(home);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function pythonEnv(home) {
|
|
80
|
+
const env = { ...process.env, PANDANEWS_HOME: home, PYTHONUNBUFFERED: '1' };
|
|
81
|
+
if (process.platform === 'win32') {
|
|
82
|
+
// python base só enxerga as dependências do venv via PYTHONPATH
|
|
83
|
+
const site = venvSitePackages(home);
|
|
84
|
+
if (existsSync(site)) env.PYTHONPATH = site;
|
|
85
|
+
}
|
|
86
|
+
return env;
|
|
87
|
+
}
|
|
88
|
+
|
|
42
89
|
const MIME = {
|
|
43
90
|
'.html': 'text/html; charset=utf-8',
|
|
44
91
|
'.js': 'text/javascript; charset=utf-8',
|
|
@@ -84,10 +131,16 @@ async function findFreePort(base, maxTries) {
|
|
|
84
131
|
}
|
|
85
132
|
|
|
86
133
|
// Encerra o processo por PID e (no Windows) a árvore de processos /T.
|
|
134
|
+
// Corre SEMPRE silenciosamente: windowsHide + stdio ignore — um taskkill
|
|
135
|
+
// que piscasse uma janela de console negaria a regra permanente do projeto.
|
|
87
136
|
function encerrarPorPid(pid, motivo) {
|
|
88
137
|
try {
|
|
89
138
|
if (process.platform === 'win32') {
|
|
90
|
-
spawnSync('taskkill', ['/pid', String(pid), '/f', '/t'], {
|
|
139
|
+
spawnSync('taskkill', ['/pid', String(pid), '/f', '/t'], {
|
|
140
|
+
windowsHide: true, // CREATE_NO_WINDOW — sem tela preta
|
|
141
|
+
detached: true, // grupo isolado, imune a Ctrl+C do console
|
|
142
|
+
stdio: 'ignore', // taskkill não escreve no terminal do launcher
|
|
143
|
+
});
|
|
91
144
|
} else {
|
|
92
145
|
process.kill(pid, 'SIGTERM');
|
|
93
146
|
}
|
|
@@ -223,6 +276,8 @@ async function esperarBackend(port) {
|
|
|
223
276
|
|
|
224
277
|
// Sobe o uvicorn em grupo de processos próprio: detached (novo grupo no
|
|
225
278
|
// Windows), windowsHide (sem janela preta) e stdio direto no arquivo de log.
|
|
279
|
+
// No Windows sobe o interpretador BASE + PYTHONPATH do venv — um único
|
|
280
|
+
// processo, sem o redirector que abria a janela de console (ver pythonBin).
|
|
226
281
|
async function startBackend(home) {
|
|
227
282
|
const port = await findFreePort(API_PORT_BASE, PORT_MAX_TRIES);
|
|
228
283
|
if (port !== API_PORT_BASE) {
|
|
@@ -238,14 +293,14 @@ async function startBackend(home) {
|
|
|
238
293
|
|
|
239
294
|
console.log(`Iniciando backend FastAPI em http://127.0.0.1:${port} ... (log: ${logPath})`);
|
|
240
295
|
const python = spawn(
|
|
241
|
-
|
|
296
|
+
pythonBin(home),
|
|
242
297
|
['-m', 'uvicorn', 'backend.app.main:app', '--host', '127.0.0.1', '--port', String(port)],
|
|
243
298
|
{
|
|
244
299
|
cwd: PKG_ROOT,
|
|
245
300
|
detached: true, // CREATE_NEW_PROCESS_GROUP no Windows — imune a Ctrl+C do console
|
|
246
301
|
windowsHide: true, // sem janela de console fantasma
|
|
247
302
|
stdio: ['ignore', logFd, logFd], // saída direto no arquivo (append) — não herda console
|
|
248
|
-
env:
|
|
303
|
+
env: pythonEnv(home),
|
|
249
304
|
},
|
|
250
305
|
);
|
|
251
306
|
closeSync(logFd); // o filho tem sua própria cópia herdada
|
|
@@ -269,22 +324,67 @@ async function startBackend(home) {
|
|
|
269
324
|
}
|
|
270
325
|
|
|
271
326
|
// Watchdog de saúde: spawn destacado não emite eventos de exit confiáveis —
|
|
272
|
-
// a porta é polidada a cada 10s
|
|
327
|
+
// a porta é polidada a cada 10s. FALHAS_PARA_REINICIAR leituras ruins
|
|
328
|
+
// consecutivas disparam um reinício, mas no MÁXIMO MAX_REINICIOS vezes:
|
|
329
|
+
// sem essa trava, um backend que não consegue subir de jeito nenhum (porta
|
|
330
|
+
// tomada por órfão, dependência quebrada) viraria um loop infinito de spawns
|
|
331
|
+
// — e cada spawn no Windows é candidato a uma janela de console.
|
|
332
|
+
const FALHAS_PARA_REINICIAR = 3;
|
|
333
|
+
const MAX_REINICIOS = 3;
|
|
334
|
+
|
|
273
335
|
let falhasSaude = 0;
|
|
336
|
+
let reinicios = 0; // tentativas de reinício mal-sucedidas EM SEQUÊNCIA
|
|
337
|
+
|
|
274
338
|
function iniciarWatchdog(home) {
|
|
275
|
-
setInterval(async () => {
|
|
339
|
+
const timer = setInterval(async () => {
|
|
276
340
|
if (await portaAberta(activeBackendPort)) {
|
|
341
|
+
//backend respondendo: zera as duas contagens (queda pontual não punida)
|
|
277
342
|
falhasSaude = 0;
|
|
343
|
+
reinicios = 0;
|
|
278
344
|
return;
|
|
279
345
|
}
|
|
346
|
+
|
|
280
347
|
falhasSaude += 1;
|
|
281
|
-
if (falhasSaude
|
|
282
|
-
|
|
283
|
-
|
|
348
|
+
if (falhasSaude < FALHAS_PARA_REINICIAR) return;
|
|
349
|
+
|
|
350
|
+
// --- teto de tentativas atingido: para tudo em vez de loop infinito ---
|
|
351
|
+
if (reinicios >= MAX_REINICIOS) {
|
|
352
|
+
clearInterval(timer);
|
|
353
|
+
// encerra o backend zumbi por código, silenciosamente
|
|
354
|
+
if (backendPid) encerrarPorPid(backendPid, `Backend (PID ${backendPid}) encerrado após ${MAX_REINICIOS} falhas.`);
|
|
355
|
+
console.error('');
|
|
356
|
+
console.error(`O backend não respondeu em http://127.0.0.1:${activeBackendPort} após ${MAX_REINICIOS} tentativas de reinício.`);
|
|
357
|
+
console.error('O launcher está sendo encerrado para não entrar em loop infinito.');
|
|
358
|
+
console.error(`Diagnóstico completo em: ${join(home, 'data', 'backend.log')}`);
|
|
359
|
+
console.error('Se a porta estiver ocupada por outra instância, encerre-a ou defina PANDANEWS_API_PORT.');
|
|
360
|
+
process.exit(1);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
falhasSaude = 0;
|
|
365
|
+
|
|
366
|
+
// --- reaproveita backend vivo em vez de empilhar outra instância ---
|
|
367
|
+
// backend duplicado era o que fazia a porta "morrer" para o proxy e
|
|
368
|
+
// disparava reinício atrás de reinício.
|
|
369
|
+
const existente = await backendJaRodando(home);
|
|
370
|
+
if (existente) {
|
|
371
|
+
console.log(`Backend já em execução (PID ${existente.pid}) na porta ${existente.port} — reaproveitando (sem subir outra instância).`);
|
|
372
|
+
activeBackendPort = existente.port;
|
|
284
373
|
backendPid = null;
|
|
285
|
-
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// --- reinício ---
|
|
378
|
+
reinicios += 1;
|
|
379
|
+
console.error(`Backend não responde — reiniciando... (tentativa ${reinicios} de ${MAX_REINICIOS})`);
|
|
380
|
+
backendPid = null;
|
|
381
|
+
const subiu = await startBackend(home);
|
|
382
|
+
if (subiu) {
|
|
383
|
+
// conseguiu subir: a próxima leitura saudável zera o contador
|
|
384
|
+
console.log('Backend reerguido pelo watchdog.');
|
|
286
385
|
}
|
|
287
|
-
}, 10000)
|
|
386
|
+
}, 10000);
|
|
387
|
+
timer.unref();
|
|
288
388
|
}
|
|
289
389
|
|
|
290
390
|
// ---------------------------------------------------------------- execução
|