@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noego/app",
3
- "version": "0.0.14",
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": {
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
- import { spawn, execSync } from 'node:child_process';
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
- * This ensures no orphaned processes remain when killing a parent
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
- // On Unix systems, use pkill to kill all descendants
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} in ${CRASH_RESTART_DELAY}ms...`);
504
- setTimeout(() => {
505
- if (!isShuttingDown) {
506
- startBackend();
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
- }, CRASH_RESTART_DELAY);
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} in ${CRASH_RESTART_DELAY}ms...`);
554
- setTimeout(() => {
555
- if (!isShuttingDown) {
556
- startFrontend();
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
- }, CRASH_RESTART_DELAY);
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');
@@ -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