bgrun 3.12.1 → 3.12.2
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/dashboard/app/api/check-port/route.ts +35 -0
- package/dashboard/app/api/dependencies/route.ts +40 -0
- package/dashboard/app/api/deploy/[name]/route.ts +6 -41
- package/dashboard/app/api/deploy-all/route.ts +25 -0
- package/dashboard/app/api/next-port/route.ts +32 -0
- package/dashboard/app/api/start/route.ts +6 -0
- package/dashboard/app/globals.css +1313 -108
- package/dashboard/app/page.client.tsx +1576 -8
- package/dashboard/app/page.tsx +194 -5
- package/dist/index.js +129 -15
- package/package.json +1 -1
- package/src/bgrun.test.ts +171 -0
- package/src/db.ts +142 -0
- package/src/deploy.ts +163 -0
- package/src/index.ts +9 -0
- package/src/platform.ts +17 -9
package/dashboard/app/page.tsx
CHANGED
|
@@ -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
|
@@ -80,7 +80,8 @@ async function isProcessRunning(pid, command) {
|
|
|
80
80
|
process.kill(pid, 0);
|
|
81
81
|
return true;
|
|
82
82
|
} catch {
|
|
83
|
-
|
|
83
|
+
const output = psExec(`Get-Process -Id ${pid} -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id`).trim();
|
|
84
|
+
return output === String(pid);
|
|
84
85
|
}
|
|
85
86
|
} else {
|
|
86
87
|
const result = await $`ps -p ${pid}`.nothrow().text();
|
|
@@ -485,15 +486,13 @@ async function getProcessPorts(pid) {
|
|
|
485
486
|
if (ports2.size > 0)
|
|
486
487
|
return Array.from(ports2);
|
|
487
488
|
} catch {}
|
|
488
|
-
const result = await $`lsof -
|
|
489
|
+
const result = await $`lsof -Pan -p ${pid} -iTCP -sTCP:LISTEN`.nothrow().quiet().text();
|
|
489
490
|
const ports = new Set;
|
|
490
491
|
for (const line of result.split(`
|
|
491
492
|
`)) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
ports.add(parseInt(portMatch[1]));
|
|
496
|
-
}
|
|
493
|
+
const portMatch = line.match(/:(\d+)\s+\(LISTEN\)/);
|
|
494
|
+
if (portMatch) {
|
|
495
|
+
ports.add(parseInt(portMatch[1]));
|
|
497
496
|
}
|
|
498
497
|
}
|
|
499
498
|
return Array.from(ports);
|
|
@@ -595,12 +594,18 @@ __export(exports_db, {
|
|
|
595
594
|
retryDatabaseOperation: () => retryDatabaseOperation,
|
|
596
595
|
removeProcessByName: () => removeProcessByName,
|
|
597
596
|
removeProcess: () => removeProcess,
|
|
597
|
+
removeDependency: () => removeDependency,
|
|
598
598
|
removeAllProcesses: () => removeAllProcesses,
|
|
599
|
+
removeAllDependencies: () => removeAllDependencies,
|
|
599
600
|
insertProcess: () => insertProcess,
|
|
600
601
|
getTemplate: () => getTemplate,
|
|
602
|
+
getStartOrder: () => getStartOrder,
|
|
601
603
|
getRecentHistory: () => getRecentHistory,
|
|
602
604
|
getProcessHistory: () => getProcessHistory,
|
|
603
605
|
getProcess: () => getProcess,
|
|
606
|
+
getDependents: () => getDependents,
|
|
607
|
+
getDependencyGraph: () => getDependencyGraph,
|
|
608
|
+
getDependencies: () => getDependencies,
|
|
604
609
|
getDbInfo: () => getDbInfo,
|
|
605
610
|
getAllTemplates: () => getAllTemplates,
|
|
606
611
|
getAllProcesses: () => getAllProcesses,
|
|
@@ -610,9 +615,11 @@ __export(exports_db, {
|
|
|
610
615
|
clearOldHistory: () => clearOldHistory,
|
|
611
616
|
bgrHome: () => bgrHome,
|
|
612
617
|
addHistoryEntry: () => addHistoryEntry,
|
|
618
|
+
addDependency: () => addDependency,
|
|
613
619
|
TemplateSchema: () => TemplateSchema,
|
|
614
620
|
ProcessSchema: () => ProcessSchema,
|
|
615
|
-
HistorySchema: () => HistorySchema
|
|
621
|
+
HistorySchema: () => HistorySchema,
|
|
622
|
+
DependencySchema: () => DependencySchema
|
|
616
623
|
});
|
|
617
624
|
import { Database, z } from "sqlite-zod-orm";
|
|
618
625
|
import { join as join2 } from "path";
|
|
@@ -715,6 +722,99 @@ function clearOldHistory(daysToKeep = 30) {
|
|
|
715
722
|
}
|
|
716
723
|
return oldEntries.length;
|
|
717
724
|
}
|
|
725
|
+
function getDependencies(processName) {
|
|
726
|
+
return db.dependency.select().where({ process_name: processName }).all().map((d) => d.depends_on);
|
|
727
|
+
}
|
|
728
|
+
function getDependents(processName) {
|
|
729
|
+
return db.dependency.select().where({ depends_on: processName }).all().map((d) => d.process_name);
|
|
730
|
+
}
|
|
731
|
+
function getDependencyGraph() {
|
|
732
|
+
const all = db.dependency.select().all();
|
|
733
|
+
const graph = {};
|
|
734
|
+
for (const dep of all) {
|
|
735
|
+
if (!graph[dep.process_name])
|
|
736
|
+
graph[dep.process_name] = [];
|
|
737
|
+
graph[dep.process_name].push(dep.depends_on);
|
|
738
|
+
}
|
|
739
|
+
return graph;
|
|
740
|
+
}
|
|
741
|
+
function addDependency(processName, dependsOn) {
|
|
742
|
+
if (processName === dependsOn)
|
|
743
|
+
return false;
|
|
744
|
+
const existing = db.dependency.select().where({ process_name: processName, depends_on: dependsOn }).limit(1).get();
|
|
745
|
+
if (existing)
|
|
746
|
+
return false;
|
|
747
|
+
if (wouldCreateCycle(processName, dependsOn))
|
|
748
|
+
return false;
|
|
749
|
+
db.dependency.insert({ process_name: processName, depends_on: dependsOn });
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
function removeDependency(processName, dependsOn) {
|
|
753
|
+
const matches = db.dependency.select().where({ process_name: processName, depends_on: dependsOn }).all();
|
|
754
|
+
for (const dep of matches) {
|
|
755
|
+
db.dependency.delete(dep.id);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
function removeAllDependencies(processName) {
|
|
759
|
+
const matches = db.dependency.select().where({ process_name: processName }).all();
|
|
760
|
+
for (const dep of matches) {
|
|
761
|
+
db.dependency.delete(dep.id);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
function wouldCreateCycle(processName, dependsOn) {
|
|
765
|
+
const graph = getDependencyGraph();
|
|
766
|
+
if (!graph[processName])
|
|
767
|
+
graph[processName] = [];
|
|
768
|
+
graph[processName].push(dependsOn);
|
|
769
|
+
const visited = new Set;
|
|
770
|
+
const stack = [dependsOn];
|
|
771
|
+
while (stack.length > 0) {
|
|
772
|
+
const current = stack.pop();
|
|
773
|
+
if (current === processName)
|
|
774
|
+
return true;
|
|
775
|
+
if (visited.has(current))
|
|
776
|
+
continue;
|
|
777
|
+
visited.add(current);
|
|
778
|
+
for (const dep of graph[current] || []) {
|
|
779
|
+
stack.push(dep);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
function getStartOrder() {
|
|
785
|
+
const graph = getDependencyGraph();
|
|
786
|
+
const allProcesses = getAllProcesses().map((p) => p.name);
|
|
787
|
+
const allNames = new Set(allProcesses);
|
|
788
|
+
const inDegree = {};
|
|
789
|
+
for (const name of allNames)
|
|
790
|
+
inDegree[name] = 0;
|
|
791
|
+
for (const [proc, deps] of Object.entries(graph)) {
|
|
792
|
+
for (const dep of deps) {
|
|
793
|
+
if (allNames.has(dep)) {
|
|
794
|
+
inDegree[proc] = (inDegree[proc] || 0) + 1;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
const queue = [];
|
|
799
|
+
for (const name of allNames) {
|
|
800
|
+
if ((inDegree[name] || 0) === 0)
|
|
801
|
+
queue.push(name);
|
|
802
|
+
}
|
|
803
|
+
const order = [];
|
|
804
|
+
while (queue.length > 0) {
|
|
805
|
+
queue.sort();
|
|
806
|
+
const current = queue.shift();
|
|
807
|
+
order.push(current);
|
|
808
|
+
for (const [proc, deps] of Object.entries(graph)) {
|
|
809
|
+
if (deps.includes(current) && allNames.has(proc)) {
|
|
810
|
+
inDegree[proc]--;
|
|
811
|
+
if (inDegree[proc] === 0)
|
|
812
|
+
queue.push(proc);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return order;
|
|
817
|
+
}
|
|
718
818
|
function getDbInfo() {
|
|
719
819
|
return {
|
|
720
820
|
dbPath,
|
|
@@ -737,7 +837,7 @@ async function retryDatabaseOperation(operation, maxRetries = 5, delay = 100) {
|
|
|
737
837
|
}
|
|
738
838
|
throw new Error("Max retries reached for database operation");
|
|
739
839
|
}
|
|
740
|
-
var ProcessSchema, TemplateSchema, HistorySchema, homePath, bgrDir, dbFilename, dbPath, bgrHome, legacyDbPath, db;
|
|
840
|
+
var ProcessSchema, TemplateSchema, HistorySchema, DependencySchema, homePath, bgrDir, dbFilename, dbPath, bgrHome, legacyDbPath, db;
|
|
741
841
|
var init_db = __esm(() => {
|
|
742
842
|
init_platform();
|
|
743
843
|
ProcessSchema = z.object({
|
|
@@ -767,6 +867,11 @@ var init_db = __esm(() => {
|
|
|
767
867
|
timestamp: z.string().default(() => new Date().toISOString()),
|
|
768
868
|
metadata: z.string().default("")
|
|
769
869
|
});
|
|
870
|
+
DependencySchema = z.object({
|
|
871
|
+
process_name: z.string(),
|
|
872
|
+
depends_on: z.string(),
|
|
873
|
+
created_at: z.string().default(() => new Date().toISOString())
|
|
874
|
+
});
|
|
770
875
|
homePath = getHomeDir();
|
|
771
876
|
bgrDir = join2(homePath, ".bgr");
|
|
772
877
|
ensureDir(bgrDir);
|
|
@@ -783,12 +888,14 @@ var init_db = __esm(() => {
|
|
|
783
888
|
db = new Database(dbPath, {
|
|
784
889
|
process: ProcessSchema,
|
|
785
890
|
template: TemplateSchema,
|
|
786
|
-
history: HistorySchema
|
|
891
|
+
history: HistorySchema,
|
|
892
|
+
dependency: DependencySchema
|
|
787
893
|
}, {
|
|
788
894
|
indexes: {
|
|
789
895
|
process: ["name", "timestamp", "pid"],
|
|
790
896
|
template: ["name"],
|
|
791
|
-
history: ["process_name", "timestamp"]
|
|
897
|
+
history: ["process_name", "timestamp"],
|
|
898
|
+
dependency: ["process_name", "depends_on"]
|
|
792
899
|
}
|
|
793
900
|
});
|
|
794
901
|
});
|
|
@@ -863,10 +970,10 @@ async function parseConfigFile(configPath) {
|
|
|
863
970
|
var exports_deps = {};
|
|
864
971
|
__export(exports_deps, {
|
|
865
972
|
getUnmetDeps: () => getUnmetDeps,
|
|
866
|
-
getDependencies: () =>
|
|
973
|
+
getDependencies: () => getDependencies2,
|
|
867
974
|
buildDepGraph: () => buildDepGraph
|
|
868
975
|
});
|
|
869
|
-
function
|
|
976
|
+
function getDependencies2(envStr) {
|
|
870
977
|
const env = parseEnvString(envStr);
|
|
871
978
|
const raw = env.BGR_DEPENDS_ON || "";
|
|
872
979
|
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -875,7 +982,7 @@ async function buildDepGraph() {
|
|
|
875
982
|
const processes = getAllProcesses();
|
|
876
983
|
const nodeMap = new Map;
|
|
877
984
|
for (const proc of processes) {
|
|
878
|
-
const deps =
|
|
985
|
+
const deps = getDependencies2(proc.env);
|
|
879
986
|
const alive = await isProcessRunning(proc.pid, proc.command);
|
|
880
987
|
nodeMap.set(proc.name, {
|
|
881
988
|
name: proc.name,
|
|
@@ -927,7 +1034,7 @@ async function getUnmetDeps(name) {
|
|
|
927
1034
|
const proc = getProcess(name);
|
|
928
1035
|
if (!proc)
|
|
929
1036
|
return [];
|
|
930
|
-
const deps =
|
|
1037
|
+
const deps = getDependencies2(proc.env);
|
|
931
1038
|
const unmet = [];
|
|
932
1039
|
for (const depName of deps) {
|
|
933
1040
|
const depProc = getProcess(depName);
|
|
@@ -2677,6 +2784,13 @@ async function run2() {
|
|
|
2677
2784
|
});
|
|
2678
2785
|
return;
|
|
2679
2786
|
}
|
|
2787
|
+
if (name === "list") {
|
|
2788
|
+
await showAll({
|
|
2789
|
+
json: values.json,
|
|
2790
|
+
filter: values.filter
|
|
2791
|
+
});
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2680
2794
|
if (name) {
|
|
2681
2795
|
if (!values.command && !values.directory) {
|
|
2682
2796
|
await showDetails(name);
|