@noego/app 0.0.13 → 0.0.15
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 +40 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noego/app",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
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');
|
|
@@ -762,6 +766,7 @@ async function runSplitServeWithWatch(context, tsxExecutable, tsxArgs, baseEnv,
|
|
|
762
766
|
*/
|
|
763
767
|
const setupBackendRestartPipeline = () => {
|
|
764
768
|
return backendRestartSubject.pipe(
|
|
769
|
+
debounceTime(100), // Batch rapid file saves (5 saves in 1 sec → 1 restart)
|
|
765
770
|
takeUntil(shutdownSubject),
|
|
766
771
|
// exhaustMap ignores new restart requests while one is in progress
|
|
767
772
|
// This prevents restart-during-restart chaos
|
|
@@ -798,6 +803,7 @@ async function runSplitServeWithWatch(context, tsxExecutable, tsxArgs, baseEnv,
|
|
|
798
803
|
*/
|
|
799
804
|
const setupFrontendRestartPipeline = () => {
|
|
800
805
|
return frontendRestartSubject.pipe(
|
|
806
|
+
debounceTime(100), // Batch rapid file saves (5 saves in 1 sec → 1 restart)
|
|
801
807
|
takeUntil(shutdownSubject),
|
|
802
808
|
// exhaustMap ignores new restart requests while one is in progress
|
|
803
809
|
exhaustMap((change) => {
|