bgrun 3.12.1 → 3.12.3

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.
@@ -12,20 +12,20 @@ export default function DashboardPage() {
12
12
  <div className="toast-container" id="toast-container"></div>
13
13
 
14
14
  {/* Stats Grid */}
15
- <div className="stats-grid">
16
- <div className="stat-card">
15
+ <div className="stats-grid" id="stats-grid">
16
+ <div className="stat-card stat-clickable" data-stat-filter="all">
17
17
  <div className="stat-label">Total Processes</div>
18
18
  <div className="stat-value" id="total-count">–</div>
19
19
  </div>
20
- <div className="stat-card running">
20
+ <div className="stat-card running stat-clickable" data-stat-filter="running">
21
21
  <div className="stat-label">Running</div>
22
22
  <div className="stat-value" id="running-count">–</div>
23
23
  </div>
24
- <div className="stat-card stopped">
24
+ <div className="stat-card stopped stat-clickable" data-stat-filter="stopped">
25
25
  <div className="stat-label">Stopped</div>
26
26
  <div className="stat-value" id="stopped-count">–</div>
27
27
  </div>
28
- <div className="stat-card guarded">
28
+ <div className="stat-card guarded stat-clickable" data-stat-filter="guarded">
29
29
  <div className="stat-label">Guarded</div>
30
30
  <div className="stat-value" id="guarded-count">–</div>
31
31
  </div>
@@ -37,6 +37,14 @@ export default function DashboardPage() {
37
37
  <div className="stat-label">Guard Restarts</div>
38
38
  <div className="stat-value" id="restarts-count">0</div>
39
39
  </div>
40
+ <div className="stat-card uptime">
41
+ <div className="stat-label">Longest Uptime</div>
42
+ <div className="stat-value" id="uptime-longest">–</div>
43
+ </div>
44
+ <div className="stat-card uptime-total">
45
+ <div className="stat-label">Total Uptime</div>
46
+ <div className="stat-value" id="uptime-total">–</div>
47
+ </div>
40
48
  </div>
41
49
 
42
50
  {/* Guard Activity Feed */}
@@ -65,9 +73,14 @@ export default function DashboardPage() {
65
73
  <span className="search-count" id="search-count" style={{ display: 'none' }}></span>
66
74
  <span className="search-shortcut">/</span>
67
75
  </div>
76
+ <span className="stat-filter-badge" id="stat-filter-badge" style={{ display: 'none' }}>
77
+ <span id="stat-filter-badge-label"></span>
78
+ <button className="stat-filter-badge-clear" id="stat-filter-badge-clear" type="button" title="Clear status filter">✕</button>
79
+ </span>
68
80
  <select className="group-filter" id="group-filter">
69
81
  <option value="">All Groups</option>
70
82
  </select>
83
+ <div className="deploy-preset-scopes" id="deploy-preset-scopes"></div>
71
84
  </div>
72
85
  <div className="toolbar-right">
73
86
  <button className="btn btn-ghost btn-icon" id="refresh-btn" title="Refresh">
@@ -82,10 +95,40 @@ export default function DashboardPage() {
82
95
  </svg>
83
96
  <span id="guard-all-label">Guard All</span>
84
97
  </button>
98
+ <button className="btn btn-ghost" id="deploy-all-btn" title="Git pull + restart all deployable processes">
99
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
100
+ <path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z" />
101
+ <path d="M12 15l-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z" />
102
+ <path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0" />
103
+ <path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" />
104
+ </svg>
105
+ <span id="deploy-all-label">Deploy All</span>
106
+ </button>
107
+ <div className="deploy-controls">
108
+ <label className="deploy-concurrency-wrap" title="Bulk deploy concurrency (saved per group)">
109
+ <span className="deploy-concurrency-label">Deploy</span>
110
+ <select className="deploy-concurrency-select" id="deploy-concurrency-select">
111
+ <option value="1">1×</option>
112
+ <option value="2">2×</option>
113
+ <option value="3">3×</option>
114
+ <option value="4">4×</option>
115
+ </select>
116
+ </label>
117
+ <span className="deploy-preset-source" id="deploy-preset-source" title="Current deploy preset source">default</span>
118
+ <button className="btn btn-ghost btn-icon" id="deploy-preset-reset-btn" title="Reset saved deploy preset for current group">
119
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
120
+ <polyline points="1 4 1 10 7 10" />
121
+ <path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10" />
122
+ </svg>
123
+ </button>
124
+ </div>
85
125
  <span className="guard-sentinel-pill" id="guard-sentinel-pill" title="Standalone guard process status">
86
126
  <span className="guard-sentinel-dot" id="guard-sentinel-dot" />
87
127
  <span id="guard-sentinel-label">Guard: –</span>
88
128
  </span>
129
+ <button className="btn btn-ghost btn-icon" id="theme-toggle-btn" title="Toggle light/dark theme">
130
+ <span id="theme-toggle-icon" style={{ fontSize: '0.85rem' }}>🌙</span>
131
+ </button>
89
132
  <button className="btn btn-ghost btn-icon" id="shortcuts-btn" title="Keyboard Shortcuts (?)">
90
133
  <span style={{ fontSize: '0.85rem', fontWeight: '700' }}>?</span>
91
134
  </button>
@@ -96,6 +139,17 @@ export default function DashboardPage() {
96
139
  </svg>
97
140
  Templates
98
141
  </button>
142
+ <button className="btn btn-ghost" id="deps-btn" title="Process Dependencies">
143
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
144
+ <circle cx="6" cy="6" r="3" />
145
+ <circle cx="18" cy="6" r="3" />
146
+ <circle cx="18" cy="18" r="3" />
147
+ <line x1="9" y1="6" x2="15" y2="6" />
148
+ <path d="M18 9v6" />
149
+ <path d="M9 6l6 9" />
150
+ </svg>
151
+ Deps
152
+ </button>
99
153
  <button className="btn btn-ghost" id="history-btn">
100
154
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
101
155
  <circle cx="12" cy="12" r="10" />
@@ -244,6 +298,19 @@ export default function DashboardPage() {
244
298
  <label htmlFor="process-directory-input">Working Directory</label>
245
299
  <input type="text" id="process-directory-input" placeholder="/path/to/project" />
246
300
  </div>
301
+ <div className="form-group">
302
+ <label htmlFor="process-port-input">Port <span style={{ fontWeight: 400, color: 'var(--text-muted)' }}>(optional)</span></label>
303
+ <div className="port-input-wrap">
304
+ <input type="number" id="process-port-input" placeholder="auto" min="1" max="65535" />
305
+ <button className="btn btn-ghost btn-sm" id="suggest-port-btn" type="button" title="Auto-assign next available port">Suggest</button>
306
+ </div>
307
+ <div className="port-range-wrap">
308
+ <span className="port-range-label">Range</span>
309
+ <input type="number" id="port-range-min" className="port-range-input" placeholder="3001" min="1" max="65535" />
310
+ <span className="port-range-sep">–</span>
311
+ <input type="number" id="port-range-max" className="port-range-input" placeholder="4000" min="1" max="65535" />
312
+ </div>
313
+ </div>
247
314
  </div>
248
315
  <div className="modal-footer">
249
316
  <button className="btn btn-ghost" id="modal-cancel-btn">Cancel</button>
@@ -253,6 +320,33 @@ export default function DashboardPage() {
253
320
  </div>
254
321
 
255
322
  {/* History Modal */}
323
+ {/* Dependencies Modal */}
324
+ <div className="modal-overlay" id="deps-modal">
325
+ <div className="modal modal-wide">
326
+ <div className="modal-header">
327
+ <h3>🔗 Process Dependencies</h3>
328
+ <button className="modal-close" id="deps-modal-close">✕</button>
329
+ </div>
330
+ <div className="modal-body">
331
+ <div className="deps-controls">
332
+ <select id="deps-process-select" className="history-select">
333
+ <option value="">Select process...</option>
334
+ </select>
335
+ <span className="deps-arrow">depends on →</span>
336
+ <select id="deps-target-select" className="history-select">
337
+ <option value="">Select dependency...</option>
338
+ </select>
339
+ <button className="btn btn-primary btn-sm" id="deps-add-btn">Add</button>
340
+ </div>
341
+ <div className="deps-graph-container" id="deps-graph-container">
342
+ <svg id="deps-graph-svg" width="100%" height="400"></svg>
343
+ </div>
344
+ <div className="deps-list" id="deps-list"></div>
345
+ <div className="deps-start-order" id="deps-start-order"></div>
346
+ </div>
347
+ </div>
348
+ </div>
349
+
256
350
  <div className="modal-overlay" id="history-modal">
257
351
  <div className="modal modal-wide">
258
352
  <div className="modal-header">
@@ -269,9 +363,85 @@ export default function DashboardPage() {
269
363
  <option value="start">Start</option>
270
364
  <option value="stop">Stop</option>
271
365
  <option value="restart">Restart</option>
366
+ <option value="deploy">Deploy</option>
272
367
  <option value="guard_on">Guard On</option>
273
368
  <option value="guard_off">Guard Off</option>
274
369
  </select>
370
+ <input id="history-metadata-filter" className="history-select history-search" type="text" placeholder="Filter metadata (comma-separated)..." />
371
+ <label className="history-density-wrap" title="History row density">
372
+ <span className="history-density-label">Density</span>
373
+ <select id="history-density-select" className="history-select history-density-select">
374
+ <option value="cozy">Cozy</option>
375
+ <option value="compact">Compact</option>
376
+ </select>
377
+ </label>
378
+ <label className="history-shortcuts-toggle" title="Show quick-action affordances in History rows">
379
+ <input id="history-shortcuts-toggle" type="checkbox" />
380
+ <span>Shortcuts</span>
381
+ </label>
382
+ <label className="history-density-wrap" title="Default History details state">
383
+ <span className="history-density-label">Details</span>
384
+ <select id="history-details-default-select" className="history-select history-density-select">
385
+ <option value="collapsed">Collapsed</option>
386
+ <option value="expanded">Expanded</option>
387
+ </select>
388
+ </label>
389
+ <button className="btn btn-ghost btn-sm" id="history-clear-filters-btn" title="Clear all history filters">Clear</button>
390
+ </div>
391
+ <div className="history-hints-bar">
392
+ <div className="history-hints-bar-left">
393
+ <span className="history-hints-title">Keyboard shortcuts</span>
394
+ <div className="history-focus-controls">
395
+ <button className="history-focus-jump" id="history-focus-prev" type="button" title="Focus previous History row">←</button>
396
+ <span className="history-focus-status" id="history-focus-status">No row selected</span>
397
+ <button className="history-focus-jump" id="history-focus-next" type="button" title="Focus next History row">→</button>
398
+ </div>
399
+ <label className="history-auto-open-toggle" title="Automatically sync the process drawer while stepping through History rows">
400
+ <input id="history-auto-open-toggle" type="checkbox" />
401
+ <span>Auto-open</span>
402
+ </label>
403
+ <label className="history-hint-density-wrap" title="Auto-open behavior while stepping through History rows">
404
+ <span className="history-density-label">Scope</span>
405
+ <select id="history-focus-scope-select" className="history-select history-hint-density-select">
406
+ <option value="sync">Sync drawer</option>
407
+ <option value="inspect">Inspect</option>
408
+ </select>
409
+ </label>
410
+ </div>
411
+ <div className="history-hints-actions">
412
+ <label className="history-hint-density-wrap" title="Keyboard hint density">
413
+ <span className="history-density-label">Hints</span>
414
+ <select id="history-hint-density-select" className="history-select history-hint-density-select">
415
+ <option value="full">Full</option>
416
+ <option value="compact">Compact</option>
417
+ </select>
418
+ </label>
419
+ <details className="history-hint-groups-menu">
420
+ <summary className="history-hints-toggle" title="Choose which History hint groups to show">Groups</summary>
421
+ <div className="history-hint-groups-panel">
422
+ <div className="history-hint-presets">
423
+ <button className="history-hint-preset" type="button" data-history-hint-preset="minimal">Minimal</button>
424
+ <button className="history-hint-preset" type="button" data-history-hint-preset="navigation">Navigation</button>
425
+ <button className="history-hint-preset" type="button" data-history-hint-preset="all">All</button>
426
+ <span className="history-hint-preset-state" id="history-hint-preset-state">Custom</span>
427
+ </div>
428
+ <label className="history-hint-group-option"><input id="history-hint-group-nav" type="checkbox" /> <span>Navigation</span></label>
429
+ <label className="history-hint-group-option"><input id="history-hint-group-open" type="checkbox" /> <span>Open</span></label>
430
+ <label className="history-hint-group-option"><input id="history-hint-group-filter" type="checkbox" /> <span>Filters</span></label>
431
+ <label className="history-hint-group-option"><input id="history-hint-group-details" type="checkbox" /> <span>Details</span></label>
432
+ <label className="history-hint-group-option"><input id="history-hint-group-close" type="checkbox" /> <span>Close</span></label>
433
+ </div>
434
+ </details>
435
+ <button className="history-hints-toggle" id="history-hints-toggle" type="button" title="Hide keyboard shortcut hints">Hide</button>
436
+ </div>
437
+ </div>
438
+ <div className="history-keyboard-hints" id="history-keyboard-hints" aria-label="History keyboard shortcuts">
439
+ <span className="history-keyboard-hint" data-hint-group="nav"><kbd>↑</kbd><kbd>↓</kbd><span>Move</span></span>
440
+ <span className="history-keyboard-hint" data-hint-group="open"><kbd>Enter</kbd><span>Open</span></span>
441
+ <span className="history-keyboard-hint" data-hint-group="filter"><kbd>F</kbd><span>Process filter</span></span>
442
+ <span className="history-keyboard-hint" data-hint-group="filter"><kbd>E</kbd><span>Event filter</span></span>
443
+ <span className="history-keyboard-hint" data-hint-group="details"><kbd>Space</kbd><span>Toggle details</span></span>
444
+ <span className="history-keyboard-hint" data-hint-group="close"><kbd>Esc</kbd><span>Close</span></span>
275
445
  </div>
276
446
  <div className="history-list" id="history-list">
277
447
  <div className="history-empty">No history yet</div>
@@ -324,6 +494,24 @@ export default function DashboardPage() {
324
494
  </div>
325
495
  </div>
326
496
 
497
+ {/* Deploy Results Modal */}
498
+ <div className="modal-overlay" id="deploy-results-modal">
499
+ <div className="modal modal-wide">
500
+ <div className="modal-header">
501
+ <h3>🚀 Deploy Results</h3>
502
+ <button className="modal-close" id="deploy-results-modal-close">✕</button>
503
+ </div>
504
+ <div className="modal-body">
505
+ <div className="deploy-results-summary" id="deploy-results-summary">
506
+ No deploy results yet
507
+ </div>
508
+ <div className="deploy-results-list" id="deploy-results-list">
509
+ <div className="history-empty">Run a bulk deploy to see detailed results</div>
510
+ </div>
511
+ </div>
512
+ </div>
513
+ </div>
514
+
327
515
  {/* Keyboard Shortcuts Overlay */}
328
516
  <div className="shortcuts-overlay" id="shortcuts-overlay">
329
517
  <div className="shortcuts-panel">
@@ -346,6 +534,7 @@ export default function DashboardPage() {
346
534
  <div className="shortcut-row"><kbd>G</kbd><span>Toggle guard</span></div>
347
535
  <div className="shortcut-row"><kbd>D</kbd><span>Delete process</span></div>
348
536
  <div className="shortcut-row"><kbd>N</kbd><span>New process</span></div>
537
+ <div className="shortcut-row"><kbd>T</kbd><span>Cycle stat filter</span></div>
349
538
  <div className="shortcut-row"><kbd>?</kbd><span>This help</span></div>
350
539
  </div>
351
540
  </div>
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ __export(exports_platform, {
25
25
  reconcileProcessPids: () => reconcileProcessPids,
26
26
  readFileTail: () => readFileTail,
27
27
  psExec: () => psExec,
28
+ parseUnixListeningPorts: () => parseUnixListeningPorts,
28
29
  killProcessOnPort: () => killProcessOnPort,
29
30
  isWindows: () => isWindows,
30
31
  isProcessRunning: () => isProcessRunning,
@@ -80,7 +81,8 @@ async function isProcessRunning(pid, command) {
80
81
  process.kill(pid, 0);
81
82
  return true;
82
83
  } catch {
83
- return false;
84
+ const output = psExec(`Get-Process -Id ${pid} -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id`).trim();
85
+ return output === String(pid);
84
86
  }
85
87
  } else {
86
88
  const result = await $`ps -p ${pid}`.nothrow().text();
@@ -456,6 +458,17 @@ async function getProcessBatchResources(pids) {
456
458
  return resourceMap;
457
459
  }) ?? new Map;
458
460
  }
461
+ function parseUnixListeningPorts(output) {
462
+ const ports = new Set;
463
+ for (const line of output.split(`
464
+ `)) {
465
+ const portMatch = line.match(/:(\d+)\s+\(LISTEN\)/);
466
+ if (portMatch) {
467
+ ports.add(parseInt(portMatch[1]));
468
+ }
469
+ }
470
+ return Array.from(ports);
471
+ }
459
472
  async function getProcessPorts(pid) {
460
473
  try {
461
474
  if (isWindows()) {
@@ -472,31 +485,21 @@ async function getProcessPorts(pid) {
472
485
  } else {
473
486
  try {
474
487
  const result2 = await $`ss -tlnp`.nothrow().quiet().text();
475
- const ports2 = new Set;
488
+ const ports = new Set;
476
489
  for (const line of result2.split(`
477
490
  `)) {
478
491
  if (line.includes(`pid=${pid}`)) {
479
492
  const portMatch = line.match(/:(\d+)\s/);
480
493
  if (portMatch) {
481
- ports2.add(parseInt(portMatch[1]));
494
+ ports.add(parseInt(portMatch[1]));
482
495
  }
483
496
  }
484
497
  }
485
- if (ports2.size > 0)
486
- return Array.from(ports2);
498
+ if (ports.size > 0)
499
+ return Array.from(ports);
487
500
  } catch {}
488
- const result = await $`lsof -i -P -n -p ${pid}`.nothrow().quiet().text();
489
- const ports = new Set;
490
- for (const line of result.split(`
491
- `)) {
492
- if (line.includes("LISTEN")) {
493
- const portMatch = line.match(/:(\d+)\s+\(LISTEN\)/);
494
- if (portMatch) {
495
- ports.add(parseInt(portMatch[1]));
496
- }
497
- }
498
- }
499
- return Array.from(ports);
501
+ const result = await $`lsof -Pan -p ${pid} -iTCP -sTCP:LISTEN`.nothrow().quiet().text();
502
+ return parseUnixListeningPorts(result);
500
503
  }
501
504
  } catch {
502
505
  return [];
@@ -595,12 +598,18 @@ __export(exports_db, {
595
598
  retryDatabaseOperation: () => retryDatabaseOperation,
596
599
  removeProcessByName: () => removeProcessByName,
597
600
  removeProcess: () => removeProcess,
601
+ removeDependency: () => removeDependency,
598
602
  removeAllProcesses: () => removeAllProcesses,
603
+ removeAllDependencies: () => removeAllDependencies,
599
604
  insertProcess: () => insertProcess,
600
605
  getTemplate: () => getTemplate,
606
+ getStartOrder: () => getStartOrder,
601
607
  getRecentHistory: () => getRecentHistory,
602
608
  getProcessHistory: () => getProcessHistory,
603
609
  getProcess: () => getProcess,
610
+ getDependents: () => getDependents,
611
+ getDependencyGraph: () => getDependencyGraph,
612
+ getDependencies: () => getDependencies,
604
613
  getDbInfo: () => getDbInfo,
605
614
  getAllTemplates: () => getAllTemplates,
606
615
  getAllProcesses: () => getAllProcesses,
@@ -610,9 +619,11 @@ __export(exports_db, {
610
619
  clearOldHistory: () => clearOldHistory,
611
620
  bgrHome: () => bgrHome,
612
621
  addHistoryEntry: () => addHistoryEntry,
622
+ addDependency: () => addDependency,
613
623
  TemplateSchema: () => TemplateSchema,
614
624
  ProcessSchema: () => ProcessSchema,
615
- HistorySchema: () => HistorySchema
625
+ HistorySchema: () => HistorySchema,
626
+ DependencySchema: () => DependencySchema
616
627
  });
617
628
  import { Database, z } from "sqlite-zod-orm";
618
629
  import { join as join2 } from "path";
@@ -715,6 +726,99 @@ function clearOldHistory(daysToKeep = 30) {
715
726
  }
716
727
  return oldEntries.length;
717
728
  }
729
+ function getDependencies(processName) {
730
+ return db.dependency.select().where({ process_name: processName }).all().map((d) => d.depends_on);
731
+ }
732
+ function getDependents(processName) {
733
+ return db.dependency.select().where({ depends_on: processName }).all().map((d) => d.process_name);
734
+ }
735
+ function getDependencyGraph() {
736
+ const all = db.dependency.select().all();
737
+ const graph = {};
738
+ for (const dep of all) {
739
+ if (!graph[dep.process_name])
740
+ graph[dep.process_name] = [];
741
+ graph[dep.process_name].push(dep.depends_on);
742
+ }
743
+ return graph;
744
+ }
745
+ function addDependency(processName, dependsOn) {
746
+ if (processName === dependsOn)
747
+ return false;
748
+ const existing = db.dependency.select().where({ process_name: processName, depends_on: dependsOn }).limit(1).get();
749
+ if (existing)
750
+ return false;
751
+ if (wouldCreateCycle(processName, dependsOn))
752
+ return false;
753
+ db.dependency.insert({ process_name: processName, depends_on: dependsOn });
754
+ return true;
755
+ }
756
+ function removeDependency(processName, dependsOn) {
757
+ const matches = db.dependency.select().where({ process_name: processName, depends_on: dependsOn }).all();
758
+ for (const dep of matches) {
759
+ db.dependency.delete(dep.id);
760
+ }
761
+ }
762
+ function removeAllDependencies(processName) {
763
+ const matches = db.dependency.select().where({ process_name: processName }).all();
764
+ for (const dep of matches) {
765
+ db.dependency.delete(dep.id);
766
+ }
767
+ }
768
+ function wouldCreateCycle(processName, dependsOn) {
769
+ const graph = getDependencyGraph();
770
+ if (!graph[processName])
771
+ graph[processName] = [];
772
+ graph[processName].push(dependsOn);
773
+ const visited = new Set;
774
+ const stack = [dependsOn];
775
+ while (stack.length > 0) {
776
+ const current = stack.pop();
777
+ if (current === processName)
778
+ return true;
779
+ if (visited.has(current))
780
+ continue;
781
+ visited.add(current);
782
+ for (const dep of graph[current] || []) {
783
+ stack.push(dep);
784
+ }
785
+ }
786
+ return false;
787
+ }
788
+ function getStartOrder() {
789
+ const graph = getDependencyGraph();
790
+ const allProcesses = getAllProcesses().map((p) => p.name);
791
+ const allNames = new Set(allProcesses);
792
+ const inDegree = {};
793
+ for (const name of allNames)
794
+ inDegree[name] = 0;
795
+ for (const [proc, deps] of Object.entries(graph)) {
796
+ for (const dep of deps) {
797
+ if (allNames.has(dep)) {
798
+ inDegree[proc] = (inDegree[proc] || 0) + 1;
799
+ }
800
+ }
801
+ }
802
+ const queue = [];
803
+ for (const name of allNames) {
804
+ if ((inDegree[name] || 0) === 0)
805
+ queue.push(name);
806
+ }
807
+ const order = [];
808
+ while (queue.length > 0) {
809
+ queue.sort();
810
+ const current = queue.shift();
811
+ order.push(current);
812
+ for (const [proc, deps] of Object.entries(graph)) {
813
+ if (deps.includes(current) && allNames.has(proc)) {
814
+ inDegree[proc]--;
815
+ if (inDegree[proc] === 0)
816
+ queue.push(proc);
817
+ }
818
+ }
819
+ }
820
+ return order;
821
+ }
718
822
  function getDbInfo() {
719
823
  return {
720
824
  dbPath,
@@ -737,7 +841,7 @@ async function retryDatabaseOperation(operation, maxRetries = 5, delay = 100) {
737
841
  }
738
842
  throw new Error("Max retries reached for database operation");
739
843
  }
740
- var ProcessSchema, TemplateSchema, HistorySchema, homePath, bgrDir, dbFilename, dbPath, bgrHome, legacyDbPath, db;
844
+ var ProcessSchema, TemplateSchema, HistorySchema, DependencySchema, homePath, bgrDir, dbFilename, dbPath, bgrHome, legacyDbPath, db;
741
845
  var init_db = __esm(() => {
742
846
  init_platform();
743
847
  ProcessSchema = z.object({
@@ -767,6 +871,11 @@ var init_db = __esm(() => {
767
871
  timestamp: z.string().default(() => new Date().toISOString()),
768
872
  metadata: z.string().default("")
769
873
  });
874
+ DependencySchema = z.object({
875
+ process_name: z.string(),
876
+ depends_on: z.string(),
877
+ created_at: z.string().default(() => new Date().toISOString())
878
+ });
770
879
  homePath = getHomeDir();
771
880
  bgrDir = join2(homePath, ".bgr");
772
881
  ensureDir(bgrDir);
@@ -783,12 +892,14 @@ var init_db = __esm(() => {
783
892
  db = new Database(dbPath, {
784
893
  process: ProcessSchema,
785
894
  template: TemplateSchema,
786
- history: HistorySchema
895
+ history: HistorySchema,
896
+ dependency: DependencySchema
787
897
  }, {
788
898
  indexes: {
789
899
  process: ["name", "timestamp", "pid"],
790
900
  template: ["name"],
791
- history: ["process_name", "timestamp"]
901
+ history: ["process_name", "timestamp"],
902
+ dependency: ["process_name", "depends_on"]
792
903
  }
793
904
  });
794
905
  });
@@ -863,10 +974,10 @@ async function parseConfigFile(configPath) {
863
974
  var exports_deps = {};
864
975
  __export(exports_deps, {
865
976
  getUnmetDeps: () => getUnmetDeps,
866
- getDependencies: () => getDependencies,
977
+ getDependencies: () => getDependencies2,
867
978
  buildDepGraph: () => buildDepGraph
868
979
  });
869
- function getDependencies(envStr) {
980
+ function getDependencies2(envStr) {
870
981
  const env = parseEnvString(envStr);
871
982
  const raw = env.BGR_DEPENDS_ON || "";
872
983
  return raw.split(",").map((s) => s.trim()).filter(Boolean);
@@ -875,7 +986,7 @@ async function buildDepGraph() {
875
986
  const processes = getAllProcesses();
876
987
  const nodeMap = new Map;
877
988
  for (const proc of processes) {
878
- const deps = getDependencies(proc.env);
989
+ const deps = getDependencies2(proc.env);
879
990
  const alive = await isProcessRunning(proc.pid, proc.command);
880
991
  nodeMap.set(proc.name, {
881
992
  name: proc.name,
@@ -927,7 +1038,7 @@ async function getUnmetDeps(name) {
927
1038
  const proc = getProcess(name);
928
1039
  if (!proc)
929
1040
  return [];
930
- const deps = getDependencies(proc.env);
1041
+ const deps = getDependencies2(proc.env);
931
1042
  const unmet = [];
932
1043
  for (const depName of deps) {
933
1044
  const depProc = getProcess(depName);
@@ -2677,6 +2788,13 @@ async function run2() {
2677
2788
  });
2678
2789
  return;
2679
2790
  }
2791
+ if (name === "list") {
2792
+ await showAll({
2793
+ json: values.json,
2794
+ filter: values.filter
2795
+ });
2796
+ return;
2797
+ }
2680
2798
  if (name) {
2681
2799
  if (!values.command && !values.directory) {
2682
2800
  await showDetails(name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bgrun",
3
- "version": "3.12.1",
3
+ "version": "3.12.3",
4
4
  "description": "bgrun — A lightweight process manager for Bun",
5
5
  "type": "module",
6
6
  "main": "./src/api.ts",