@noego/app 0.0.14 → 0.0.16
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/commands/dev.js +38 -34
- package/src/runtime/runtime.js +4 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noego/app",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"description": "Production build tool for Dinner/Forge apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"http-proxy": "^1.18.1",
|
|
35
35
|
"picomatch": "^2.3.1",
|
|
36
36
|
"rxjs": "^7.8.1",
|
|
37
|
+
"tree-kill": "^1.2.2",
|
|
37
38
|
"yaml": "^2.6.0"
|
|
38
39
|
},
|
|
39
40
|
"peerDependencies": {
|
package/src/commands/dev.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { spawn
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
4
|
import { createBuildContext } from '../build/context.js';
|
|
5
5
|
import { findConfigFile } from '../runtime/index.js';
|
|
6
6
|
import { loadConfig } from '../runtime/config-loader.js';
|
|
@@ -10,39 +10,19 @@ import { debounceTime, filter, exhaustMap, tap, catchError, takeUntil, finalize,
|
|
|
10
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
|
+
import treeKill from 'tree-kill';
|
|
13
14
|
|
|
14
15
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Kill a process and all its descendants (entire process tree)
|
|
18
|
-
*
|
|
19
|
+
* Uses tree-kill for cross-platform recursive process tree termination
|
|
19
20
|
*/
|
|
20
21
|
function killProcessTree(pid, signal = 'SIGKILL') {
|
|
21
22
|
if (!pid) return;
|
|
22
23
|
|
|
23
24
|
try {
|
|
24
|
-
|
|
25
|
-
if (process.platform !== 'win32') {
|
|
26
|
-
// Kill all child processes first
|
|
27
|
-
try {
|
|
28
|
-
execSync(`pkill -P ${pid}`, { stdio: 'ignore' });
|
|
29
|
-
} catch (e) {
|
|
30
|
-
// No children or already dead, that's fine
|
|
31
|
-
}
|
|
32
|
-
// Then kill the parent
|
|
33
|
-
try {
|
|
34
|
-
process.kill(pid, signal);
|
|
35
|
-
} catch (e) {
|
|
36
|
-
// Process may already be dead
|
|
37
|
-
}
|
|
38
|
-
} else {
|
|
39
|
-
// On Windows, use taskkill with /T flag to kill process tree
|
|
40
|
-
try {
|
|
41
|
-
execSync(`taskkill /pid ${pid} /T /F`, { stdio: 'ignore' });
|
|
42
|
-
} catch (e) {
|
|
43
|
-
// Process may already be dead
|
|
44
|
-
}
|
|
45
|
-
}
|
|
25
|
+
treeKill(pid, signal);
|
|
46
26
|
} catch (error) {
|
|
47
27
|
// Ignore errors - process might already be dead
|
|
48
28
|
}
|
|
@@ -500,12 +480,24 @@ async function runSplitServeWithWatch(context, tsxExecutable, tsxArgs, baseEnv,
|
|
|
500
480
|
// Auto-restart on crash if under limit
|
|
501
481
|
if (backendCrashRestarts < MAX_CRASH_RESTARTS) {
|
|
502
482
|
backendCrashRestarts++;
|
|
503
|
-
logger.warn(`[BACKEND] Crash detected. Auto-restart ${backendCrashRestarts}/${MAX_CRASH_RESTARTS}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
483
|
+
logger.warn(`[BACKEND] Crash detected. Auto-restart ${backendCrashRestarts}/${MAX_CRASH_RESTARTS}...`);
|
|
484
|
+
|
|
485
|
+
// Wait for port to be free before restarting (fixes race condition)
|
|
486
|
+
backendProc = null;
|
|
487
|
+
waitForPortFree(backendPort, 10000, 100).subscribe({
|
|
488
|
+
next: () => {
|
|
489
|
+
if (!isShuttingDown) {
|
|
490
|
+
logger.info(`[BACKEND] Port ${backendPort} is free, restarting...`);
|
|
491
|
+
startBackend();
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
error: (err) => {
|
|
495
|
+
logger.warn(`[BACKEND] Port wait warning: ${err.message}. Attempting restart anyway...`);
|
|
496
|
+
if (!isShuttingDown) {
|
|
497
|
+
startBackend();
|
|
498
|
+
}
|
|
507
499
|
}
|
|
508
|
-
}
|
|
500
|
+
});
|
|
509
501
|
} else {
|
|
510
502
|
logger.error(`[BACKEND] Exceeded max crash restarts (${MAX_CRASH_RESTARTS}). Shutting down...`);
|
|
511
503
|
shutdown('backend-exceeded-restarts', 1, 'backend-crash');
|
|
@@ -550,12 +542,24 @@ async function runSplitServeWithWatch(context, tsxExecutable, tsxArgs, baseEnv,
|
|
|
550
542
|
// Auto-restart on crash if under limit
|
|
551
543
|
if (frontendCrashRestarts < MAX_CRASH_RESTARTS) {
|
|
552
544
|
frontendCrashRestarts++;
|
|
553
|
-
logger.warn(`[FRONTEND] Crash detected. Auto-restart ${frontendCrashRestarts}/${MAX_CRASH_RESTARTS}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
545
|
+
logger.warn(`[FRONTEND] Crash detected. Auto-restart ${frontendCrashRestarts}/${MAX_CRASH_RESTARTS}...`);
|
|
546
|
+
|
|
547
|
+
// Wait for port to be free before restarting (fixes race condition)
|
|
548
|
+
frontendProc = null;
|
|
549
|
+
waitForPortFree(frontendPort, 10000, 100).subscribe({
|
|
550
|
+
next: () => {
|
|
551
|
+
if (!isShuttingDown) {
|
|
552
|
+
logger.info(`[FRONTEND] Port ${frontendPort} is free, restarting...`);
|
|
553
|
+
startFrontend();
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
error: (err) => {
|
|
557
|
+
logger.warn(`[FRONTEND] Port wait warning: ${err.message}. Attempting restart anyway...`);
|
|
558
|
+
if (!isShuttingDown) {
|
|
559
|
+
startFrontend();
|
|
560
|
+
}
|
|
557
561
|
}
|
|
558
|
-
}
|
|
562
|
+
});
|
|
559
563
|
} else {
|
|
560
564
|
logger.error(`[FRONTEND] Exceeded max crash restarts (${MAX_CRASH_RESTARTS}). Shutting down...`);
|
|
561
565
|
shutdown('frontend-exceeded-restarts', 1, 'frontend-crash');
|
package/src/runtime/runtime.js
CHANGED
|
@@ -372,7 +372,7 @@ async function setupProxy(app, backendPort, config) {
|
|
|
372
372
|
*/
|
|
373
373
|
async function runBackendService(config) {
|
|
374
374
|
const appBootModule = await import(toFileUrl(config.app.boot_abs));
|
|
375
|
-
const backendApp = appBootModule.default(config);
|
|
375
|
+
const backendApp = await appBootModule.default(config);
|
|
376
376
|
|
|
377
377
|
attachCookiePolyfill(backendApp);
|
|
378
378
|
|
|
@@ -402,7 +402,7 @@ async function runBackendService(config) {
|
|
|
402
402
|
*/
|
|
403
403
|
async function runFrontendService(config) {
|
|
404
404
|
const appBootModule = await import(toFileUrl(config.app.boot_abs));
|
|
405
|
-
const frontendApp = appBootModule.default(config);
|
|
405
|
+
const frontendApp = await appBootModule.default(config);
|
|
406
406
|
|
|
407
407
|
attachCookiePolyfill(frontendApp);
|
|
408
408
|
|
|
@@ -684,9 +684,9 @@ async function runRouterService(config) {
|
|
|
684
684
|
*/
|
|
685
685
|
export async function runCombinedServices(config, options = {}) {
|
|
686
686
|
const { hasBackend = true, hasFrontend = true } = options;
|
|
687
|
-
|
|
687
|
+
|
|
688
688
|
const appBootModule = await import(toFileUrl(config.app.boot_abs));
|
|
689
|
-
const app = appBootModule.default(config);
|
|
689
|
+
const app = await appBootModule.default(config);
|
|
690
690
|
|
|
691
691
|
attachCookiePolyfill(app);
|
|
692
692
|
|