feat: add GitHub issue fix command and release command

- Introduced a new command for fetching and validating GitHub issues, allowing users to address issues directly from the command line.
- Added a release command to bump the version of the application and build the Electron app, ensuring version consistency across UI and server packages.
- Updated package.json files for both UI and server to version 0.7.1, reflecting the latest changes.
- Implemented version utility in the server to read the version from package.json, enhancing version management across the application.
This commit is contained in:
WebDevCody
2025-12-31 23:24:01 -05:00
parent eae60ab6b9
commit 98381441b9
38 changed files with 1406 additions and 295 deletions

View File

@@ -11,6 +11,7 @@ import path from 'path';
import { spawn, execSync, ChildProcess } from 'child_process';
import crypto from 'crypto';
import http, { Server } from 'http';
import net from 'net';
import { app, BrowserWindow, ipcMain, dialog, shell, screen } from 'electron';
import {
findNodeExecutable,
@@ -51,8 +52,46 @@ if (isDev) {
let mainWindow: BrowserWindow | null = null;
let serverProcess: ChildProcess | null = null;
let staticServer: Server | null = null;
const SERVER_PORT = 3008;
const STATIC_PORT = 3007;
// Default ports - will be dynamically assigned if these are in use
const DEFAULT_SERVER_PORT = 3008;
const DEFAULT_STATIC_PORT = 3007;
// Actual ports in use (set during startup)
let serverPort = DEFAULT_SERVER_PORT;
let staticPort = DEFAULT_STATIC_PORT;
/**
* Check if a port is available
*/
function isPortAvailable(port: number): Promise<boolean> {
return new Promise((resolve) => {
const server = net.createServer();
server.once('error', () => {
resolve(false);
});
server.once('listening', () => {
server.close(() => {
resolve(true);
});
});
server.listen(port, '127.0.0.1');
});
}
/**
* Find an available port starting from the preferred port
* Tries up to 100 ports in sequence
*/
async function findAvailablePort(preferredPort: number): Promise<number> {
for (let offset = 0; offset < 100; offset++) {
const port = preferredPort + offset;
if (await isPortAvailable(port)) {
return port;
}
}
throw new Error(`Could not find an available port starting from ${preferredPort}`);
}
// ============================================
// Window sizing constants for kanban layout
@@ -326,8 +365,8 @@ async function startStaticServer(): Promise<void> {
});
return new Promise((resolve, reject) => {
staticServer!.listen(STATIC_PORT, () => {
console.log(`[Electron] Static server running at http://localhost:${STATIC_PORT}`);
staticServer!.listen(staticPort, () => {
console.log(`[Electron] Static server running at http://localhost:${staticPort}`);
resolve();
});
staticServer!.on('error', reject);
@@ -432,7 +471,7 @@ async function startServer(): Promise<void> {
const env = {
...process.env,
PATH: enhancedPath,
PORT: SERVER_PORT.toString(),
PORT: serverPort.toString(),
DATA_DIR: app.getPath('userData'),
NODE_PATH: serverNodeModules,
// Pass API key to server for CSRF protection
@@ -444,6 +483,8 @@ async function startServer(): Promise<void> {
}),
};
console.log(`[Electron] Server will use port ${serverPort}`);
console.log('[Electron] Starting backend server...');
console.log('[Electron] Server path:', serverPath);
console.log('[Electron] Server root (cwd):', serverRoot);
@@ -483,7 +524,7 @@ async function waitForServer(maxAttempts = 30): Promise<void> {
for (let i = 0; i < maxAttempts; i++) {
try {
await new Promise<void>((resolve, reject) => {
const req = http.get(`http://localhost:${SERVER_PORT}/api/health`, (res) => {
const req = http.get(`http://localhost:${serverPort}/api/health`, (res) => {
if (res.statusCode === 200) {
resolve();
} else {
@@ -548,9 +589,9 @@ function createWindow(): void {
mainWindow.loadURL(VITE_DEV_SERVER_URL);
} else if (isDev) {
// Fallback for dev without Vite server URL
mainWindow.loadURL(`http://localhost:${STATIC_PORT}`);
mainWindow.loadURL(`http://localhost:${staticPort}`);
} else {
mainWindow.loadURL(`http://localhost:${STATIC_PORT}`);
mainWindow.loadURL(`http://localhost:${staticPort}`);
}
if (isDev && process.env.OPEN_DEVTOOLS === 'true') {
@@ -642,6 +683,21 @@ app.whenReady().then(async () => {
ensureApiKey();
try {
// Find available ports (prevents conflicts with other apps using same ports)
serverPort = await findAvailablePort(DEFAULT_SERVER_PORT);
if (serverPort !== DEFAULT_SERVER_PORT) {
console.log(
`[Electron] Default server port ${DEFAULT_SERVER_PORT} in use, using port ${serverPort}`
);
}
staticPort = await findAvailablePort(DEFAULT_STATIC_PORT);
if (staticPort !== DEFAULT_STATIC_PORT) {
console.log(
`[Electron] Default static port ${DEFAULT_STATIC_PORT} in use, using port ${staticPort}`
);
}
// Start static file server in production
if (app.isPackaged) {
await startStaticServer();
@@ -675,6 +731,28 @@ app.whenReady().then(async () => {
});
app.on('window-all-closed', () => {
// Stop the server when all windows are closed, even on macOS
// This prevents port conflicts when reopening the app
if (serverProcess && serverProcess.pid) {
console.log('[Electron] All windows closed, stopping server...');
if (process.platform === 'win32') {
try {
execSync(`taskkill /f /t /pid ${serverProcess.pid}`, { stdio: 'ignore' });
} catch (error) {
console.error('[Electron] Failed to kill server process:', (error as Error).message);
}
} else {
serverProcess.kill('SIGTERM');
}
serverProcess = null;
}
if (staticServer) {
console.log('[Electron] Stopping static server...');
staticServer.close();
staticServer = null;
}
if (process.platform !== 'darwin') {
app.quit();
}
@@ -822,7 +900,7 @@ ipcMain.handle('ping', async () => {
// Get server URL for HTTP client
ipcMain.handle('server:getUrl', async () => {
return `http://localhost:${SERVER_PORT}`;
return `http://localhost:${serverPort}`;
});
// Get API key for authentication