sickbay 1.16.0 → 1.16.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/README.md +1 -1
- package/dist/DiffApp-DeNEtlY7.js +181 -0
- package/dist/DiffApp-DeNEtlY7.js.map +1 -0
- package/dist/DoctorApp-CaiqnhHH.js +415 -0
- package/dist/DoctorApp-CaiqnhHH.js.map +1 -0
- package/dist/FixApp-MWXohP60.js +337 -0
- package/dist/FixApp-MWXohP60.js.map +1 -0
- package/dist/StatsApp-BC4P4hII.js +372 -0
- package/dist/StatsApp-BC4P4hII.js.map +1 -0
- package/dist/TrendApp-DOBt7ITA.js +127 -0
- package/dist/TrendApp-DOBt7ITA.js.map +1 -0
- package/dist/TuiApp-D4Wn81Qv.js +1042 -0
- package/dist/TuiApp-D4Wn81Qv.js.map +1 -0
- package/dist/ai-DxA5r3hA.js +69 -0
- package/dist/ai-DxA5r3hA.js.map +1 -0
- package/dist/badge-DB3y0fWt.js +37 -0
- package/dist/badge-DB3y0fWt.js.map +1 -0
- package/dist/{claude-SN6IVGBI.js → claude-C5dUtWBJ.js} +13 -12
- package/dist/claude-C5dUtWBJ.js.map +1 -0
- package/dist/config.d.ts +27 -1
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +2 -6
- package/dist/dist-Bjhxbcak.js +5431 -0
- package/dist/dist-Bjhxbcak.js.map +1 -0
- package/dist/history-DVegAwFp.js +83 -0
- package/dist/history-DVegAwFp.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +692 -564
- package/dist/index.js.map +1 -0
- package/dist/init-Dn7eDMEp.js +199 -0
- package/dist/init-Dn7eDMEp.js.map +1 -0
- package/dist/resolve-package-DS27JBhN.js +60 -0
- package/dist/resolve-package-DS27JBhN.js.map +1 -0
- package/dist/rolldown-runtime-CiIaOW0V.js +13 -0
- package/dist/trend-D-Hufthb.js +45 -0
- package/dist/trend-D-Hufthb.js.map +1 -0
- package/dist/update-check-C7bNNfvf.js +58 -0
- package/dist/update-check-C7bNNfvf.js.map +1 -0
- package/dist/web-D5etye5Q.js +259 -0
- package/dist/web-D5etye5Q.js.map +1 -0
- package/package.json +6 -5
- package/dist/DiffApp-ASQWWDLS.js +0 -172
- package/dist/DoctorApp-SMIZDD64.js +0 -440
- package/dist/FixApp-63PU5VF7.js +0 -351
- package/dist/StatsApp-CFZBEITZ.js +0 -356
- package/dist/TrendApp-BKGRSP6Q.js +0 -106
- package/dist/TuiApp-K7KP35RC.js +0 -980
- package/dist/ai-7DGOLNJX.js +0 -64
- package/dist/badge-KQ73KEIN.js +0 -41
- package/dist/chunk-352RAVRZ.js +0 -19
- package/dist/chunk-3OR2GFVE.js +0 -90
- package/dist/chunk-DJA6K3MN.js +0 -5564
- package/dist/chunk-LZOEMCE7.js +0 -62
- package/dist/chunk-MBVA75EM.js +0 -19
- package/dist/chunk-SHO3ZXTH.js +0 -23
- package/dist/chunk-TWCTPAXQ.js +0 -73
- package/dist/chunk-VBM6NZAY.js +0 -60
- package/dist/dist-LQY3FYCP.js +0 -64
- package/dist/history-XLNVZEDI.js +0 -14
- package/dist/init-DD2HOQE2.js +0 -217
- package/dist/resolve-package-774YTW5Q.js +0 -9
- package/dist/update-check-M7IFOMLJ.js +0 -12
- package/dist/web-2KWCYTW4.js +0 -269
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { o as detectProject } from "./dist-Bjhxbcak.js";
|
|
2
|
+
import { r as Header } from "./index.js";
|
|
3
|
+
import { shortName } from "./resolve-package-DS27JBhN.js";
|
|
4
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
5
|
+
import { extname, join } from "path";
|
|
6
|
+
import React, { useEffect, useState } from "react";
|
|
7
|
+
import { Box, Text, useApp } from "ink";
|
|
8
|
+
import Spinner from "ink-spinner";
|
|
9
|
+
import { execSync } from "child_process";
|
|
10
|
+
//#region src/commands/stats.ts
|
|
11
|
+
const IGNORE_DIRS = new Set([
|
|
12
|
+
"node_modules",
|
|
13
|
+
".git",
|
|
14
|
+
"dist",
|
|
15
|
+
"build",
|
|
16
|
+
".next",
|
|
17
|
+
"coverage",
|
|
18
|
+
".turbo",
|
|
19
|
+
".cache",
|
|
20
|
+
".vite",
|
|
21
|
+
"__pycache__",
|
|
22
|
+
".svelte-kit"
|
|
23
|
+
]);
|
|
24
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
25
|
+
".ts",
|
|
26
|
+
".tsx",
|
|
27
|
+
".js",
|
|
28
|
+
".jsx",
|
|
29
|
+
".mjs",
|
|
30
|
+
".cjs",
|
|
31
|
+
".css",
|
|
32
|
+
".scss",
|
|
33
|
+
".less",
|
|
34
|
+
".sass",
|
|
35
|
+
".json",
|
|
36
|
+
".html",
|
|
37
|
+
".svg",
|
|
38
|
+
".vue",
|
|
39
|
+
".svelte"
|
|
40
|
+
]);
|
|
41
|
+
function walkDir(dir, extensions) {
|
|
42
|
+
const results = [];
|
|
43
|
+
try {
|
|
44
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
if (entry.name.startsWith(".") && entry.isDirectory()) continue;
|
|
47
|
+
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
48
|
+
const fullPath = join(dir, entry.name);
|
|
49
|
+
if (entry.isDirectory()) results.push(...walkDir(fullPath, extensions));
|
|
50
|
+
else {
|
|
51
|
+
const ext = extname(entry.name).toLowerCase();
|
|
52
|
+
if (extensions.has(ext)) results.push({
|
|
53
|
+
path: fullPath,
|
|
54
|
+
ext
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch {}
|
|
59
|
+
return results;
|
|
60
|
+
}
|
|
61
|
+
function countLines(filePath) {
|
|
62
|
+
try {
|
|
63
|
+
return readFileSync(filePath, "utf-8").split("\n").length;
|
|
64
|
+
} catch {
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function countComponents(filePath) {
|
|
69
|
+
try {
|
|
70
|
+
const content = readFileSync(filePath, "utf-8");
|
|
71
|
+
return {
|
|
72
|
+
functional: (content.match(/(?:export\s+)?(?:default\s+)?function\s+[A-Z]\w*\s*\(/g) ?? []).length + (content.match(/(?:export\s+)?(?:default\s+)?const\s+[A-Z]\w*\s*[=:]\s*(?:\(|React\.)/g) ?? []).length,
|
|
73
|
+
classBased: (content.match(/class\s+[A-Z]\w*\s+extends\s+(?:React\.)?(?:Component|PureComponent)/g) ?? []).length
|
|
74
|
+
};
|
|
75
|
+
} catch {
|
|
76
|
+
return {
|
|
77
|
+
functional: 0,
|
|
78
|
+
classBased: 0
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function getGitInfo(projectPath) {
|
|
83
|
+
try {
|
|
84
|
+
if (!existsSync(join(projectPath, ".git"))) return null;
|
|
85
|
+
return {
|
|
86
|
+
commits: parseInt(execSync("git rev-list --count HEAD", {
|
|
87
|
+
cwd: projectPath,
|
|
88
|
+
encoding: "utf-8"
|
|
89
|
+
}).trim(), 10),
|
|
90
|
+
contributors: parseInt(execSync("git log --format='%ae' | sort -u | wc -l", {
|
|
91
|
+
cwd: projectPath,
|
|
92
|
+
encoding: "utf-8",
|
|
93
|
+
shell: "/bin/sh"
|
|
94
|
+
}).trim(), 10),
|
|
95
|
+
age: execSync("git log --reverse --format='%ar' | head -1", {
|
|
96
|
+
cwd: projectPath,
|
|
97
|
+
encoding: "utf-8",
|
|
98
|
+
shell: "/bin/sh"
|
|
99
|
+
}).trim(),
|
|
100
|
+
branch: execSync("git branch --show-current", {
|
|
101
|
+
cwd: projectPath,
|
|
102
|
+
encoding: "utf-8"
|
|
103
|
+
}).trim()
|
|
104
|
+
};
|
|
105
|
+
} catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function formatBytes(bytes) {
|
|
110
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
111
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
112
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
113
|
+
}
|
|
114
|
+
async function gatherStats(projectPath) {
|
|
115
|
+
const project = await detectProject(projectPath);
|
|
116
|
+
const files = walkDir(projectPath, SOURCE_EXTENSIONS);
|
|
117
|
+
const byExtension = {};
|
|
118
|
+
for (const f of files) byExtension[f.ext] = (byExtension[f.ext] ?? 0) + 1;
|
|
119
|
+
let totalLines = 0;
|
|
120
|
+
let totalFunctional = 0;
|
|
121
|
+
let totalClassBased = 0;
|
|
122
|
+
let testFiles = 0;
|
|
123
|
+
let totalBytes = 0;
|
|
124
|
+
const componentExts = new Set([
|
|
125
|
+
".tsx",
|
|
126
|
+
".jsx",
|
|
127
|
+
".js",
|
|
128
|
+
".ts"
|
|
129
|
+
]);
|
|
130
|
+
for (const f of files) {
|
|
131
|
+
const lines = countLines(f.path);
|
|
132
|
+
totalLines += lines;
|
|
133
|
+
try {
|
|
134
|
+
totalBytes += statSync(f.path).size;
|
|
135
|
+
} catch {}
|
|
136
|
+
if (componentExts.has(f.ext)) {
|
|
137
|
+
const { functional, classBased } = countComponents(f.path);
|
|
138
|
+
totalFunctional += functional;
|
|
139
|
+
totalClassBased += classBased;
|
|
140
|
+
}
|
|
141
|
+
const name = f.path.toLowerCase();
|
|
142
|
+
if (name.includes(".test.") || name.includes(".spec.") || name.includes("__tests__")) testFiles++;
|
|
143
|
+
}
|
|
144
|
+
const git = getGitInfo(projectPath);
|
|
145
|
+
return {
|
|
146
|
+
project,
|
|
147
|
+
files: {
|
|
148
|
+
total: files.length,
|
|
149
|
+
byExtension
|
|
150
|
+
},
|
|
151
|
+
lines: {
|
|
152
|
+
total: totalLines,
|
|
153
|
+
avgPerFile: files.length > 0 ? Math.round(totalLines / files.length) : 0
|
|
154
|
+
},
|
|
155
|
+
components: {
|
|
156
|
+
total: totalFunctional + totalClassBased,
|
|
157
|
+
functional: totalFunctional,
|
|
158
|
+
classBased: totalClassBased
|
|
159
|
+
},
|
|
160
|
+
dependencies: {
|
|
161
|
+
prod: Object.keys(project.dependencies).length,
|
|
162
|
+
dev: Object.keys(project.devDependencies).length,
|
|
163
|
+
total: project.totalDependencies
|
|
164
|
+
},
|
|
165
|
+
git,
|
|
166
|
+
testFiles,
|
|
167
|
+
sourceSize: formatBytes(totalBytes)
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region src/components/StatsApp.tsx
|
|
172
|
+
const FRAMEWORK_LABELS = {
|
|
173
|
+
next: "Next.js",
|
|
174
|
+
vite: "Vite",
|
|
175
|
+
cra: "Create React App",
|
|
176
|
+
react: "React",
|
|
177
|
+
unknown: "Unknown"
|
|
178
|
+
};
|
|
179
|
+
const PM_LABELS = {
|
|
180
|
+
npm: "npm",
|
|
181
|
+
pnpm: "pnpm",
|
|
182
|
+
yarn: "Yarn"
|
|
183
|
+
};
|
|
184
|
+
function StatRow({ label, value, dimValue }) {
|
|
185
|
+
return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, label.padEnd(18)), /* @__PURE__ */ React.createElement(Text, { bold: true }, value), dimValue && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " ", dimValue));
|
|
186
|
+
}
|
|
187
|
+
function formatExtBreakdown(byExtension) {
|
|
188
|
+
return Object.entries(byExtension).sort((a, b) => b[1] - a[1]).slice(0, 6).map(([ext, count]) => `${ext}: ${count}`).join(", ");
|
|
189
|
+
}
|
|
190
|
+
function ToolBadges({ project }) {
|
|
191
|
+
const badges = [
|
|
192
|
+
{
|
|
193
|
+
label: "TypeScript",
|
|
194
|
+
active: project.hasTypeScript
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
label: "ESLint",
|
|
198
|
+
active: project.hasESLint
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
label: "Prettier",
|
|
202
|
+
active: project.hasPrettier
|
|
203
|
+
}
|
|
204
|
+
];
|
|
205
|
+
return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Tooling".padEnd(18)), badges.map((b, i) => /* @__PURE__ */ React.createElement(React.Fragment, { key: b.label }, i > 0 && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " "), /* @__PURE__ */ React.createElement(Text, { color: b.active ? "green" : "red" }, b.active ? "✓" : "✗", " ", b.label))));
|
|
206
|
+
}
|
|
207
|
+
function SingleProjectStats({ stats }) {
|
|
208
|
+
const { project, files, lines, components, dependencies, git, testFiles, sourceSize } = stats;
|
|
209
|
+
const frameworkLabel = FRAMEWORK_LABELS[project.framework] ?? project.framework;
|
|
210
|
+
const techStack = [frameworkLabel];
|
|
211
|
+
if (project.hasTypeScript) {
|
|
212
|
+
const tsVersion = {
|
|
213
|
+
...project.dependencies,
|
|
214
|
+
...project.devDependencies
|
|
215
|
+
}["typescript"];
|
|
216
|
+
techStack.push(`TypeScript${tsVersion ? ` ${tsVersion.replace("^", "")}` : ""}`);
|
|
217
|
+
}
|
|
218
|
+
const reactVersion = project.dependencies["react"] ?? project.devDependencies["react"];
|
|
219
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, {
|
|
220
|
+
flexDirection: "column",
|
|
221
|
+
marginTop: 1,
|
|
222
|
+
marginLeft: 2
|
|
223
|
+
}, /* @__PURE__ */ React.createElement(StatRow, {
|
|
224
|
+
label: "Framework",
|
|
225
|
+
value: frameworkLabel,
|
|
226
|
+
dimValue: reactVersion ? `(React ${reactVersion.replace("^", "")})` : void 0
|
|
227
|
+
}), /* @__PURE__ */ React.createElement(StatRow, {
|
|
228
|
+
label: "Package Manager",
|
|
229
|
+
value: PM_LABELS[project.packageManager] ?? project.packageManager
|
|
230
|
+
}), /* @__PURE__ */ React.createElement(ToolBadges, { project })), /* @__PURE__ */ React.createElement(Box, {
|
|
231
|
+
marginTop: 1,
|
|
232
|
+
marginLeft: 2,
|
|
233
|
+
flexDirection: "column"
|
|
234
|
+
}, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "━".repeat(48))), /* @__PURE__ */ React.createElement(Box, {
|
|
235
|
+
flexDirection: "column",
|
|
236
|
+
marginTop: 1,
|
|
237
|
+
marginLeft: 2
|
|
238
|
+
}, /* @__PURE__ */ React.createElement(StatRow, {
|
|
239
|
+
label: "Files",
|
|
240
|
+
value: `${files.total}`,
|
|
241
|
+
dimValue: `(${formatExtBreakdown(files.byExtension)})`
|
|
242
|
+
}), /* @__PURE__ */ React.createElement(StatRow, {
|
|
243
|
+
label: "Lines of Code",
|
|
244
|
+
value: lines.total.toLocaleString(),
|
|
245
|
+
dimValue: `(avg ${lines.avgPerFile}/file)`
|
|
246
|
+
}), /* @__PURE__ */ React.createElement(StatRow, {
|
|
247
|
+
label: "Source Size",
|
|
248
|
+
value: sourceSize
|
|
249
|
+
})), components.total > 0 && /* @__PURE__ */ React.createElement(Box, {
|
|
250
|
+
flexDirection: "column",
|
|
251
|
+
marginTop: 1,
|
|
252
|
+
marginLeft: 2
|
|
253
|
+
}, /* @__PURE__ */ React.createElement(StatRow, {
|
|
254
|
+
label: "Components",
|
|
255
|
+
value: `${components.total}`,
|
|
256
|
+
dimValue: `(${components.functional} functional${components.classBased > 0 ? `, ${components.classBased} class` : ""})`
|
|
257
|
+
})), /* @__PURE__ */ React.createElement(Box, {
|
|
258
|
+
flexDirection: "column",
|
|
259
|
+
marginTop: 1,
|
|
260
|
+
marginLeft: 2
|
|
261
|
+
}, /* @__PURE__ */ React.createElement(StatRow, {
|
|
262
|
+
label: "Dependencies",
|
|
263
|
+
value: `${dependencies.total}`,
|
|
264
|
+
dimValue: `(prod: ${dependencies.prod}, dev: ${dependencies.dev})`
|
|
265
|
+
}), /* @__PURE__ */ React.createElement(StatRow, {
|
|
266
|
+
label: "Test Files",
|
|
267
|
+
value: `${testFiles}`,
|
|
268
|
+
dimValue: files.total > 0 ? `(covering ${Math.round(testFiles / files.total * 100)}% of files)` : void 0
|
|
269
|
+
})), git && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Box, {
|
|
270
|
+
marginTop: 1,
|
|
271
|
+
marginLeft: 2,
|
|
272
|
+
flexDirection: "column"
|
|
273
|
+
}, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "━".repeat(48))), /* @__PURE__ */ React.createElement(Box, {
|
|
274
|
+
flexDirection: "column",
|
|
275
|
+
marginTop: 1,
|
|
276
|
+
marginLeft: 2
|
|
277
|
+
}, /* @__PURE__ */ React.createElement(StatRow, {
|
|
278
|
+
label: "Git Branch",
|
|
279
|
+
value: git.branch
|
|
280
|
+
}), /* @__PURE__ */ React.createElement(StatRow, {
|
|
281
|
+
label: "Commits",
|
|
282
|
+
value: `${git.commits}`,
|
|
283
|
+
dimValue: `by ${git.contributors} contributor${git.contributors !== 1 ? "s" : ""}`
|
|
284
|
+
}), /* @__PURE__ */ React.createElement(StatRow, {
|
|
285
|
+
label: "Project Age",
|
|
286
|
+
value: git.age
|
|
287
|
+
}))));
|
|
288
|
+
}
|
|
289
|
+
function StatsApp({ projectPath, jsonOutput, isMonorepo, packagePaths, packageNames }) {
|
|
290
|
+
const { exit } = useApp();
|
|
291
|
+
const [stats, setStats] = useState(null);
|
|
292
|
+
const [packageStats, setPackageStats] = useState([]);
|
|
293
|
+
const [loading, setLoading] = useState(true);
|
|
294
|
+
const [error, setError] = useState(null);
|
|
295
|
+
useEffect(() => {
|
|
296
|
+
if (isMonorepo && packagePaths && packageNames) Promise.all(packagePaths.map(async (pkgPath) => {
|
|
297
|
+
return {
|
|
298
|
+
name: packageNames.get(pkgPath) ?? pkgPath,
|
|
299
|
+
path: pkgPath,
|
|
300
|
+
stats: await gatherStats(pkgPath)
|
|
301
|
+
};
|
|
302
|
+
})).then((all) => {
|
|
303
|
+
setPackageStats(all);
|
|
304
|
+
setLoading(false);
|
|
305
|
+
if (jsonOutput) {
|
|
306
|
+
const output = all.map((p) => ({
|
|
307
|
+
package: p.name,
|
|
308
|
+
path: p.path,
|
|
309
|
+
stats: p.stats
|
|
310
|
+
}));
|
|
311
|
+
process.stdout.write(JSON.stringify(output, null, 2) + "\n");
|
|
312
|
+
}
|
|
313
|
+
setTimeout(() => exit(), 100);
|
|
314
|
+
}).catch((err) => {
|
|
315
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
316
|
+
setLoading(false);
|
|
317
|
+
setTimeout(() => exit(), 100);
|
|
318
|
+
});
|
|
319
|
+
else gatherStats(projectPath).then((s) => {
|
|
320
|
+
setStats(s);
|
|
321
|
+
setLoading(false);
|
|
322
|
+
if (jsonOutput) process.stdout.write(JSON.stringify(s, null, 2) + "\n");
|
|
323
|
+
setTimeout(() => exit(), 100);
|
|
324
|
+
}).catch((err) => {
|
|
325
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
326
|
+
setLoading(false);
|
|
327
|
+
setTimeout(() => exit(), 100);
|
|
328
|
+
});
|
|
329
|
+
}, []);
|
|
330
|
+
if (loading) return /* @__PURE__ */ React.createElement(Box, {
|
|
331
|
+
flexDirection: "column",
|
|
332
|
+
padding: 1
|
|
333
|
+
}, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: "green" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" })), " ", "Scanning project", isMonorepo ? ` (${packagePaths?.length} packages)` : "", "..."));
|
|
334
|
+
if (error) return /* @__PURE__ */ React.createElement(Box, {
|
|
335
|
+
flexDirection: "column",
|
|
336
|
+
padding: 1
|
|
337
|
+
}, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(Text, { color: "red" }, "✗ Error: ", error));
|
|
338
|
+
if (jsonOutput) return null;
|
|
339
|
+
if (isMonorepo && packageStats.length > 0) {
|
|
340
|
+
const totals = packageStats.reduce((acc, p) => ({
|
|
341
|
+
files: acc.files + p.stats.files.total,
|
|
342
|
+
lines: acc.lines + p.stats.lines.total,
|
|
343
|
+
deps: acc.deps + p.stats.dependencies.total,
|
|
344
|
+
tests: acc.tests + p.stats.testFiles
|
|
345
|
+
}), {
|
|
346
|
+
files: 0,
|
|
347
|
+
lines: 0,
|
|
348
|
+
deps: 0,
|
|
349
|
+
tests: 0
|
|
350
|
+
});
|
|
351
|
+
return /* @__PURE__ */ React.createElement(Box, {
|
|
352
|
+
flexDirection: "column",
|
|
353
|
+
padding: 1
|
|
354
|
+
}, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Monorepo Overview"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, packageStats.length, " packages"), /* @__PURE__ */ React.createElement(Box, {
|
|
355
|
+
flexDirection: "column",
|
|
356
|
+
marginTop: 1,
|
|
357
|
+
marginLeft: 2
|
|
358
|
+
}, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Package".padEnd(36)), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Framework".padEnd(14)), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Files".padEnd(8)), /* @__PURE__ */ React.createElement(Text, { bold: true }, "LOC".padEnd(10)), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Deps".padEnd(8)), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Tests")), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "━".repeat(84)), packageStats.map((pkg) => {
|
|
359
|
+
const fw = FRAMEWORK_LABELS[pkg.stats.project.framework] ?? pkg.stats.project.framework;
|
|
360
|
+
return /* @__PURE__ */ React.createElement(Box, { key: pkg.path }, /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, shortName(pkg.name).padEnd(36)), /* @__PURE__ */ React.createElement(Text, null, fw.padEnd(14)), /* @__PURE__ */ React.createElement(Text, null, String(pkg.stats.files.total).padEnd(8)), /* @__PURE__ */ React.createElement(Text, null, pkg.stats.lines.total.toLocaleString().padEnd(10)), /* @__PURE__ */ React.createElement(Text, null, String(pkg.stats.dependencies.total).padEnd(8)), /* @__PURE__ */ React.createElement(Text, null, pkg.stats.testFiles));
|
|
361
|
+
}), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "━".repeat(84)), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Total".padEnd(36)), /* @__PURE__ */ React.createElement(Text, null, "".padEnd(14)), /* @__PURE__ */ React.createElement(Text, { bold: true }, String(totals.files).padEnd(8)), /* @__PURE__ */ React.createElement(Text, { bold: true }, totals.lines.toLocaleString().padEnd(10)), /* @__PURE__ */ React.createElement(Text, { bold: true }, String(totals.deps).padEnd(8)), /* @__PURE__ */ React.createElement(Text, { bold: true }, totals.tests))));
|
|
362
|
+
}
|
|
363
|
+
if (!stats) return null;
|
|
364
|
+
return /* @__PURE__ */ React.createElement(Box, {
|
|
365
|
+
flexDirection: "column",
|
|
366
|
+
padding: 1
|
|
367
|
+
}, /* @__PURE__ */ React.createElement(Header, { projectName: stats.project.name }), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Codebase Overview"), /* @__PURE__ */ React.createElement(SingleProjectStats, { stats }));
|
|
368
|
+
}
|
|
369
|
+
//#endregion
|
|
370
|
+
export { StatsApp };
|
|
371
|
+
|
|
372
|
+
//# sourceMappingURL=StatsApp-BC4P4hII.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StatsApp-BC4P4hII.js","names":[],"sources":["../src/commands/stats.ts","../src/components/StatsApp.tsx"],"sourcesContent":["import { execSync } from 'child_process';\nimport { readFileSync, readdirSync, statSync, existsSync } from 'fs';\nimport { join, extname } from 'path';\n\nimport { detectProject } from 'sickbay-core';\n\nimport type { ProjectInfo } from 'sickbay-core';\n\n/**\n * This module provides a function to gather various statistics about a project, including file counts, line counts, component types, dependencies, and git information.\n * The gatherStats function walks through the project directory, collects data on source files, counts lines of code, identifies React components, and retrieves git metadata if available.\n * The resulting ProjectStats object gives a comprehensive overview of the project's structure and health, which can be used for reporting or further analysis.\n */\n\nexport interface ProjectStats {\n project: ProjectInfo;\n files: {\n total: number;\n byExtension: Record<string, number>;\n };\n lines: {\n total: number;\n avgPerFile: number;\n };\n components: {\n total: number;\n functional: number;\n classBased: number;\n };\n dependencies: {\n prod: number;\n dev: number;\n total: number;\n };\n git: {\n commits: number;\n contributors: number;\n age: string;\n branch: string;\n } | null;\n testFiles: number;\n sourceSize: string;\n}\n\nconst IGNORE_DIRS = new Set([\n 'node_modules',\n '.git',\n 'dist',\n 'build',\n '.next',\n 'coverage',\n '.turbo',\n '.cache',\n '.vite',\n '__pycache__',\n '.svelte-kit',\n]);\n\nconst SOURCE_EXTENSIONS = new Set([\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.css',\n '.scss',\n '.less',\n '.sass',\n '.json',\n '.html',\n '.svg',\n '.vue',\n '.svelte',\n]);\n\nfunction walkDir(dir: string, extensions: Set<string>): { path: string; ext: string }[] {\n const results: { path: string; ext: string }[] = [];\n\n try {\n const entries = readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.name.startsWith('.') && entry.isDirectory()) continue;\n if (IGNORE_DIRS.has(entry.name)) continue;\n\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...walkDir(fullPath, extensions));\n } else {\n const ext = extname(entry.name).toLowerCase();\n if (extensions.has(ext)) {\n results.push({ path: fullPath, ext });\n }\n }\n }\n } catch {\n // Permission errors, etc.\n }\n\n return results;\n}\n\nfunction countLines(filePath: string): number {\n try {\n const content = readFileSync(filePath, 'utf-8');\n return content.split('\\n').length;\n } catch {\n return 0;\n }\n}\n\nfunction countComponents(filePath: string): {\n functional: number;\n classBased: number;\n} {\n try {\n const content = readFileSync(filePath, 'utf-8');\n const functional =\n (content.match(/(?:export\\s+)?(?:default\\s+)?function\\s+[A-Z]\\w*\\s*\\(/g) ?? []).length +\n (\n content.match(/(?:export\\s+)?(?:default\\s+)?const\\s+[A-Z]\\w*\\s*[=:]\\s*(?:\\(|React\\.)/g) ??\n []\n ).length;\n const classBased = (\n content.match(/class\\s+[A-Z]\\w*\\s+extends\\s+(?:React\\.)?(?:Component|PureComponent)/g) ?? []\n ).length;\n return { functional, classBased };\n } catch {\n return { functional: 0, classBased: 0 };\n }\n}\n\nfunction getGitInfo(projectPath: string): ProjectStats['git'] {\n try {\n if (!existsSync(join(projectPath, '.git'))) return null;\n\n const commits = parseInt(\n execSync('git rev-list --count HEAD', {\n cwd: projectPath,\n encoding: 'utf-8',\n }).trim(),\n 10,\n );\n\n const contributors = parseInt(\n execSync(\"git log --format='%ae' | sort -u | wc -l\", {\n cwd: projectPath,\n encoding: 'utf-8',\n shell: '/bin/sh',\n }).trim(),\n 10,\n );\n\n const firstCommit = execSync(\"git log --reverse --format='%ar' | head -1\", {\n cwd: projectPath,\n encoding: 'utf-8',\n shell: '/bin/sh',\n }).trim();\n\n const branch = execSync('git branch --show-current', {\n cwd: projectPath,\n encoding: 'utf-8',\n }).trim();\n\n return { commits, contributors, age: firstCommit, branch };\n } catch {\n return null;\n }\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nexport async function gatherStats(projectPath: string): Promise<ProjectStats> {\n const project = await detectProject(projectPath);\n\n const files = walkDir(projectPath, SOURCE_EXTENSIONS);\n const byExtension: Record<string, number> = {};\n for (const f of files) {\n byExtension[f.ext] = (byExtension[f.ext] ?? 0) + 1;\n }\n\n let totalLines = 0;\n let totalFunctional = 0;\n let totalClassBased = 0;\n let testFiles = 0;\n let totalBytes = 0;\n\n const componentExts = new Set(['.tsx', '.jsx', '.js', '.ts']);\n\n for (const f of files) {\n const lines = countLines(f.path);\n totalLines += lines;\n\n try {\n totalBytes += statSync(f.path).size;\n } catch {\n // ignore\n }\n\n if (componentExts.has(f.ext)) {\n const { functional, classBased } = countComponents(f.path);\n totalFunctional += functional;\n totalClassBased += classBased;\n }\n\n const name = f.path.toLowerCase();\n if (name.includes('.test.') || name.includes('.spec.') || name.includes('__tests__')) {\n testFiles++;\n }\n }\n\n const git = getGitInfo(projectPath);\n\n return {\n project,\n files: {\n total: files.length,\n byExtension,\n },\n lines: {\n total: totalLines,\n avgPerFile: files.length > 0 ? Math.round(totalLines / files.length) : 0,\n },\n components: {\n total: totalFunctional + totalClassBased,\n functional: totalFunctional,\n classBased: totalClassBased,\n },\n dependencies: {\n prod: Object.keys(project.dependencies).length,\n dev: Object.keys(project.devDependencies).length,\n total: project.totalDependencies,\n },\n git,\n testFiles,\n sourceSize: formatBytes(totalBytes),\n };\n}\n","import React, { useState, useEffect } from 'react';\n\nimport { Box, Text, useApp } from 'ink';\nimport Spinner from 'ink-spinner';\n\nimport { gatherStats, type ProjectStats } from '../commands/stats.js';\nimport { shortName } from '../lib/resolve-package.js';\nimport { Header } from './Header.js';\n\ninterface StatsAppProps {\n projectPath: string;\n jsonOutput: boolean;\n isMonorepo?: boolean;\n packagePaths?: string[];\n packageNames?: Map<string, string>;\n}\n\ninterface PackageStats {\n name: string;\n path: string;\n stats: ProjectStats;\n}\n\nconst FRAMEWORK_LABELS: Record<string, string> = {\n next: 'Next.js',\n vite: 'Vite',\n cra: 'Create React App',\n react: 'React',\n unknown: 'Unknown',\n};\n\nconst PM_LABELS: Record<string, string> = {\n npm: 'npm',\n pnpm: 'pnpm',\n yarn: 'Yarn',\n};\n\nfunction StatRow({ label, value, dimValue }: { label: string; value: string; dimValue?: string }) {\n return (\n <Box>\n <Text dimColor>{label.padEnd(18)}</Text>\n <Text bold>{value}</Text>\n {dimValue && <Text dimColor> {dimValue}</Text>}\n </Box>\n );\n}\n\nfunction formatExtBreakdown(byExtension: Record<string, number>): string {\n const sorted = Object.entries(byExtension)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 6);\n return sorted.map(([ext, count]) => `${ext}: ${count}`).join(', ');\n}\n\nfunction ToolBadges({ project }: { project: ProjectStats['project'] }) {\n const badges: Array<{ label: string; active: boolean }> = [\n { label: 'TypeScript', active: project.hasTypeScript },\n { label: 'ESLint', active: project.hasESLint },\n { label: 'Prettier', active: project.hasPrettier },\n ];\n\n return (\n <Box>\n <Text dimColor>{'Tooling'.padEnd(18)}</Text>\n {badges.map((b, i) => (\n <React.Fragment key={b.label}>\n {i > 0 && <Text dimColor> </Text>}\n <Text color={b.active ? 'green' : 'red'}>\n {b.active ? '✓' : '✗'} {b.label}\n </Text>\n </React.Fragment>\n ))}\n </Box>\n );\n}\n\nfunction SingleProjectStats({ stats }: { stats: ProjectStats }) {\n const { project, files, lines, components, dependencies, git, testFiles, sourceSize } = stats;\n\n const frameworkLabel = FRAMEWORK_LABELS[project.framework] ?? project.framework;\n const techStack = [frameworkLabel];\n if (project.hasTypeScript) {\n const tsVersion = { ...project.dependencies, ...project.devDependencies }['typescript'];\n techStack.push(`TypeScript${tsVersion ? ` ${tsVersion.replace('^', '')}` : ''}`);\n }\n\n const reactVersion = project.dependencies['react'] ?? project.devDependencies['react'];\n\n return (\n <Box flexDirection=\"column\">\n <Box flexDirection=\"column\" marginTop={1} marginLeft={2}>\n <StatRow\n label=\"Framework\"\n value={frameworkLabel}\n dimValue={reactVersion ? `(React ${reactVersion.replace('^', '')})` : undefined}\n />\n <StatRow\n label=\"Package Manager\"\n value={PM_LABELS[project.packageManager] ?? project.packageManager}\n />\n <ToolBadges project={project} />\n </Box>\n\n <Box marginTop={1} marginLeft={2} flexDirection=\"column\">\n <Text dimColor>{'━'.repeat(48)}</Text>\n </Box>\n\n <Box flexDirection=\"column\" marginTop={1} marginLeft={2}>\n <StatRow\n label=\"Files\"\n value={`${files.total}`}\n dimValue={`(${formatExtBreakdown(files.byExtension)})`}\n />\n <StatRow\n label=\"Lines of Code\"\n value={lines.total.toLocaleString()}\n dimValue={`(avg ${lines.avgPerFile}/file)`}\n />\n <StatRow label=\"Source Size\" value={sourceSize} />\n </Box>\n\n {components.total > 0 && (\n <Box flexDirection=\"column\" marginTop={1} marginLeft={2}>\n <StatRow\n label=\"Components\"\n value={`${components.total}`}\n dimValue={`(${components.functional} functional${components.classBased > 0 ? `, ${components.classBased} class` : ''})`}\n />\n </Box>\n )}\n\n <Box flexDirection=\"column\" marginTop={1} marginLeft={2}>\n <StatRow\n label=\"Dependencies\"\n value={`${dependencies.total}`}\n dimValue={`(prod: ${dependencies.prod}, dev: ${dependencies.dev})`}\n />\n <StatRow\n label=\"Test Files\"\n value={`${testFiles}`}\n dimValue={\n files.total > 0\n ? `(covering ${Math.round((testFiles / files.total) * 100)}% of files)`\n : undefined\n }\n />\n </Box>\n\n {git && (\n <>\n <Box marginTop={1} marginLeft={2} flexDirection=\"column\">\n <Text dimColor>{'━'.repeat(48)}</Text>\n </Box>\n <Box flexDirection=\"column\" marginTop={1} marginLeft={2}>\n <StatRow label=\"Git Branch\" value={git.branch} />\n <StatRow\n label=\"Commits\"\n value={`${git.commits}`}\n dimValue={`by ${git.contributors} contributor${git.contributors !== 1 ? 's' : ''}`}\n />\n <StatRow label=\"Project Age\" value={git.age} />\n </Box>\n </>\n )}\n </Box>\n );\n}\n\nexport function StatsApp({\n projectPath,\n jsonOutput,\n isMonorepo,\n packagePaths,\n packageNames,\n}: StatsAppProps) {\n const { exit } = useApp();\n const [stats, setStats] = useState<ProjectStats | null>(null);\n const [packageStats, setPackageStats] = useState<PackageStats[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n if (isMonorepo && packagePaths && packageNames) {\n Promise.all(\n packagePaths.map(async (pkgPath) => {\n const name = packageNames.get(pkgPath) ?? pkgPath;\n const s = await gatherStats(pkgPath);\n return { name, path: pkgPath, stats: s };\n }),\n )\n .then((all) => {\n setPackageStats(all);\n setLoading(false);\n\n if (jsonOutput) {\n const output = all.map((p) => ({\n package: p.name,\n path: p.path,\n stats: p.stats,\n }));\n process.stdout.write(JSON.stringify(output, null, 2) + '\\n');\n }\n\n setTimeout(() => exit(), 100);\n })\n .catch((err) => {\n setError(err instanceof Error ? err.message : String(err));\n setLoading(false);\n setTimeout(() => exit(), 100);\n });\n } else {\n gatherStats(projectPath)\n .then((s) => {\n setStats(s);\n setLoading(false);\n\n if (jsonOutput) {\n process.stdout.write(JSON.stringify(s, null, 2) + '\\n');\n }\n\n setTimeout(() => exit(), 100);\n })\n .catch((err) => {\n setError(err instanceof Error ? err.message : String(err));\n setLoading(false);\n setTimeout(() => exit(), 100);\n });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n if (loading) {\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Header />\n <Text>\n <Text color=\"green\">\n <Spinner type=\"dots\" />\n </Text>{' '}\n Scanning project\n {isMonorepo ? ` (${packagePaths?.length} packages)` : ''}...\n </Text>\n </Box>\n );\n }\n\n if (error) {\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Header />\n <Text color=\"red\">✗ Error: {error}</Text>\n </Box>\n );\n }\n\n if (jsonOutput) return null;\n\n // Monorepo: show per-package stats with summary table\n if (isMonorepo && packageStats.length > 0) {\n const totals = packageStats.reduce(\n (acc, p) => ({\n files: acc.files + p.stats.files.total,\n lines: acc.lines + p.stats.lines.total,\n deps: acc.deps + p.stats.dependencies.total,\n tests: acc.tests + p.stats.testFiles,\n }),\n { files: 0, lines: 0, deps: 0, tests: 0 },\n );\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Header />\n <Text bold>Monorepo Overview</Text>\n <Text dimColor>{packageStats.length} packages</Text>\n\n <Box flexDirection=\"column\" marginTop={1} marginLeft={2}>\n <Box>\n <Text bold>{'Package'.padEnd(36)}</Text>\n <Text bold>{'Framework'.padEnd(14)}</Text>\n <Text bold>{'Files'.padEnd(8)}</Text>\n <Text bold>{'LOC'.padEnd(10)}</Text>\n <Text bold>{'Deps'.padEnd(8)}</Text>\n <Text bold>Tests</Text>\n </Box>\n <Text dimColor>{'━'.repeat(84)}</Text>\n {packageStats.map((pkg) => {\n const fw = FRAMEWORK_LABELS[pkg.stats.project.framework] ?? pkg.stats.project.framework;\n return (\n <Box key={pkg.path}>\n <Text color=\"cyan\">{shortName(pkg.name).padEnd(36)}</Text>\n <Text>{fw.padEnd(14)}</Text>\n <Text>{String(pkg.stats.files.total).padEnd(8)}</Text>\n <Text>{pkg.stats.lines.total.toLocaleString().padEnd(10)}</Text>\n <Text>{String(pkg.stats.dependencies.total).padEnd(8)}</Text>\n <Text>{pkg.stats.testFiles}</Text>\n </Box>\n );\n })}\n <Text dimColor>{'━'.repeat(84)}</Text>\n <Box>\n <Text bold>{'Total'.padEnd(36)}</Text>\n <Text>{''.padEnd(14)}</Text>\n <Text bold>{String(totals.files).padEnd(8)}</Text>\n <Text bold>{totals.lines.toLocaleString().padEnd(10)}</Text>\n <Text bold>{String(totals.deps).padEnd(8)}</Text>\n <Text bold>{totals.tests}</Text>\n </Box>\n </Box>\n </Box>\n );\n }\n\n // Single project\n if (!stats) return null;\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Header projectName={stats.project.name} />\n <Text bold>Codebase Overview</Text>\n <SingleProjectStats stats={stats} />\n </Box>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { r as Header } from "./index.js";
|
|
2
|
+
import { r as loadHistory, t as detectRegressions } from "./history-DVegAwFp.js";
|
|
3
|
+
import { shortName } from "./resolve-package-DS27JBhN.js";
|
|
4
|
+
import { n as trendArrow, t as sparkline } from "./trend-D-Hufthb.js";
|
|
5
|
+
import React, { useEffect, useState } from "react";
|
|
6
|
+
import { Box, Text, useApp } from "ink";
|
|
7
|
+
//#region src/components/TrendApp.tsx
|
|
8
|
+
const CATEGORIES = [
|
|
9
|
+
"dependencies",
|
|
10
|
+
"security",
|
|
11
|
+
"code-quality",
|
|
12
|
+
"performance",
|
|
13
|
+
"git"
|
|
14
|
+
];
|
|
15
|
+
const CATEGORY_LABELS = {
|
|
16
|
+
dependencies: "Dependencies",
|
|
17
|
+
security: "Security",
|
|
18
|
+
"code-quality": "Code Quality",
|
|
19
|
+
performance: "Performance",
|
|
20
|
+
git: "Git"
|
|
21
|
+
};
|
|
22
|
+
function trendColor(direction) {
|
|
23
|
+
if (direction === "up") return "green";
|
|
24
|
+
if (direction === "down") return "red";
|
|
25
|
+
return "gray";
|
|
26
|
+
}
|
|
27
|
+
function SingleTrendView({ history, last }) {
|
|
28
|
+
const entries = history.entries.slice(-last);
|
|
29
|
+
const scores = entries.map((e) => e.overallScore);
|
|
30
|
+
const overall = trendArrow(scores);
|
|
31
|
+
const regressions = detectRegressions(entries);
|
|
32
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Score History"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, entries.length, " scan", entries.length !== 1 ? "s" : "", " recorded"), /* @__PURE__ */ React.createElement(Box, {
|
|
33
|
+
marginTop: 1,
|
|
34
|
+
marginLeft: 2,
|
|
35
|
+
flexDirection: "column"
|
|
36
|
+
}, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Overall".padEnd(15)), /* @__PURE__ */ React.createElement(Text, { color: trendColor(overall.direction) }, sparkline(scores)), /* @__PURE__ */ React.createElement(Text, { bold: true }, " ", scores[scores.length - 1], "/100 "), /* @__PURE__ */ React.createElement(Text, { color: trendColor(overall.direction) }, overall.label)), /* @__PURE__ */ React.createElement(Box, {
|
|
37
|
+
marginTop: 1,
|
|
38
|
+
flexDirection: "column"
|
|
39
|
+
}, CATEGORIES.map((cat) => {
|
|
40
|
+
const catScores = entries.map((e) => e.categoryScores[cat]).filter((s) => s !== void 0);
|
|
41
|
+
if (catScores.length === 0) return null;
|
|
42
|
+
const catTrend = trendArrow(catScores);
|
|
43
|
+
return /* @__PURE__ */ React.createElement(Box, { key: cat }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, (CATEGORY_LABELS[cat] ?? cat).padEnd(15)), /* @__PURE__ */ React.createElement(Text, { color: trendColor(catTrend.direction) }, sparkline(catScores)), /* @__PURE__ */ React.createElement(Text, null, " ", catScores[catScores.length - 1], "/100 "), /* @__PURE__ */ React.createElement(Text, { color: trendColor(catTrend.direction) }, catTrend.label));
|
|
44
|
+
}))), regressions.length > 0 && /* @__PURE__ */ React.createElement(Box, {
|
|
45
|
+
flexDirection: "column",
|
|
46
|
+
marginTop: 1
|
|
47
|
+
}, /* @__PURE__ */ React.createElement(Text, {
|
|
48
|
+
bold: true,
|
|
49
|
+
color: "red"
|
|
50
|
+
}, "Regressions Detected:"), regressions.map((r) => /* @__PURE__ */ React.createElement(Box, {
|
|
51
|
+
key: r.category,
|
|
52
|
+
marginLeft: 2
|
|
53
|
+
}, /* @__PURE__ */ React.createElement(Text, { color: "red" }, "↓ ", CATEGORY_LABELS[r.category] ?? r.category, ": ", r.from, " → ", r.to, " (-", r.drop, " pts)")))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "━".repeat(52))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "First scan: ", new Date(entries[0].timestamp).toLocaleDateString(), " · ", "Latest: ", new Date(entries[entries.length - 1].timestamp).toLocaleDateString())));
|
|
54
|
+
}
|
|
55
|
+
function TrendApp({ projectPath, last, jsonOutput, isMonorepo, packagePaths, packageNames }) {
|
|
56
|
+
const { exit } = useApp();
|
|
57
|
+
const [history, setHistory] = useState(null);
|
|
58
|
+
const [packageTrends, setPackageTrends] = useState([]);
|
|
59
|
+
const [loaded, setLoaded] = useState(false);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (isMonorepo && packagePaths && packageNames) {
|
|
62
|
+
const trends = packagePaths.map((pkgPath) => {
|
|
63
|
+
return {
|
|
64
|
+
name: packageNames.get(pkgPath) ?? pkgPath,
|
|
65
|
+
path: pkgPath,
|
|
66
|
+
history: loadHistory(pkgPath)
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
setPackageTrends(trends);
|
|
70
|
+
setLoaded(true);
|
|
71
|
+
if (jsonOutput) {
|
|
72
|
+
const output = trends.map((t) => ({
|
|
73
|
+
package: t.name,
|
|
74
|
+
path: t.path,
|
|
75
|
+
history: t.history
|
|
76
|
+
}));
|
|
77
|
+
process.stdout.write(JSON.stringify(output, null, 2) + "\n");
|
|
78
|
+
}
|
|
79
|
+
setTimeout(() => exit(), 100);
|
|
80
|
+
} else {
|
|
81
|
+
const h = loadHistory(projectPath);
|
|
82
|
+
setHistory(h);
|
|
83
|
+
setLoaded(true);
|
|
84
|
+
if (jsonOutput && h) process.stdout.write(JSON.stringify(h, null, 2) + "\n");
|
|
85
|
+
setTimeout(() => exit(), 100);
|
|
86
|
+
}
|
|
87
|
+
}, []);
|
|
88
|
+
if (!loaded) return null;
|
|
89
|
+
if (jsonOutput) return null;
|
|
90
|
+
if (isMonorepo && packageTrends.length > 0) {
|
|
91
|
+
const withHistory = packageTrends.filter((t) => t.history && t.history.entries.length > 0);
|
|
92
|
+
if (withHistory.length === 0) return /* @__PURE__ */ React.createElement(Box, {
|
|
93
|
+
flexDirection: "column",
|
|
94
|
+
padding: 1
|
|
95
|
+
}, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "No scan history found for any package in this monorepo."), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Run "), /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, "sickbay --package <name>"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " first to start tracking scores.")));
|
|
96
|
+
return /* @__PURE__ */ React.createElement(Box, {
|
|
97
|
+
flexDirection: "column",
|
|
98
|
+
padding: 1
|
|
99
|
+
}, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Monorepo Score Trends"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, withHistory.length, " of ", packageTrends.length, " packages have history"), /* @__PURE__ */ React.createElement(Box, {
|
|
100
|
+
flexDirection: "column",
|
|
101
|
+
marginTop: 1,
|
|
102
|
+
marginLeft: 2
|
|
103
|
+
}, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Package".padEnd(24)), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Trend".padEnd(22)), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Score".padEnd(10)), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Direction")), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "━".repeat(64)), withHistory.map((pkg) => {
|
|
104
|
+
const scores = pkg.history.entries.slice(-last).map((e) => e.overallScore);
|
|
105
|
+
const trend = trendArrow(scores);
|
|
106
|
+
return /* @__PURE__ */ React.createElement(Box, { key: pkg.path }, /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, shortName(pkg.name).padEnd(24)), /* @__PURE__ */ React.createElement(Text, { color: trendColor(trend.direction) }, sparkline(scores).padEnd(22)), /* @__PURE__ */ React.createElement(Text, { bold: true }, String(scores[scores.length - 1]).padEnd(10)), /* @__PURE__ */ React.createElement(Text, { color: trendColor(trend.direction) }, trend.label));
|
|
107
|
+
})), packageTrends.length > withHistory.length && /* @__PURE__ */ React.createElement(Box, {
|
|
108
|
+
marginTop: 1,
|
|
109
|
+
marginLeft: 2
|
|
110
|
+
}, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, packageTrends.length - withHistory.length, " package", packageTrends.length - withHistory.length !== 1 ? "s" : "", " with no history yet")));
|
|
111
|
+
}
|
|
112
|
+
if (!history || history.entries.length === 0) return /* @__PURE__ */ React.createElement(Box, {
|
|
113
|
+
flexDirection: "column",
|
|
114
|
+
padding: 1
|
|
115
|
+
}, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "No scan history found for this project."), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Run "), /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, "sickbay"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " first to start tracking scores.")));
|
|
116
|
+
return /* @__PURE__ */ React.createElement(Box, {
|
|
117
|
+
flexDirection: "column",
|
|
118
|
+
padding: 1
|
|
119
|
+
}, /* @__PURE__ */ React.createElement(Header, { projectName: history.projectName }), /* @__PURE__ */ React.createElement(SingleTrendView, {
|
|
120
|
+
history,
|
|
121
|
+
last
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
export { TrendApp };
|
|
126
|
+
|
|
127
|
+
//# sourceMappingURL=TrendApp-DOBt7ITA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TrendApp-DOBt7ITA.js","names":[],"sources":["../src/components/TrendApp.tsx"],"sourcesContent":["import React, { useState, useEffect } from 'react';\n\nimport { Box, Text, useApp } from 'ink';\n\nimport { sparkline, trendArrow } from '../commands/trend.js';\nimport { loadHistory, detectRegressions } from '../lib/history.js';\nimport { shortName } from '../lib/resolve-package.js';\nimport { Header } from './Header.js';\n\nimport type { TrendHistory } from '../lib/history.js';\n\ninterface TrendAppProps {\n projectPath: string;\n last: number;\n jsonOutput: boolean;\n isMonorepo?: boolean;\n packagePaths?: string[];\n packageNames?: Map<string, string>;\n}\n\ninterface PackageTrend {\n name: string;\n path: string;\n history: TrendHistory | null;\n}\n\nconst CATEGORIES = ['dependencies', 'security', 'code-quality', 'performance', 'git'];\n\nconst CATEGORY_LABELS: Record<string, string> = {\n dependencies: 'Dependencies',\n security: 'Security',\n 'code-quality': 'Code Quality',\n performance: 'Performance',\n git: 'Git',\n};\n\nfunction trendColor(direction: 'up' | 'down' | 'stable') {\n if (direction === 'up') return 'green' as const;\n if (direction === 'down') return 'red' as const;\n return 'gray' as const;\n}\n\nfunction SingleTrendView({ history, last }: { history: TrendHistory; last: number }) {\n const entries = history.entries.slice(-last);\n const scores = entries.map((e) => e.overallScore);\n const overall = trendArrow(scores);\n const regressions = detectRegressions(entries);\n\n return (\n <Box flexDirection=\"column\">\n <Text bold>Score History</Text>\n <Text dimColor>\n {entries.length} scan{entries.length !== 1 ? 's' : ''} recorded\n </Text>\n\n <Box marginTop={1} marginLeft={2} flexDirection=\"column\">\n <Box>\n <Text bold>{'Overall'.padEnd(15)}</Text>\n <Text color={trendColor(overall.direction)}>{sparkline(scores)}</Text>\n <Text bold> {scores[scores.length - 1]}/100 </Text>\n <Text color={trendColor(overall.direction)}>{overall.label}</Text>\n </Box>\n\n <Box marginTop={1} flexDirection=\"column\">\n {CATEGORIES.map((cat) => {\n const catScores = entries\n .map((e) => e.categoryScores[cat])\n .filter((s) => s !== undefined);\n if (catScores.length === 0) return null;\n const catTrend = trendArrow(catScores);\n return (\n <Box key={cat}>\n <Text dimColor>{(CATEGORY_LABELS[cat] ?? cat).padEnd(15)}</Text>\n <Text color={trendColor(catTrend.direction)}>{sparkline(catScores)}</Text>\n <Text> {catScores[catScores.length - 1]}/100 </Text>\n <Text color={trendColor(catTrend.direction)}>{catTrend.label}</Text>\n </Box>\n );\n })}\n </Box>\n </Box>\n\n {regressions.length > 0 && (\n <Box flexDirection=\"column\" marginTop={1}>\n <Text bold color=\"red\">\n Regressions Detected:\n </Text>\n {regressions.map((r) => (\n <Box key={r.category} marginLeft={2}>\n <Text color=\"red\">\n ↓ {CATEGORY_LABELS[r.category] ?? r.category}: {r.from} → {r.to} (-{r.drop} pts)\n </Text>\n </Box>\n ))}\n </Box>\n )}\n\n <Box marginTop={1}>\n <Text dimColor>{'━'.repeat(52)}</Text>\n </Box>\n <Box marginTop={1}>\n <Text dimColor>\n First scan: {new Date(entries[0].timestamp).toLocaleDateString()}\n {' · '}\n Latest: {new Date(entries[entries.length - 1].timestamp).toLocaleDateString()}\n </Text>\n </Box>\n </Box>\n );\n}\n\nexport function TrendApp({\n projectPath,\n last,\n jsonOutput,\n isMonorepo,\n packagePaths,\n packageNames,\n}: TrendAppProps) {\n const { exit } = useApp();\n const [history, setHistory] = useState<TrendHistory | null>(null);\n const [packageTrends, setPackageTrends] = useState<PackageTrend[]>([]);\n const [loaded, setLoaded] = useState(false);\n\n useEffect(() => {\n if (isMonorepo && packagePaths && packageNames) {\n const trends = packagePaths.map((pkgPath) => {\n const name = packageNames.get(pkgPath) ?? pkgPath;\n const h = loadHistory(pkgPath);\n return { name, path: pkgPath, history: h };\n });\n setPackageTrends(trends);\n setLoaded(true);\n\n if (jsonOutput) {\n const output = trends.map((t) => ({\n package: t.name,\n path: t.path,\n history: t.history,\n }));\n process.stdout.write(JSON.stringify(output, null, 2) + '\\n');\n }\n\n setTimeout(() => exit(), 100);\n } else {\n const h = loadHistory(projectPath);\n setHistory(h);\n setLoaded(true);\n\n if (jsonOutput && h) {\n process.stdout.write(JSON.stringify(h, null, 2) + '\\n');\n }\n\n setTimeout(() => exit(), 100);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n if (!loaded) return null;\n\n if (jsonOutput) return null;\n\n // Monorepo: per-package trend summary\n if (isMonorepo && packageTrends.length > 0) {\n const withHistory = packageTrends.filter((t) => t.history && t.history.entries.length > 0);\n\n if (withHistory.length === 0) {\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Header />\n <Text color=\"yellow\">No scan history found for any package in this monorepo.</Text>\n <Box marginTop={1}>\n <Text dimColor>Run </Text>\n <Text color=\"cyan\">sickbay --package <name></Text>\n <Text dimColor> first to start tracking scores.</Text>\n </Box>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Header />\n <Text bold>Monorepo Score Trends</Text>\n <Text dimColor>\n {withHistory.length} of {packageTrends.length} packages have history\n </Text>\n\n <Box flexDirection=\"column\" marginTop={1} marginLeft={2}>\n <Box>\n <Text bold>{'Package'.padEnd(24)}</Text>\n <Text bold>{'Trend'.padEnd(22)}</Text>\n <Text bold>{'Score'.padEnd(10)}</Text>\n <Text bold>Direction</Text>\n </Box>\n <Text dimColor>{'━'.repeat(64)}</Text>\n {withHistory.map((pkg) => {\n const entries = pkg.history!.entries.slice(-last);\n const scores = entries.map((e) => e.overallScore);\n const trend = trendArrow(scores);\n return (\n <Box key={pkg.path}>\n <Text color=\"cyan\">{shortName(pkg.name).padEnd(24)}</Text>\n <Text color={trendColor(trend.direction)}>{sparkline(scores).padEnd(22)}</Text>\n <Text bold>{String(scores[scores.length - 1]).padEnd(10)}</Text>\n <Text color={trendColor(trend.direction)}>{trend.label}</Text>\n </Box>\n );\n })}\n </Box>\n\n {packageTrends.length > withHistory.length && (\n <Box marginTop={1} marginLeft={2}>\n <Text dimColor>\n {packageTrends.length - withHistory.length} package\n {packageTrends.length - withHistory.length !== 1 ? 's' : ''} with no history yet\n </Text>\n </Box>\n )}\n </Box>\n );\n }\n\n // Single project: no history\n if (!history || history.entries.length === 0) {\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Header />\n <Text color=\"yellow\">No scan history found for this project.</Text>\n <Box marginTop={1}>\n <Text dimColor>Run </Text>\n <Text color=\"cyan\">sickbay</Text>\n <Text dimColor> first to start tracking scores.</Text>\n </Box>\n </Box>\n );\n }\n\n // Single project: has history\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Header projectName={history.projectName} />\n <SingleTrendView history={history} last={last} />\n </Box>\n );\n}\n"],"mappings":"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"}
|