@danhachuel/pandanews 0.1.1 → 0.1.2

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
@@ -2,6 +2,12 @@
2
2
 
3
3
  Todas as versões publicadas são registradas aqui antes do publish (regra em AGENTS.md). A etiqueta visual usa PATCH de dois dígitos (ex.: 0.1.00); o registry npm normaliza para a versão canônica (ex.: 0.1.0).
4
4
 
5
+ ## 0.1.02 (2026-10-23)
6
+
7
+ - Porta padrão da UI alterada de 3030 para **4030** (sobrescrevível via `PANDANEWS_UI_PORT`)
8
+ - Fallback automático de porta: se 4030 estiver ocupada, o PandaNews tenta a seguinte (4031, 4032, ...) em até 20 tentativas, com mensagem clara se nenhuma estiver livre
9
+ - CORS do backend passa a aceitar localhost/127.0.0.1 em qualquer porta, acompanhando a porta dinâmica da UI
10
+
5
11
  ## 0.1.01 (2026-10-23)
6
12
 
7
13
  - README reescrito para o fluxo de um único comando via npx (padrão Thunderbolt): `npx @danhachuel/pandanews` instala tudo junto e sobe o app; removidas as instruções manuais de desenvolvimento (pip, uvicorn, npm)
package/README.md CHANGED
@@ -34,9 +34,9 @@ npx --yes @danhachuel/pandanews install
34
34
 
35
35
  O runtime fica em `.runtime/` na raiz do pacote (sobrescreva com a variável de ambiente `PANDANEWS_HOME`).
36
36
 
37
- ## Status (versão 0.1.01)
37
+ ## Status (versão 0.1.02)
38
38
 
39
- - `npx @danhachuel/pandanews` instala tudo junto (Python portátil + dependências + banco local) e sobe o backend em 127.0.0.1:8000 com página em 127.0.0.1:3030
39
+ - `npx @danhachuel/pandanews` instala tudo junto (Python portátil + dependências + banco local) e sobe o backend em 127.0.0.1:8000 com página em 127.0.0.1:4030 — se a porta estiver ocupada, usa a seguinte automaticamente (4031, 4032, ...)
40
40
  - A UI React completa (Dashboard, Blogs, Notícias, Imagens, Automação, Configurações) e o pipeline de notícias chegam nas próximas versões (Fases 1 a 3 do plano — ver `CHANGELOG.md`)
41
41
 
42
42
  ## Regras do projeto
@@ -1,16 +1,17 @@
1
1
  """PandaNews API — FastAPI.
2
2
 
3
3
  Fase 0/1: aplicação mínima com endpoint de health e CORS liberado para a UI
4
- local (127.0.0.1:3030). CRUD de blogs e configurações chegam na Fase 1.
4
+ local (localhost/127.0.0.1 em qualquer porta — a UI usa porta dinâmica a partir
5
+ de 4030). CRUD de blogs e configurações chegam na Fase 1.
5
6
  """
6
7
  from fastapi import FastAPI
7
8
  from fastapi.middleware.cors import CORSMiddleware
8
9
 
9
- app = FastAPI(title="PandaNews API", version="0.1.1")
10
+ app = FastAPI(title="PandaNews API", version="0.1.2")
10
11
 
11
12
  app.add_middleware(
12
13
  CORSMiddleware,
13
- allow_origins=["http://localhost:3030", "http://127.0.0.1:3030"],
14
+ allow_origin_regex=r"^https?://(localhost|127\.0\.0\.1)(:\d+)?$",
14
15
  allow_methods=["*"],
15
16
  allow_headers=["*"],
16
17
  )
@@ -5,4 +5,4 @@ UI do PandaNews (React + Vite + Tailwind), inicializada na Fase 2 do plano de im
5
5
  - Páginas: Dashboard, Blogs, Notícias, Imagens, Automação, Configurações, Logs
6
6
  - Sidebar + topbar no padrão Leadmaster
7
7
  - 7 leis de usabilidade aplicadas (Fitts, Hick, Zeigarnik, Jakob, Gradiente de Meta, von Restorff, Miller)
8
- - Build: `npm run build` → `dist/` servido em 127.0.0.1:3030
8
+ - Build: `npm run build` → `dist/` servido em 127.0.0.1:4030 (com fallback para 4031, 4032, ... se ocupada)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/pandanews",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "SaaS local de criação e automação de múltiplos blogs de notícias com IA (FastAPI + React + Tailwind).",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/scripts/start.js CHANGED
@@ -1,8 +1,9 @@
1
- // Inicia o PandaNews: backend FastAPI (127.0.0.1:8000) + UI (127.0.0.1:3030).
1
+ // Inicia o PandaNews: backend FastAPI (127.0.0.1:8000) + UI (127.0.0.1:4030+).
2
2
  //
