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.
Files changed (63) hide show
  1. package/README.md +1 -1
  2. package/dist/DiffApp-DeNEtlY7.js +181 -0
  3. package/dist/DiffApp-DeNEtlY7.js.map +1 -0
  4. package/dist/DoctorApp-CaiqnhHH.js +415 -0
  5. package/dist/DoctorApp-CaiqnhHH.js.map +1 -0
  6. package/dist/FixApp-MWXohP60.js +337 -0
  7. package/dist/FixApp-MWXohP60.js.map +1 -0
  8. package/dist/StatsApp-BC4P4hII.js +372 -0
  9. package/dist/StatsApp-BC4P4hII.js.map +1 -0
  10. package/dist/TrendApp-DOBt7ITA.js +127 -0
  11. package/dist/TrendApp-DOBt7ITA.js.map +1 -0
  12. package/dist/TuiApp-D4Wn81Qv.js +1042 -0
  13. package/dist/TuiApp-D4Wn81Qv.js.map +1 -0
  14. package/dist/ai-DxA5r3hA.js +69 -0
  15. package/dist/ai-DxA5r3hA.js.map +1 -0
  16. package/dist/badge-DB3y0fWt.js +37 -0
  17. package/dist/badge-DB3y0fWt.js.map +1 -0
  18. package/dist/{claude-SN6IVGBI.js → claude-C5dUtWBJ.js} +13 -12
  19. package/dist/claude-C5dUtWBJ.js.map +1 -0
  20. package/dist/config.d.ts +27 -1
  21. package/dist/config.d.ts.map +1 -0
  22. package/dist/config.js +2 -6
  23. package/dist/dist-Bjhxbcak.js +5431 -0
  24. package/dist/dist-Bjhxbcak.js.map +1 -0
  25. package/dist/history-DVegAwFp.js +83 -0
  26. package/dist/history-DVegAwFp.js.map +1 -0
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.js +692 -564
  29. package/dist/index.js.map +1 -0
  30. package/dist/init-Dn7eDMEp.js +199 -0
  31. package/dist/init-Dn7eDMEp.js.map +1 -0
  32. package/dist/resolve-package-DS27JBhN.js +60 -0
  33. package/dist/resolve-package-DS27JBhN.js.map +1 -0
  34. package/dist/rolldown-runtime-CiIaOW0V.js +13 -0
  35. package/dist/trend-D-Hufthb.js +45 -0
  36. package/dist/trend-D-Hufthb.js.map +1 -0
  37. package/dist/update-check-C7bNNfvf.js +58 -0
  38. package/dist/update-check-C7bNNfvf.js.map +1 -0
  39. package/dist/web-D5etye5Q.js +259 -0
  40. package/dist/web-D5etye5Q.js.map +1 -0
  41. package/package.json +6 -5
  42. package/dist/DiffApp-ASQWWDLS.js +0 -172
  43. package/dist/DoctorApp-SMIZDD64.js +0 -440
  44. package/dist/FixApp-63PU5VF7.js +0 -351
  45. package/dist/StatsApp-CFZBEITZ.js +0 -356
  46. package/dist/TrendApp-BKGRSP6Q.js +0 -106
  47. package/dist/TuiApp-K7KP35RC.js +0 -980
  48. package/dist/ai-7DGOLNJX.js +0 -64
  49. package/dist/badge-KQ73KEIN.js +0 -41
  50. package/dist/chunk-352RAVRZ.js +0 -19
  51. package/dist/chunk-3OR2GFVE.js +0 -90
  52. package/dist/chunk-DJA6K3MN.js +0 -5564
  53. package/dist/chunk-LZOEMCE7.js +0 -62
  54. package/dist/chunk-MBVA75EM.js +0 -19
  55. package/dist/chunk-SHO3ZXTH.js +0 -23
  56. package/dist/chunk-TWCTPAXQ.js +0 -73
  57. package/dist/chunk-VBM6NZAY.js +0 -60
  58. package/dist/dist-LQY3FYCP.js +0 -64
  59. package/dist/history-XLNVZEDI.js +0 -14
  60. package/dist/init-DD2HOQE2.js +0 -217
  61. package/dist/resolve-package-774YTW5Q.js +0 -9
  62. package/dist/update-check-M7IFOMLJ.js +0 -12
  63. 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 &lt;name&gt;</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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"}