@noego/app 0.0.22 → 0.0.23
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/package.json +2 -1
- package/src/build/server.js +4 -1
- package/src/build/ssr.js +4 -1
- package/src/cli.js +4 -1
- package/src/commands/dev.js +10 -8
- package/src/commands/preview.js +5 -3
- package/src/commands/runtime-entry.js +5 -2
- package/src/config.js +1 -1
- package/src/logger.js +3 -14
- package/src/runtime/runtime.js +57 -50
- package/src/utils/port.js +5 -2
- package/src/utils/process-observable.js +5 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noego/app",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.23",
|
|
4
4
|
"description": "Production build tool for Dinner/Forge apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"author": "App Build CLI",
|
|
35
35
|
"dependencies": {
|
|
36
|
+
"@noego/logger": "file:../logger",
|
|
36
37
|
"deepmerge": "^4.3.1",
|
|
37
38
|
"glob-parent": "^6.0.2",
|
|
38
39
|
"http-proxy": "^1.18.1",
|
package/src/build/server.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
// No URL utilities needed when not vendoring Forge
|
|
4
|
+
import { getLogger } from '@noego/logger';
|
|
4
5
|
|
|
5
6
|
import { runCommand } from '../utils/command.js';
|
|
6
7
|
import { fixImportExtensions } from './fix-imports.js';
|
|
7
8
|
|
|
9
|
+
const log = getLogger('app');
|
|
10
|
+
|
|
8
11
|
export async function buildServer(context, discovery) {
|
|
9
12
|
const { logger } = context;
|
|
10
13
|
logger.info('Building server bundle');
|
|
@@ -182,7 +185,7 @@ async function mirrorUiModules(context) {
|
|
|
182
185
|
|
|
183
186
|
// Overlay compiled UI outputs from tsc emit so .ts becomes .js under mirrored UI root
|
|
184
187
|
if (config.verbose) {
|
|
185
|
-
|
|
188
|
+
log.debug('overlayUi called:', {
|
|
186
189
|
'config.ui': config.ui,
|
|
187
190
|
'config.ui.rootDir': config.ui?.rootDir,
|
|
188
191
|
'config.rootDir': config.rootDir
|
package/src/build/ssr.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { getLogger } from '@noego/logger';
|
|
4
5
|
|
|
5
6
|
import { buildWithVite } from './vite.js';
|
|
6
7
|
import { createUiBuildConfig } from './ui-common.js';
|
|
7
8
|
import { resolveClientEntry } from './client.js';
|
|
8
9
|
|
|
10
|
+
const log = getLogger('app');
|
|
11
|
+
|
|
9
12
|
export async function resolveSsrBuildConfig(context, discovery) {
|
|
10
13
|
const { config } = context;
|
|
11
14
|
const { entryAbsolute } = await resolveClientEntry(context);
|
|
@@ -352,7 +355,7 @@ function buildSsrManifest(config, discovery) {
|
|
|
352
355
|
|
|
353
356
|
function componentToSsrPath(componentPath, config) {
|
|
354
357
|
if (config.verbose) {
|
|
355
|
-
|
|
358
|
+
log.debug('componentToSsrPath called:', {
|
|
356
359
|
componentPath,
|
|
357
360
|
'config.ui': config.ui,
|
|
358
361
|
'config.ui.rootDir': config.ui?.rootDir,
|
package/src/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import process from 'node:process';
|
|
4
|
+
import { getLogger } from '@noego/logger';
|
|
4
5
|
|
|
5
6
|
import { parseCliArgs, printHelpAndExit } from './args.js';
|
|
6
7
|
import { loadBuildConfig } from './config.js';
|
|
@@ -9,6 +10,8 @@ import { runServe } from './commands/serve.js';
|
|
|
9
10
|
import { runPreview } from './commands/preview.js';
|
|
10
11
|
import { runDev } from './commands/dev.js';
|
|
11
12
|
|
|
13
|
+
const log = getLogger('app');
|
|
14
|
+
|
|
12
15
|
export async function runCli(argv = process.argv.slice(2)) {
|
|
13
16
|
try {
|
|
14
17
|
const { command, options } = parseCliArgs(argv);
|
|
@@ -56,7 +59,7 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
56
59
|
|
|
57
60
|
process.stderr.write(`[app] ${error?.message || error}\n`);
|
|
58
61
|
if (process.env.APP_DEBUG || process.env.HAMMER_DEBUG) {
|
|
59
|
-
|
|
62
|
+
log.error(error);
|
|
60
63
|
}
|
|
61
64
|
process.exitCode = 1;
|
|
62
65
|
}
|
package/src/commands/dev.js
CHANGED
|
@@ -11,8 +11,10 @@ import { waitForPortFree } from '../utils/port.js';
|
|
|
11
11
|
import { stopProcess, killProcessTree as killProcessTreeUtil } from '../utils/process-observable.js';
|
|
12
12
|
import { watcherToObservable, FileEventType } from '../utils/file-watcher-observable.js';
|
|
13
13
|
import treeKill from 'tree-kill';
|
|
14
|
+
import { getLogger } from '@noego/logger';
|
|
14
15
|
|
|
15
16
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const log = getLogger('app');
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* Kill a process and all its descendants (entire process tree)
|
|
@@ -287,12 +289,12 @@ async function runSplitServeNoWatch(tsxExecutable, tsxArgs, baseEnv, routerPort,
|
|
|
287
289
|
logger.info(`Starting frontend on port ${frontendPort}...`);
|
|
288
290
|
logger.info(`Starting backend on port ${backendPort}...`);
|
|
289
291
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
292
|
+
log.debug('Port assignments:');
|
|
293
|
+
log.debug(' routerPort:', routerPort);
|
|
294
|
+
log.debug(' frontendPort:', frontendPort);
|
|
295
|
+
log.debug(' backendPort:', backendPort);
|
|
296
|
+
log.debug('baseEnv NOEGO_PORT:', baseEnv.NOEGO_PORT);
|
|
297
|
+
log.debug('baseEnv NOEGO_BACKEND_PORT:', baseEnv.NOEGO_BACKEND_PORT);
|
|
296
298
|
|
|
297
299
|
// Spawn backend process
|
|
298
300
|
const backendEnv = {
|
|
@@ -301,7 +303,7 @@ async function runSplitServeNoWatch(tsxExecutable, tsxArgs, baseEnv, routerPort,
|
|
|
301
303
|
NOEGO_PORT: String(backendPort)
|
|
302
304
|
};
|
|
303
305
|
|
|
304
|
-
|
|
306
|
+
log.debug('Backend env NOEGO_PORT:', backendEnv.NOEGO_PORT);
|
|
305
307
|
|
|
306
308
|
const backendProc = spawn(tsxExecutable, tsxArgs, {
|
|
307
309
|
cwd: rootDir,
|
|
@@ -318,7 +320,7 @@ async function runSplitServeNoWatch(tsxExecutable, tsxArgs, baseEnv, routerPort,
|
|
|
318
320
|
// Frontend doesn't proxy to backend anymore - router handles that
|
|
319
321
|
};
|
|
320
322
|
|
|
321
|
-
|
|
323
|
+
log.debug('Frontend env NOEGO_PORT:', frontendEnv.NOEGO_PORT);
|
|
322
324
|
|
|
323
325
|
const frontendProc = spawn(tsxExecutable, tsxArgs, {
|
|
324
326
|
cwd: rootDir,
|
package/src/commands/preview.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import util from 'node:util';
|
|
2
|
+
import { getLogger } from '@noego/logger';
|
|
2
3
|
|
|
3
4
|
import { createBuildContext } from '../build/context.js';
|
|
4
5
|
import { discoverProject } from '../build/openapi.js';
|
|
@@ -9,12 +10,13 @@ import {
|
|
|
9
10
|
resolveRecursiveRendererBuildConfig
|
|
10
11
|
} from '../build/ssr.js';
|
|
11
12
|
|
|
13
|
+
const log = getLogger('app');
|
|
12
14
|
const INSPECT_OPTIONS = { depth: null, colors: false };
|
|
13
15
|
|
|
14
16
|
function printSection(heading, payload) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
log.info(`========== ${heading} ==========`);
|
|
18
|
+
log.info(util.inspect(payload, INSPECT_OPTIONS));
|
|
19
|
+
log.info('');
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
export async function runPreview(config) {
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { runRuntime } from '../runtime/runtime.js';
|
|
2
|
+
import { getLogger } from '@noego/logger';
|
|
3
|
+
|
|
4
|
+
const log = getLogger('app');
|
|
2
5
|
|
|
3
6
|
// This file is run via tsx, so it can import TypeScript files
|
|
4
7
|
const configFilePath = process.env.NOEGO_CONFIG_FILE;
|
|
5
8
|
const cliRoot = process.env.NOEGO_CLI_ROOT;
|
|
6
9
|
|
|
7
10
|
if (!configFilePath) {
|
|
8
|
-
|
|
11
|
+
log.error('NOEGO_CONFIG_FILE environment variable is required');
|
|
9
12
|
process.exit(1);
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
runRuntime(configFilePath, cliRoot || undefined).catch((error) => {
|
|
13
|
-
|
|
16
|
+
log.error('Runtime error:', error);
|
|
14
17
|
process.exit(1);
|
|
15
18
|
});
|
|
16
19
|
|
package/src/config.js
CHANGED
|
@@ -48,7 +48,7 @@ export async function loadBuildConfig(cliOptions = {}, { cwd = process.cwd() } =
|
|
|
48
48
|
const uiRelRoot = uiRootDir ? path.relative(config.root, uiRootDir) : null;
|
|
49
49
|
|
|
50
50
|
if (cliOptions.verbose) {
|
|
51
|
-
|
|
51
|
+
log.debug('uiRootDir calculation:', {
|
|
52
52
|
'config.client': config.client,
|
|
53
53
|
'config.client.main_abs': config.client?.main_abs,
|
|
54
54
|
'uiRootDir': uiRootDir,
|
package/src/logger.js
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { getLogger } from '@noego/logger';
|
|
2
2
|
|
|
3
|
-
export function createLogger(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
return {
|
|
7
|
-
info: (...args) => console.log('[app]', ...args),
|
|
8
|
-
warn: (...args) => console.warn('[app]', ...args),
|
|
9
|
-
error: (...args) => console.error('[app]', ...args),
|
|
10
|
-
debug: (...args) => {
|
|
11
|
-
if (debugEnabled) {
|
|
12
|
-
console.log('[app:debug]', ...args);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
};
|
|
3
|
+
export function createLogger() {
|
|
4
|
+
return getLogger('app');
|
|
16
5
|
}
|
package/src/runtime/runtime.js
CHANGED
|
@@ -4,9 +4,16 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
5
|
import { loadConfig } from './config-loader.js';
|
|
6
6
|
import { setContext } from '../client.js';
|
|
7
|
+
import { getLogger } from '@noego/logger';
|
|
7
8
|
|
|
8
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
|
|
11
|
+
const log = getLogger('app');
|
|
12
|
+
const proxyLog = getLogger('app:proxy');
|
|
13
|
+
const routerLog = getLogger('app:router');
|
|
14
|
+
const frontendLog = getLogger('app:frontend');
|
|
15
|
+
const backendLog = getLogger('app:backend');
|
|
16
|
+
|
|
10
17
|
function toFileUrl(filePath) {
|
|
11
18
|
return `file://${filePath}`;
|
|
12
19
|
}
|
|
@@ -148,7 +155,7 @@ async function setupProxyFirst(app, backendPort, config) {
|
|
|
148
155
|
logLevel: 'silent', // We'll do our own logging
|
|
149
156
|
onProxyReq: (proxyReq, req, res) => {
|
|
150
157
|
if (config.mode !== 'production') {
|
|
151
|
-
|
|
158
|
+
proxyLog.debug(`${req.method} ${req.url} -> backend:${backendPort}`);
|
|
152
159
|
}
|
|
153
160
|
}
|
|
154
161
|
});
|
|
@@ -175,18 +182,18 @@ async function setupProxyFirst(app, backendPort, config) {
|
|
|
175
182
|
}
|
|
176
183
|
} catch (err) {
|
|
177
184
|
// On any error, fall back to Vite/Forge
|
|
178
|
-
|
|
185
|
+
proxyLog.error('Error checking backend:', err);
|
|
179
186
|
next();
|
|
180
187
|
}
|
|
181
188
|
});
|
|
182
189
|
|
|
183
|
-
|
|
190
|
+
log.info(`Dynamic proxy configured: checking backend at http://localhost:${backendPort} for non-asset routes`);
|
|
184
191
|
return;
|
|
185
192
|
} catch {
|
|
186
|
-
|
|
193
|
+
log.warn('http-proxy-middleware not available, skipping proxy setup');
|
|
187
194
|
}
|
|
188
195
|
} catch (error) {
|
|
189
|
-
|
|
196
|
+
log.error('Failed to setup proxy:', error);
|
|
190
197
|
}
|
|
191
198
|
}
|
|
192
199
|
|
|
@@ -257,12 +264,12 @@ async function setupProxy(app, backendPort, config) {
|
|
|
257
264
|
logLevel: config.mode === 'production' ? 'error' : 'info',
|
|
258
265
|
onProxyReq: (proxyReq, req, res) => {
|
|
259
266
|
if (config.mode !== 'production') {
|
|
260
|
-
|
|
267
|
+
proxyLog.debug(`${req.method} ${req.url} -> backend:${backendPort}`);
|
|
261
268
|
}
|
|
262
269
|
}
|
|
263
270
|
});
|
|
264
271
|
installFallback(proxyMiddleware);
|
|
265
|
-
|
|
272
|
+
log.info(`Proxy fallback configured: unhandled requests will be forwarded to http://localhost:${backendPort}`);
|
|
266
273
|
return;
|
|
267
274
|
} catch {
|
|
268
275
|
// Fallback to express-http-proxy if http-proxy-middleware not available
|
|
@@ -281,13 +288,13 @@ async function setupProxy(app, backendPort, config) {
|
|
|
281
288
|
installFallback(proxyMiddleware);
|
|
282
289
|
return;
|
|
283
290
|
} catch {
|
|
284
|
-
|
|
291
|
+
log.warn('Proxy dependencies not found; falling back to built-in proxy handler.');
|
|
285
292
|
}
|
|
286
293
|
|
|
287
294
|
const nativeProxy = (req, res, next) => {
|
|
288
295
|
const targetPath = req.originalUrl || req.url || '/';
|
|
289
296
|
if (config.mode !== 'production') {
|
|
290
|
-
|
|
297
|
+
proxyLog.debug(`Forwarding ${req.method} ${targetPath} -> http://localhost:${backendPort}`);
|
|
291
298
|
}
|
|
292
299
|
const requestHeaders = {
|
|
293
300
|
...req.headers,
|
|
@@ -312,7 +319,7 @@ async function setupProxy(app, backendPort, config) {
|
|
|
312
319
|
}
|
|
313
320
|
}
|
|
314
321
|
backendResponse.on('error', (err) => {
|
|
315
|
-
|
|
322
|
+
proxyLog.error('Response error:', err.message);
|
|
316
323
|
if (!res.headersSent) {
|
|
317
324
|
res.status(502).end();
|
|
318
325
|
}
|
|
@@ -322,7 +329,7 @@ async function setupProxy(app, backendPort, config) {
|
|
|
322
329
|
);
|
|
323
330
|
|
|
324
331
|
backendRequest.on('error', (err) => {
|
|
325
|
-
|
|
332
|
+
proxyLog.error('Request error:', err.message);
|
|
326
333
|
if (!res.headersSent) {
|
|
327
334
|
res.status(502).end();
|
|
328
335
|
}
|
|
@@ -362,7 +369,7 @@ async function setupProxy(app, backendPort, config) {
|
|
|
362
369
|
installFallback(nativeProxy);
|
|
363
370
|
return;
|
|
364
371
|
} catch (err) {
|
|
365
|
-
|
|
372
|
+
log.error(`Failed to setup proxy: ${err.message}`);
|
|
366
373
|
throw err;
|
|
367
374
|
}
|
|
368
375
|
}
|
|
@@ -385,26 +392,26 @@ async function runBackendService(config) {
|
|
|
385
392
|
const backendPort = process.env.NOEGO_SERVICE === 'backend' ?
|
|
386
393
|
parseInt(process.env.NOEGO_PORT) : config.dev.backendPort;
|
|
387
394
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
395
|
+
backendLog.debug('NOEGO_SERVICE:', process.env.NOEGO_SERVICE);
|
|
396
|
+
backendLog.debug('NOEGO_PORT:', process.env.NOEGO_PORT);
|
|
397
|
+
backendLog.debug('config.dev.backendPort:', config.dev.backendPort);
|
|
398
|
+
backendLog.debug('Using port:', backendPort);
|
|
392
399
|
|
|
393
400
|
const httpServer = http.createServer(backendApp);
|
|
394
401
|
|
|
395
402
|
httpServer.on('error', (err) => {
|
|
396
403
|
if (err.code === 'EADDRINUSE') {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
404
|
+
backendLog.error(`Port ${backendPort} is already in use (EADDRINUSE)`);
|
|
405
|
+
backendLog.error(`This likely means a stale process is still running.`);
|
|
406
|
+
backendLog.error(`The dev server will attempt to recover...`);
|
|
400
407
|
} else {
|
|
401
|
-
|
|
408
|
+
backendLog.error(`Server error on port ${backendPort}:`, err.message);
|
|
402
409
|
}
|
|
403
410
|
process.exit(1);
|
|
404
411
|
});
|
|
405
412
|
|
|
406
413
|
httpServer.listen(backendPort, '0.0.0.0', () => {
|
|
407
|
-
|
|
414
|
+
backendLog.info(`Backend server running on http://localhost:${backendPort}`);
|
|
408
415
|
});
|
|
409
416
|
|
|
410
417
|
return backendApp;
|
|
@@ -449,24 +456,24 @@ async function runFrontendService(config) {
|
|
|
449
456
|
const frontendPort = process.env.NOEGO_SERVICE === 'frontend' ?
|
|
450
457
|
parseInt(process.env.NOEGO_PORT) : config.dev.port;
|
|
451
458
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
459
|
+
frontendLog.debug('NOEGO_SERVICE:', process.env.NOEGO_SERVICE);
|
|
460
|
+
frontendLog.debug('NOEGO_PORT:', process.env.NOEGO_PORT);
|
|
461
|
+
frontendLog.debug('config.dev.port:', config.dev.port);
|
|
462
|
+
frontendLog.debug('Using port:', frontendPort);
|
|
456
463
|
|
|
457
464
|
httpServer.on('error', (err) => {
|
|
458
465
|
if (err.code === 'EADDRINUSE') {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
466
|
+
frontendLog.error(`Port ${frontendPort} is already in use (EADDRINUSE)`);
|
|
467
|
+
frontendLog.error(`This likely means a stale process is still running.`);
|
|
468
|
+
frontendLog.error(`The dev server will attempt to recover...`);
|
|
462
469
|
} else {
|
|
463
|
-
|
|
470
|
+
frontendLog.error(`Server error on port ${frontendPort}:`, err.message);
|
|
464
471
|
}
|
|
465
472
|
process.exit(1);
|
|
466
473
|
});
|
|
467
474
|
|
|
468
475
|
httpServer.listen(frontendPort, '0.0.0.0', () => {
|
|
469
|
-
|
|
476
|
+
frontendLog.info(`Frontend server running on http://localhost:${frontendPort}`);
|
|
470
477
|
});
|
|
471
478
|
|
|
472
479
|
return frontendApp;
|
|
@@ -546,7 +553,7 @@ async function runRouterService(config) {
|
|
|
546
553
|
|
|
547
554
|
// Debug logging for authentication-related headers
|
|
548
555
|
if (headers.cookie || headers.authorization) {
|
|
549
|
-
|
|
556
|
+
routerLog.debug(`Forwarding auth headers to ${serviceName}:`, {
|
|
550
557
|
cookie: headers.cookie ? 'present' : 'absent',
|
|
551
558
|
authorization: headers.authorization ? 'present' : 'absent'
|
|
552
559
|
});
|
|
@@ -565,7 +572,7 @@ async function runRouterService(config) {
|
|
|
565
572
|
|
|
566
573
|
// Debug logging for authentication response headers
|
|
567
574
|
if (proxyRes.headers['set-cookie'] || proxyRes.statusCode === 401 || proxyRes.statusCode === 403) {
|
|
568
|
-
|
|
575
|
+
routerLog.debug(`Response from ${serviceName}:`, {
|
|
569
576
|
status: proxyRes.statusCode,
|
|
570
577
|
'set-cookie': proxyRes.headers['set-cookie'] ? 'present' : 'absent'
|
|
571
578
|
});
|
|
@@ -583,7 +590,7 @@ async function runRouterService(config) {
|
|
|
583
590
|
});
|
|
584
591
|
|
|
585
592
|
proxyReq.on('error', (err) => {
|
|
586
|
-
|
|
593
|
+
routerLog.error(`Error proxying to ${serviceName}: ${err.message}`);
|
|
587
594
|
resolve({ statusCode: 502, error: err, serviceName });
|
|
588
595
|
});
|
|
589
596
|
|
|
@@ -634,7 +641,7 @@ async function runRouterService(config) {
|
|
|
634
641
|
|
|
635
642
|
if (frontendResult.statusCode === 404) {
|
|
636
643
|
// Frontend returned 404, try backend
|
|
637
|
-
|
|
644
|
+
routerLog.info(`Frontend returned 404 for ${method} ${originalUrl}, trying backend...`);
|
|
638
645
|
const backendResult = await tryService(backendPort, 'backend');
|
|
639
646
|
|
|
640
647
|
// Send backend response
|
|
@@ -679,36 +686,36 @@ async function runRouterService(config) {
|
|
|
679
686
|
// Only route to backend if explicitly needed (currently none)
|
|
680
687
|
const target = `ws://localhost:${frontendPort}`;
|
|
681
688
|
|
|
682
|
-
|
|
689
|
+
routerLog.debug(`[ws] Routing WebSocket ${url} to ${target}`);
|
|
683
690
|
|
|
684
691
|
wsProxy.ws(req, socket, head, { target }, (err) => {
|
|
685
|
-
|
|
692
|
+
routerLog.error('[ws] proxy error:', err?.message);
|
|
686
693
|
try { socket.destroy(); } catch {}
|
|
687
694
|
});
|
|
688
695
|
});
|
|
689
696
|
|
|
690
697
|
// Optional: observe proxy-level errors
|
|
691
698
|
wsProxy.on('error', (err, req, socket) => {
|
|
692
|
-
|
|
699
|
+
routerLog.error('[ws] proxy error (global):', err?.message);
|
|
693
700
|
try { socket?.destroy?.(); } catch {}
|
|
694
701
|
});
|
|
695
702
|
|
|
696
703
|
httpServer.on('error', (err) => {
|
|
697
704
|
if (err.code === 'EADDRINUSE') {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
705
|
+
routerLog.error(`Port ${routerPort} is already in use (EADDRINUSE)`);
|
|
706
|
+
routerLog.error(`This likely means a stale process is still running.`);
|
|
707
|
+
routerLog.error(`The dev server will attempt to recover...`);
|
|
701
708
|
} else {
|
|
702
|
-
|
|
709
|
+
routerLog.error(`Server error on port ${routerPort}:`, err.message);
|
|
703
710
|
}
|
|
704
711
|
process.exit(1);
|
|
705
712
|
});
|
|
706
713
|
|
|
707
714
|
httpServer.listen(routerPort, '0.0.0.0', () => {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
715
|
+
routerLog.info(`Router server running on http://localhost:${routerPort}`);
|
|
716
|
+
routerLog.info(` Proxying to frontend on port ${frontendPort}`);
|
|
717
|
+
routerLog.info(` Proxying to backend on port ${backendPort}`);
|
|
718
|
+
routerLog.info(` WebSocket support enabled via http-proxy`);
|
|
712
719
|
});
|
|
713
720
|
|
|
714
721
|
return routerApp;
|
|
@@ -762,11 +769,11 @@ export async function runCombinedServices(config, options = {}) {
|
|
|
762
769
|
|
|
763
770
|
httpServer.on('error', (err) => {
|
|
764
771
|
if (err.code === 'EADDRINUSE') {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
772
|
+
log.error(`Port ${port} is already in use (EADDRINUSE)`);
|
|
773
|
+
log.error(`This likely means a stale process is still running.`);
|
|
774
|
+
log.error(`The dev server will attempt to recover...`);
|
|
768
775
|
} else {
|
|
769
|
-
|
|
776
|
+
log.error(`Server error on port ${port}:`, err.message);
|
|
770
777
|
}
|
|
771
778
|
process.exit(1);
|
|
772
779
|
});
|
|
@@ -776,7 +783,7 @@ export async function runCombinedServices(config, options = {}) {
|
|
|
776
783
|
if (hasBackend && config.server?.main_abs) services.push('Backend');
|
|
777
784
|
if (hasFrontend && config.client?.main_abs) services.push('Frontend');
|
|
778
785
|
const servicesStr = services.length > 0 ? ` (${services.join(' + ')})` : '';
|
|
779
|
-
|
|
786
|
+
log.info(`Server running on http://localhost:${port}${servicesStr}`);
|
|
780
787
|
});
|
|
781
788
|
|
|
782
789
|
return app;
|
package/src/utils/port.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Observable, interval, of, throwError } from 'rxjs';
|
|
2
2
|
import { map, switchMap, filter, take, timeout, catchError } from 'rxjs/operators';
|
|
3
3
|
import net from 'node:net';
|
|
4
|
+
import { getLogger } from '@noego/logger';
|
|
5
|
+
|
|
6
|
+
const log = getLogger('app');
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* Check if a port is currently free (available for binding)
|
|
@@ -67,7 +70,7 @@ export function waitForPortFree(port, timeoutMs = 5000, pollInterval = 50) {
|
|
|
67
70
|
}),
|
|
68
71
|
// Provide graceful fallback on timeout (continue anyway but log warning)
|
|
69
72
|
catchError((err) => {
|
|
70
|
-
|
|
73
|
+
log.warn(`Warning: ${err.message}. Proceeding anyway.`);
|
|
71
74
|
return of(undefined);
|
|
72
75
|
})
|
|
73
76
|
);
|
|
@@ -97,7 +100,7 @@ export function waitForPortInUse(port, timeoutMs = 10000, pollInterval = 100) {
|
|
|
97
100
|
}),
|
|
98
101
|
// Provide graceful fallback on timeout
|
|
99
102
|
catchError((err) => {
|
|
100
|
-
|
|
103
|
+
log.warn(`Warning: ${err.message}. Proceeding anyway.`);
|
|
101
104
|
return of(undefined);
|
|
102
105
|
})
|
|
103
106
|
);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Observable, of, timer, race, EMPTY } from 'rxjs';
|
|
2
2
|
import { map, switchMap, take, catchError, finalize, tap } from 'rxjs/operators';
|
|
3
3
|
import { spawn, execSync } from 'node:child_process';
|
|
4
|
+
import { getLogger } from '@noego/logger';
|
|
5
|
+
|
|
6
|
+
const log = getLogger('app');
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* Process event types emitted by spawnProcess observable
|
|
@@ -149,7 +152,7 @@ export function stopProcess(proc, gracefulTimeout = 5000, killTreeFn = killProce
|
|
|
149
152
|
// Set up force kill timer
|
|
150
153
|
forceKillTimer = setTimeout(() => {
|
|
151
154
|
if (!exited && pid) {
|
|
152
|
-
|
|
155
|
+
log.warn(`Process ${pid} didn't exit gracefully, force killing...`);
|
|
153
156
|
killTreeFn(pid, 'SIGKILL');
|
|
154
157
|
}
|
|
155
158
|
}, gracefulTimeout);
|
|
@@ -211,7 +214,7 @@ export function stopProcessAndVerify(proc, gracefulTimeout = 5000, killTreeFn =
|
|
|
211
214
|
switchMap(() => timer(50)),
|
|
212
215
|
map(() => undefined),
|
|
213
216
|
catchError((err) => {
|
|
214
|
-
|
|
217
|
+
log.warn(`Error stopping process: ${err.message}`);
|
|
215
218
|
return of(undefined);
|
|
216
219
|
})
|
|
217
220
|
);
|