3
3
  // Fluxo de um único comando (padrão Thunderbolt): se o runtime não estiver
4
4
  // provisionado, executa o install completo (Python portátil + dependências)
5
- // antes de subir os servidores. Ao final, abre o navegador.
5
+ // antes de subir os servidores. A UI começa em PANDANEWS_UI_PORT (padrão 4030)
6
+ // e, se a porta estiver ocupada, tenta automaticamente a seguinte (4031, ...).
6
7
  import { existsSync, readFileSync } from 'node:fs';
7
8
  import { spawn } from 'node:child_process';
8
9
  import { createServer } from 'node:http';
@@ -10,7 +11,8 @@ import { join, extname } from 'node:path';
10
11
  import { resolveHome, runInstall, PKG_ROOT } from './install.js';
11
12
 
12
13
  const BACKEND_PORT = Number(process.env.PANDANEWS_API_PORT || 8000);
13
- const UI_PORT = Number(process.env.PANDANEWS_UI_PORT || 3030);
14
+ const UI_PORT_BASE = Number(process.env.PANDANEWS_UI_PORT || 4030);
15
+ const UI_PORT_MAX_TRIES = 20;
14
16
 
15
17
  function venvBin(home) {
16
18
  return process.platform === 'win32'
@@ -43,9 +45,11 @@ function placeholderPage() {
43
45
  </html>`;
44
46
  }
45
47
 
46
- function serveUi(onReady) {
48
+ let uiServer = null;
49
+
50
+ function createUiServer() {
47
51
  const distDir = join(PKG_ROOT, 'frontend', 'dist');
48
- const server = createServer((req, res) => {
52
+ return createServer((req, res) => {
49
53
  const url = req.url.split('?')[0];
50
54
  const filePath = join(distDir, url === '/' ? 'index.html' : url.slice(1));
51
55
  if (!existsSync(filePath)) {
@@ -56,16 +60,30 @@ function serveUi(onReady) {
56
60
  res.writeHead(200, { 'Content-Type': MIME[extname(filePath)] || 'application/octet-stream' });
57
61
  res.end(readFileSync(filePath));
58
62
  });
59
- server.on('error', (err) => {
63
+ }
64
+
65
+ function listenUi(port, attempt) {
66
+ if (attempt > UI_PORT_MAX_TRIES) {
67
+ console.error(`Nenhuma porta livre entre ${UI_PORT_BASE} e ${UI_PORT_BASE + UI_PORT_MAX_TRIES - 1}.`);
68
+ console.error('Encerre algum processo que as ocupe ou defina PANDANEWS_UI_PORT.');
69
+ process.exit(1);
70
+ }
71
+ const server = createUiServer();
72
+ server.once('error', (err) => {
60
73
  if (err && err.code === 'EADDRINUSE') {
61
- console.error(`A porta ${UI_PORT} já está em uso. Encerre o processo que a ocupa ou defina PANDANEWS_UI_PORT.`);
74
+ console.log(`Porta ${port} ocupada — tentando ${port + 1}...`);
75
+ listenUi(port + 1, attempt + 1);
62
76
  } else {
63
77
  console.error(`Erro no servidor de UI: ${err && err.message}`);
78
+ process.exit(1);
64
79
  }
65
- process.exit(1);
66
80
  });
67
- server.listen(UI_PORT, '127.0.0.1', onReady);
68
- return server;
81
+ server.listen(port, '127.0.0.1', () => {
82
+ uiServer = server;
83
+ console.log(`PandaNews ativo em http://127.0.0.1:${port}`);
84
+ console.log('Pressione Ctrl+C para encerrar.');
85
+ openBrowser(`http://127.0.0.1:${port}`);
86
+ });
69
87
  }
70
88
 
71
89
  async function openBrowser(url) {
@@ -103,14 +121,10 @@ export async function runStart() {
103
121
  }
104
122
  });
105
123
 
106
- const server = serveUi(() => {
107
- console.log(`PandaNews ativo em http://127.0.0.1:${UI_PORT}`);
108
- console.log('Pressione Ctrl+C para encerrar.');
109
- openBrowser(`http://127.0.0.1:${UI_PORT}`);
110
- });
124
+ listenUi(UI_PORT_BASE, 1);
111
125
 
112
126
  process.on('exit', () => {
113
- server.close();
127
+ if (uiServer) uiServer.close();
114
128
  uvicorn.kill();
115
129
  });
116
130
  process.on('SIGINT', () => process.exit(0));