Repository navigation
Conversation
…e.js - Added package.json with project metadata, dependencies, and build scripts. - Created loading.html for a loading screen with a spinner and status message. - Implemented main.js to manage the Electron app lifecycle, including backend startup and health checks. - Added preload.js for potential IPC helpers in the future.
#20) …e.js - Added package.json with project metadata, dependencies, and build scripts. - Created loading.html for a loading screen with a spinner and status message. - Implemented main.js to manage the Electron app lifecycle, including backend startup and health checks. - Added preload.js for potential IPC helpers in the future.
There was a problem hiding this comment.
Pull request overview
This PR extends the monorepo to support a Windows desktop distribution via Electron, embedding the existing Express backend and React UI into a packaged app and adding a UI-triggered scraper run.
Changes:
- Adds Electron main process boot flow (start backend in-process, wait for health, load UI) plus loading window assets.
- Introduces
POST /api/scraper/runin the backend and wires a “Buscar vagas” action in the frontend to trigger it. - Updates root packaging/build setup (electron-builder config, scripts) and documents the desktop workflow.
Reviewed changes
Copilot reviewed 12 out of 14 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| package.json | Adds Electron scripts, electron-builder config, and app metadata for desktop distribution. |
| frontend/src/services/jobsService.ts | Adds runScraperRequest() calling POST /api/scraper/run. |
| frontend/src/hooks/useJobsData.ts | Adds scraper state + triggerScraper() to run scraper and refresh available XLSX files/jobs. |
| frontend/src/App.tsx | Adds “Buscar vagas” button with spinner state and disables UI during scraping. |
| electron/preload.js | Adds preload placeholder (no IPC exposed yet). |
| electron/main.js | Implements Electron boot flow, backend start + health polling, loading window, and main window. |
| electron/loading.html | Adds a dedicated loading screen shown while backend starts. |
| backend/src/jobsApiApp.js | Adds POST /api/scraper/run with a single-run concurrency guard. |
| backend/src/server.js | Adds Electron-specific output/static directory handling and SPA fallback routing. |
| backend/tests/unit/services/server.test.js | Adds tests for ELECTRON_* env behavior and SPA fallback. |
| backend/tests/integration/jobsApi.test.js | Adds integration tests for /api/scraper/run success, conflict, and failure cases. |
| README.md | Documents how to run/build/distribute the Electron app. |
| .gitignore | Ignores dist-electron/. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try { | ||
| activeScraperRun = runScraper(); | ||
| await activeScraperRun; | ||
|
|
||
| const files = listXlsxFiles(); |
There was a problem hiding this comment.
runScraper() usa getConfig() (que lê OUTPUT_FILE/PDF_FILE do process.env) para decidir onde gravar os arquivos, mas este endpoint lista/retorna arquivos a partir de outputDir (opção do createJobsApiApp). Se outputDir for customizado (ex.: em testes/uso embutido), o scraper pode escrever em outro diretório e a resposta pode não refletir o resultado do run. Considere alinhar o diretório de output do scraper com outputDir (passando config para o scraper ou setando env defaults com base em outputDir).
| // Router (if used in future) works correctly. | ||
| app.use((req, res) => { | ||
| if (req.path.startsWith("/api")) { | ||
| return res.status(404).json({ error: "Rota não encontrada." }); |
There was a problem hiding this comment.
O fallback para rotas /api/* está retornando { error: ... }, mas o frontend (e os demais endpoints) usam o campo message para exibir erros. Para manter consistência e garantir que readMessage(payload) funcione, troque para { message: "Rota não encontrada." } (e ajuste o teste correspondente).
| return res.status(404).json({ error: "Rota não encontrada." }); | |
| return res.status(404).json({ message: "Rota não encontrada." }); |
| }; | ||
| fallbackHandler({ path: "/api/unknown" }, apiRes); | ||
| expect(apiRes.status).toHaveBeenCalledWith(404); | ||
| expect(apiRes.json).toHaveBeenCalledWith({ error: "Rota não encontrada." }); |
There was a problem hiding this comment.
Este teste valida o fallback SPA com { error: "Rota não encontrada." }, mas a API e o frontend padronizam mensagens de erro no campo message. Após ajustar o middleware em backend/src/server.js para usar message, atualize esta asserção para refletir o payload correto.
| expect(apiRes.json).toHaveBeenCalledWith({ error: "Rota não encontrada." }); | |
| expect(apiRes.json).toHaveBeenCalledWith({ message: "Rota não encontrada." }); |
| dialog.showErrorBox( | ||
| 'Falha ao iniciar o aplicativo', | ||
| `O servidor não pôde ser iniciado.\n\n${fatal.message}`, |
There was a problem hiding this comment.
No catch, o template string acessa fatal.message, mas fatal pode não ser um Error (por exemplo, um string/objeto). Isso pode causar uma exceção dentro do próprio handler de erro e impedir a exibição do dialog. Faça um narrowing (ex.: fatal instanceof Error) e faça fallback para String(fatal)/mensagem padrão.
| dialog.showErrorBox( | |
| 'Falha ao iniciar o aplicativo', | |
| `O servidor não pôde ser iniciado.\n\n${fatal.message}`, | |
| let fatalMessage; | |
| if (fatal instanceof Error && typeof fatal.message === 'string' && fatal.message) { | |
| fatalMessage = fatal.message; | |
| } else if (fatal != null) { | |
| fatalMessage = String(fatal); | |
| } else { | |
| fatalMessage = 'Erro desconhecido.'; | |
| } | |
| dialog.showErrorBox( | |
| 'Falha ao iniciar o aplicativo', | |
| `O servidor não pôde ser iniciado.\n\n${fatalMessage}`, |
No description provided.