@nebulord/sickbay 0.1.0
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/README.md +231 -0
- package/dist/DiffApp-YF2PYQZK.js +187 -0
- package/dist/DoctorApp-U465IMK7.js +447 -0
- package/dist/FixApp-RJPCWNXJ.js +344 -0
- package/dist/StatsApp-BI6COY7S.js +375 -0
- package/dist/TrendApp-XL77HKDR.js +118 -0
- package/dist/TuiApp-VJNV4FD3.js +982 -0
- package/dist/ai-7DGOLNJX.js +64 -0
- package/dist/badge-KQ73KEIN.js +41 -0
- package/dist/chunk-5KJOYSVJ.js +95 -0
- package/dist/chunk-BIK4EL4H.js +19 -0
- package/dist/chunk-BUD5BE6U.js +61 -0
- package/dist/chunk-D24FSOW4.js +22 -0
- package/dist/chunk-POUHUMJN.js +21 -0
- package/dist/chunk-SSUXSMGH.js +25 -0
- package/dist/history-DYFJ65XH.js +14 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +535 -0
- package/dist/init-J2NPRPDO.js +54 -0
- package/dist/resolve-package-PHPJWOLY.js +8 -0
- package/dist/web-EE2VYPEX.js +198 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# @nebulord/sickbay
|
|
2
|
+
|
|
3
|
+
The terminal interface for Sickbay. Built with [Ink](https://github.com/vadimdemedes/ink) (React for terminals) and [Commander](https://github.com/tj/commander.js).
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
sickbay [options]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Commands
|
|
12
|
+
|
|
13
|
+
| Command | Description |
|
|
14
|
+
| ------------------ | ------------------------------------------------------------------ |
|
|
15
|
+
| `init [options]` | Scaffold `.sickbay/`, run baseline scan, seed history |
|
|
16
|
+
| `fix [options]` | Interactively fix issues found by sickbay scan |
|
|
17
|
+
| `trend [options]` | Show score history and trends over time |
|
|
18
|
+
| `stats [options]` | Show a quick codebase overview and project summary |
|
|
19
|
+
| `doctor [options]` | Diagnose project setup and configuration issues |
|
|
20
|
+
| `tui [options]` | Persistent live dashboard with file watching and activity tracking |
|
|
21
|
+
|
|
22
|
+
### Flags
|
|
23
|
+
|
|
24
|
+
| Flag | Default | Description |
|
|
25
|
+
| ---------------------- | --------------- | --------------------------------- |
|
|
26
|
+
| `-p, --path <path>` | `process.cwd()` | Path to the project to analyze |
|
|
27
|
+
| `-c, --checks <names>` | all | Comma-separated check IDs to run |
|
|
28
|
+
| `--json` | false | Output raw JSON to stdout (no UI) |
|
|
29
|
+
| `--web` | false | Open web dashboard after scan |
|
|
30
|
+
| `--verbose` | false | Show tool output during checks |
|
|
31
|
+
| `-V, --version` | | Print version |
|
|
32
|
+
| `-h, --help` | | Show help |
|
|
33
|
+
|
|
34
|
+
### Examples
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Analyze current directory
|
|
38
|
+
sickbay
|
|
39
|
+
|
|
40
|
+
# Analyze another project
|
|
41
|
+
sickbay -p ~/projects/my-app
|
|
42
|
+
|
|
43
|
+
# Run specific checks only
|
|
44
|
+
sickbay --checks knip,npm-audit,depcheck
|
|
45
|
+
|
|
46
|
+
# JSON output for CI
|
|
47
|
+
sickbay --json | jq '.overallScore'
|
|
48
|
+
|
|
49
|
+
# Get just the summary
|
|
50
|
+
sickbay --json | jq '.summary'
|
|
51
|
+
|
|
52
|
+
# List all check names and their scores
|
|
53
|
+
sickbay --json | jq '.checks[] | {name, score}'
|
|
54
|
+
|
|
55
|
+
# Get only failing checks
|
|
56
|
+
sickbay --json | jq '.checks[] | select(.status == "fail")'
|
|
57
|
+
|
|
58
|
+
# Open web dashboard
|
|
59
|
+
sickbay --web
|
|
60
|
+
|
|
61
|
+
# Initialize .sickbay/ folder with baseline scan
|
|
62
|
+
sickbay init
|
|
63
|
+
|
|
64
|
+
# Initialize for a specific project
|
|
65
|
+
sickbay init --path ~/projects/my-app
|
|
66
|
+
|
|
67
|
+
# Interactively fix issues
|
|
68
|
+
sickbay fix
|
|
69
|
+
|
|
70
|
+
# View score history and trends
|
|
71
|
+
sickbay trend
|
|
72
|
+
|
|
73
|
+
# Get quick project stats
|
|
74
|
+
sickbay stats
|
|
75
|
+
|
|
76
|
+
# Diagnose project setup
|
|
77
|
+
sickbay doctor
|
|
78
|
+
|
|
79
|
+
# Launch tui dashboard (current directory, file watching enabled)
|
|
80
|
+
sickbay tui
|
|
81
|
+
|
|
82
|
+
# TUI for a specific project, disable file watching
|
|
83
|
+
sickbay tui --path ~/projects/my-app --no-watch
|
|
84
|
+
|
|
85
|
+
# TUI with faster auto-refresh (60 seconds) and specific checks only
|
|
86
|
+
sickbay tui --path ~/projects/my-app --refresh 60 --checks knip,npm-audit,eslint
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## `sickbay init` vs `sickbay`
|
|
90
|
+
|
|
91
|
+
**Run `sickbay init` once when setting up a project for the first time.**
|
|
92
|
+
|
|
93
|
+
It scaffolds the `.sickbay/` data folder, saves a `baseline.json` snapshot of the project's current health, and wires up `.gitignore` entries so `history.json` doesn't pollute your repo. Think of it as "onboarding" Sickbay to a project.
|
|
94
|
+
|
|
95
|
+
**Run `sickbay` for every subsequent scan.**
|
|
96
|
+
|
|
97
|
+
Each scan automatically appends an entry to `.sickbay/history.json`, so your score trend builds up over time without any extra steps. The History tab in the web dashboard (`sickbay --web`) reads from this file.
|
|
98
|
+
|
|
99
|
+
| | First time | Ongoing |
|
|
100
|
+
| ------------------------- | ------------- | -------------- |
|
|
101
|
+
| Command | `sickbay init` | `sickbay` |
|
|
102
|
+
| Creates `.sickbay/` | ✓ | ✓ (if missing) |
|
|
103
|
+
| Saves `baseline.json` | ✓ | ✗ |
|
|
104
|
+
| Updates root `.gitignore` | ✓ | ✗ |
|
|
105
|
+
| Appends to `history.json` | ✓ | ✓ |
|
|
106
|
+
|
|
107
|
+
> If you skip `sickbay init` and go straight to `sickbay`, history will still accumulate — you just won't have a baseline snapshot or gitignore entries for `.sickbay/`. But you can always ignore it manually.
|
|
108
|
+
|
|
109
|
+
## TUI Dashboard
|
|
110
|
+
|
|
111
|
+
`sickbay tui` opens a persistent split-pane TUI that continuously monitors your project. Unlike a one-shot scan, it stays running, watches for file changes, and lets you interact with results in real time.
|
|
112
|
+
|
|
113
|
+
### TUI Flags
|
|
114
|
+
|
|
115
|
+
| Flag | Default | Description |
|
|
116
|
+
| ----------------------- | --------------- | ------------------------------------- |
|
|
117
|
+
| `-p, --path <path>` | `process.cwd()` | Project path to monitor |
|
|
118
|
+
| `--no-watch` | watch enabled | Disable file-watching auto-refresh |
|
|
119
|
+
| `--refresh <seconds>` | `300` | Auto-refresh interval in seconds |
|
|
120
|
+
| `-c, --checks <checks>` | all | Comma-separated list of checks to run |
|
|
121
|
+
|
|
122
|
+
### Panels
|
|
123
|
+
|
|
124
|
+
The tui displays six panels arranged in a responsive grid:
|
|
125
|
+
|
|
126
|
+
| Panel | Key | Content |
|
|
127
|
+
| -------------- | --- | -------------------------------------------------------------------------- |
|
|
128
|
+
| **Health** | `h` | All check results with status icons, names, and score bars |
|
|
129
|
+
| **Score** | — | Overall score (0–100), color-coded status, issue counts, score delta |
|
|
130
|
+
| **Trend** | `t` | Sparkline charts for overall score and each category (last 10 scans) |
|
|
131
|
+
| **Git** | `g` | Branch, commits ahead/behind, modified/staged/untracked files, last commit |
|
|
132
|
+
| **Quick Wins** | `q` | Top 5 actionable fixes prioritized by severity |
|
|
133
|
+
| **Activity** | `a` | Time-stamped event log (scans, file changes, regressions, git changes) |
|
|
134
|
+
|
|
135
|
+
### Keyboard Controls
|
|
136
|
+
|
|
137
|
+
| Key | Action |
|
|
138
|
+
| -------- | ------------------------------------------ |
|
|
139
|
+
| `r` | Manually trigger a rescan |
|
|
140
|
+
| `w` | Launch web dashboard (without AI) |
|
|
141
|
+
| `W` | Launch web dashboard with AI analysis |
|
|
142
|
+
| `f` | Expand focused panel to fullscreen |
|
|
143
|
+
| `Escape` | Unfocus current panel |
|
|
144
|
+
| `↑ / ↓` | Scroll Health Panel results (when focused) |
|
|
145
|
+
| `h` | Focus Health panel |
|
|
146
|
+
| `g` | Focus Git panel |
|
|
147
|
+
| `t` | Focus Trend panel |
|
|
148
|
+
| `q` | Focus Quick Wins panel |
|
|
149
|
+
| `a` | Focus Activity panel |
|
|
150
|
+
|
|
151
|
+
### Automatic Triggers
|
|
152
|
+
|
|
153
|
+
- **Startup** — Initial scan runs immediately
|
|
154
|
+
- **File watch** — Rescans when TypeScript, JavaScript, or JSON files change (debounced 2s)
|
|
155
|
+
- **Auto-refresh** — Periodic rescan at the configured interval (default 5 minutes)
|
|
156
|
+
- **Regression detection** — Activity panel flags category score decreases automatically
|
|
157
|
+
|
|
158
|
+
## Architecture
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
src/
|
|
162
|
+
├── index.ts # Commander entry — parses flags, renders Ink <App>
|
|
163
|
+
├── commands/
|
|
164
|
+
│ └── web.ts # HTTP server (Node built-in) for the dashboard
|
|
165
|
+
└── components/
|
|
166
|
+
├── App.tsx # Root Ink component — manages phases & state
|
|
167
|
+
├── Header.tsx # ASCII art banner + project name
|
|
168
|
+
├── ProgressList.tsx # Animated check progress (pending → running → done)
|
|
169
|
+
├── CheckResult.tsx # Single check: name, status, score bar, issues
|
|
170
|
+
├── ScoreBar.tsx # Colored horizontal bar (green/yellow/red)
|
|
171
|
+
├── Summary.tsx # Overall score + issue counts
|
|
172
|
+
├── QuickWins.tsx # Top actionable fix suggestions
|
|
173
|
+
└── tui/
|
|
174
|
+
├── TUIApp.tsx # TUI root — layout, keyboard input, state
|
|
175
|
+
├── HealthPanel.tsx # Check results with status icons and score bars
|
|
176
|
+
├── ScorePanel.tsx # Overall score, issue counts, delta from last scan
|
|
177
|
+
├── TrendPanel.tsx # Sparkline charts for score history (last 10 scans)
|
|
178
|
+
├── GitPanel.tsx # Branch, ahead/behind, staged/modified file counts
|
|
179
|
+
├── QuickWinsPanel.tsx # Top 5 actionable fixes by severity
|
|
180
|
+
├── ActivityPanel.tsx # Timestamped event log
|
|
181
|
+
├── HotkeyBar.tsx # Fixed footer with keyboard shortcut reference
|
|
182
|
+
├── PanelBorder.tsx # Focused/unfocused border styling
|
|
183
|
+
└── hooks/
|
|
184
|
+
├── useSickbayRunner.ts # Manages check execution and scan state
|
|
185
|
+
├── useFileWatcher.ts # chokidar file watcher with debounce
|
|
186
|
+
├── useGitStatus.ts # Polls git status every 10 seconds
|
|
187
|
+
└── useTerminalSize.ts # Tracks terminal dimensions for responsive layout
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### UI Phases
|
|
191
|
+
|
|
192
|
+
The `<App>` component cycles through phases:
|
|
193
|
+
|
|
194
|
+
1. **`loading`** — Shows progress list with animated spinners while checks run
|
|
195
|
+
2. **`results`** — Displays all check results + summary + quick wins
|
|
196
|
+
3. **`opening-web`** — Starts HTTP server, opens browser, stays alive until Ctrl+C
|
|
197
|
+
4. **`error`** — Shows error message and exits
|
|
198
|
+
|
|
199
|
+
### `--web` flag flow
|
|
200
|
+
|
|
201
|
+
When `--web` is passed:
|
|
202
|
+
|
|
203
|
+
1. Scan completes normally
|
|
204
|
+
2. `serveWeb(report)` starts an HTTP server on port 3030 (or next free port)
|
|
205
|
+
3. Server serves `packages/web/dist/` as static files
|
|
206
|
+
4. Server responds to `GET /sickbay-report.json` with the in-memory report
|
|
207
|
+
5. `open` package opens the browser
|
|
208
|
+
6. Process stays alive until Ctrl+C
|
|
209
|
+
|
|
210
|
+
### `--json` flag flow
|
|
211
|
+
|
|
212
|
+
Skips the Ink UI entirely, writes `JSON.stringify(report, null, 2)` to stdout, then exits.
|
|
213
|
+
|
|
214
|
+
## Local Development
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# Watch mode — rebuilds on file changes
|
|
218
|
+
pnpm dev
|
|
219
|
+
|
|
220
|
+
# Test against a project
|
|
221
|
+
node dist/index.js --path ~/Desktop/sickbay-test-app
|
|
222
|
+
node dist/index.js --path ~/Desktop/sickbay-test-app --web
|
|
223
|
+
node dist/index.js --path ~/Desktop/sickbay-test-app --json
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Build
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
pnpm build # tsup → dist/index.js + dist/web-*.js (code-split)
|
|
230
|
+
pnpm clean # rm -rf dist/
|
|
231
|
+
```
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Header
|
|
3
|
+
} from "./chunk-BIK4EL4H.js";
|
|
4
|
+
|
|
5
|
+
// src/components/DiffApp.tsx
|
|
6
|
+
import React, { useState, useEffect } from "react";
|
|
7
|
+
import { Box, Text, useApp } from "ink";
|
|
8
|
+
import Spinner from "ink-spinner";
|
|
9
|
+
|
|
10
|
+
// src/commands/diff.ts
|
|
11
|
+
import { execFileSync } from "child_process";
|
|
12
|
+
function loadBaseReport(projectPath, branch) {
|
|
13
|
+
try {
|
|
14
|
+
const output = execFileSync(
|
|
15
|
+
"git",
|
|
16
|
+
["show", `${branch}:.sickbay/last-report.json`],
|
|
17
|
+
{ cwd: projectPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
18
|
+
);
|
|
19
|
+
return JSON.parse(output);
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
var STATUS_ORDER = {
|
|
25
|
+
regressed: 0,
|
|
26
|
+
improved: 1,
|
|
27
|
+
new: 2,
|
|
28
|
+
removed: 3,
|
|
29
|
+
unchanged: 4
|
|
30
|
+
};
|
|
31
|
+
function compareReports(current, base, branch) {
|
|
32
|
+
const baseMap = new Map(base.checks.map((c) => [c.id, c]));
|
|
33
|
+
const currentMap = new Map(current.checks.map((c) => [c.id, c]));
|
|
34
|
+
const checks = [];
|
|
35
|
+
for (const check of current.checks) {
|
|
36
|
+
const baseCheck = baseMap.get(check.id);
|
|
37
|
+
if (!baseCheck) {
|
|
38
|
+
checks.push({
|
|
39
|
+
id: check.id,
|
|
40
|
+
name: check.name,
|
|
41
|
+
category: check.category,
|
|
42
|
+
currentScore: check.score,
|
|
43
|
+
baseScore: 0,
|
|
44
|
+
delta: check.score,
|
|
45
|
+
status: "new"
|
|
46
|
+
});
|
|
47
|
+
} else {
|
|
48
|
+
const delta = check.score - baseCheck.score;
|
|
49
|
+
checks.push({
|
|
50
|
+
id: check.id,
|
|
51
|
+
name: check.name,
|
|
52
|
+
category: check.category,
|
|
53
|
+
currentScore: check.score,
|
|
54
|
+
baseScore: baseCheck.score,
|
|
55
|
+
delta,
|
|
56
|
+
status: delta > 0 ? "improved" : delta < 0 ? "regressed" : "unchanged"
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const check of base.checks) {
|
|
61
|
+
if (!currentMap.has(check.id)) {
|
|
62
|
+
checks.push({
|
|
63
|
+
id: check.id,
|
|
64
|
+
name: check.name,
|
|
65
|
+
category: check.category,
|
|
66
|
+
currentScore: 0,
|
|
67
|
+
baseScore: check.score,
|
|
68
|
+
delta: -check.score,
|
|
69
|
+
status: "removed"
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
checks.sort((a, b) => STATUS_ORDER[a.status] - STATUS_ORDER[b.status]);
|
|
74
|
+
const summary = {
|
|
75
|
+
improved: checks.filter((c) => c.status === "improved").length,
|
|
76
|
+
regressed: checks.filter((c) => c.status === "regressed").length,
|
|
77
|
+
unchanged: checks.filter((c) => c.status === "unchanged").length,
|
|
78
|
+
newChecks: checks.filter((c) => c.status === "new").length,
|
|
79
|
+
removedChecks: checks.filter((c) => c.status === "removed").length
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
branch,
|
|
83
|
+
currentScore: current.overallScore,
|
|
84
|
+
baseScore: base.overallScore,
|
|
85
|
+
scoreDelta: current.overallScore - base.overallScore,
|
|
86
|
+
checks,
|
|
87
|
+
summary
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/components/DiffApp.tsx
|
|
92
|
+
var STATUS_ICONS = {
|
|
93
|
+
improved: "\u2191",
|
|
94
|
+
regressed: "\u2193",
|
|
95
|
+
unchanged: "=",
|
|
96
|
+
new: "+",
|
|
97
|
+
removed: "\u2212"
|
|
98
|
+
};
|
|
99
|
+
var STATUS_COLORS = {
|
|
100
|
+
improved: "green",
|
|
101
|
+
regressed: "red",
|
|
102
|
+
unchanged: "gray",
|
|
103
|
+
new: "cyan",
|
|
104
|
+
removed: "yellow"
|
|
105
|
+
};
|
|
106
|
+
function formatDelta(delta) {
|
|
107
|
+
if (delta > 0) return `+${delta}`;
|
|
108
|
+
if (delta < 0) return `${delta}`;
|
|
109
|
+
return "0";
|
|
110
|
+
}
|
|
111
|
+
function DiffTable({ diff }) {
|
|
112
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Overall: "), /* @__PURE__ */ React.createElement(Text, { bold: true }, diff.baseScore), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React.createElement(Text, { bold: true }, diff.currentScore), /* @__PURE__ */ React.createElement(Text, null, " "), /* @__PURE__ */ React.createElement(
|
|
113
|
+
Text,
|
|
114
|
+
{
|
|
115
|
+
color: diff.scoreDelta > 0 ? "green" : diff.scoreDelta < 0 ? "red" : "gray",
|
|
116
|
+
bold: true
|
|
117
|
+
},
|
|
118
|
+
"(",
|
|
119
|
+
formatDelta(diff.scoreDelta),
|
|
120
|
+
")"
|
|
121
|
+
)), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginTop: 1, marginLeft: 2 }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, " Check".padEnd(30)), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Current".padEnd(10)), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Base".padEnd(10)), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Delta")), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u2501".repeat(56)), diff.checks.map((check) => /* @__PURE__ */ React.createElement(Box, { key: check.id }, /* @__PURE__ */ React.createElement(Text, { color: STATUS_COLORS[check.status] }, STATUS_ICONS[check.status], " "), /* @__PURE__ */ React.createElement(Text, null, check.name.padEnd(28)), /* @__PURE__ */ React.createElement(Text, null, String(check.currentScore || "\u2014").padEnd(10)), /* @__PURE__ */ React.createElement(Text, null, String(check.baseScore || "\u2014").padEnd(10)), /* @__PURE__ */ React.createElement(Text, { color: STATUS_COLORS[check.status], bold: true }, check.status === "new" ? "new" : check.status === "removed" ? "removed" : formatDelta(check.delta))))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1, marginLeft: 2 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, diff.summary.improved > 0 && /* @__PURE__ */ React.createElement(Text, { color: "green" }, diff.summary.improved, " improved"), diff.summary.improved > 0 && diff.summary.regressed > 0 && /* @__PURE__ */ React.createElement(Text, null, ", "), diff.summary.regressed > 0 && /* @__PURE__ */ React.createElement(Text, { color: "red" }, diff.summary.regressed, " regressed"), (diff.summary.improved > 0 || diff.summary.regressed > 0) && diff.summary.unchanged > 0 && /* @__PURE__ */ React.createElement(Text, null, ", "), diff.summary.unchanged > 0 && /* @__PURE__ */ React.createElement(Text, null, diff.summary.unchanged, " unchanged"), diff.summary.newChecks > 0 && /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, ", ", diff.summary.newChecks, " new"), diff.summary.removedChecks > 0 && /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, ", ", diff.summary.removedChecks, " removed"))));
|
|
122
|
+
}
|
|
123
|
+
function DiffApp({
|
|
124
|
+
projectPath,
|
|
125
|
+
branch,
|
|
126
|
+
jsonOutput,
|
|
127
|
+
checks,
|
|
128
|
+
verbose
|
|
129
|
+
}) {
|
|
130
|
+
const { exit } = useApp();
|
|
131
|
+
const [phase, setPhase] = useState("scanning");
|
|
132
|
+
const [diff, setDiff] = useState(null);
|
|
133
|
+
const [error, setError] = useState(null);
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
(async () => {
|
|
136
|
+
try {
|
|
137
|
+
const { runSickbay } = await import("@nebulord/sickbay-core");
|
|
138
|
+
const currentReport = await runSickbay({
|
|
139
|
+
projectPath,
|
|
140
|
+
checks,
|
|
141
|
+
verbose
|
|
142
|
+
});
|
|
143
|
+
try {
|
|
144
|
+
const { saveEntry, saveLastReport } = await import("./history-DYFJ65XH.js");
|
|
145
|
+
saveEntry(currentReport);
|
|
146
|
+
saveLastReport(currentReport);
|
|
147
|
+
} catch {
|
|
148
|
+
}
|
|
149
|
+
setPhase("loading-base");
|
|
150
|
+
const baseReport = loadBaseReport(projectPath, branch);
|
|
151
|
+
if (!baseReport) {
|
|
152
|
+
setError(
|
|
153
|
+
`No saved report found on "${branch}". Run \`sickbay\` on that branch and commit .sickbay/last-report.json so it can be read via git.`
|
|
154
|
+
);
|
|
155
|
+
setPhase("error");
|
|
156
|
+
setTimeout(() => exit(), 100);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const result = compareReports(currentReport, baseReport, branch);
|
|
160
|
+
setDiff(result);
|
|
161
|
+
setPhase("results");
|
|
162
|
+
if (jsonOutput) {
|
|
163
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
164
|
+
}
|
|
165
|
+
setTimeout(() => exit(), 100);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
168
|
+
setPhase("error");
|
|
169
|
+
setTimeout(() => exit(), 100);
|
|
170
|
+
}
|
|
171
|
+
})();
|
|
172
|
+
}, []);
|
|
173
|
+
if (phase === "scanning") {
|
|
174
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: "green" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" })), " ", "Scanning current branch..."));
|
|
175
|
+
}
|
|
176
|
+
if (phase === "loading-base") {
|
|
177
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: "green" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" })), " ", "Loading ", branch, " baseline..."));
|
|
178
|
+
}
|
|
179
|
+
if (phase === "error") {
|
|
180
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(Text, { color: "red" }, "\u2717 ", error));
|
|
181
|
+
}
|
|
182
|
+
if (jsonOutput || !diff) return null;
|
|
183
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Branch Diff: current vs ", branch), /* @__PURE__ */ React.createElement(DiffTable, { diff }));
|
|
184
|
+
}
|
|
185
|
+
export {
|
|
186
|
+
DiffApp
|
|
187
|
+
};
|