a reactive (signals based) hypermedia web framework (wip)
stormlightlabs.github.io/volt/
hypermedia
frontend
signals
1import { echo } from "$console/echo.js";
2import { findMonorepoRoot, getLibSrcPath, getLibTestPath } from "$utils/paths.js";
3import { readdir, readFile, stat } from "node:fs/promises";
4import path from "node:path";
5
6type FileStats = { path: string; lines: number; totalLines: number };
7type DirectoryStats = { totalLines: number; codeLines: number; files: FileStats[] };
8type Lines = { lines: number; totalLines: number };
9
10/**
11 * Count lines of code in a file, excluding doc comments
12 */
13async function countLines(filePath: string): Promise<Lines> {
14 const content = await readFile(filePath, "utf8");
15 const lines = content.split("\n");
16
17 let codeLines = 0;
18 let inDocComment = false;
19
20 for (const line of lines) {
21 const trimmed = line.trim();
22
23 if (trimmed.startsWith("/**")) {
24 inDocComment = true;
25 continue;
26 }
27
28 if (inDocComment) {
29 if (trimmed.endsWith("*/")) {
30 inDocComment = false;
31 }
32 continue;
33 }
34
35 if (trimmed === "" || trimmed.startsWith("//")) {
36 continue;
37 }
38
39 codeLines++;
40 }
41
42 return { lines: codeLines, totalLines: lines.length };
43}
44
45/**
46 * Recursively walk a directory and collect TypeScript files
47 */
48async function walkDirectory(dir: string, files: string[] = []): Promise<string[]> {
49 const entries = await readdir(dir);
50
51 for (const entry of entries) {
52 const fullPath = path.join(dir, entry);
53 const stats = await stat(fullPath);
54
55 if (stats.isDirectory()) {
56 if (entry !== "node_modules" && entry !== "dist" && entry !== ".git") {
57 await walkDirectory(fullPath, files);
58 }
59 } else if (stats.isFile() && entry.endsWith(".ts")) {
60 files.push(fullPath);
61 }
62 }
63
64 return files;
65}
66
67/**
68 * Collect stats for a directory
69 */
70async function collectStats(directory: string, baseDir: string): Promise<DirectoryStats> {
71 const files = await walkDirectory(directory);
72 const fileStats: FileStats[] = [];
73 let totalLines = 0;
74 let codeLines = 0;
75
76 for (const file of files) {
77 const { lines, totalLines: total } = await countLines(file);
78 const relativePath = path.relative(baseDir, file);
79
80 fileStats.push({ path: relativePath, lines, totalLines: total });
81
82 codeLines += lines;
83 totalLines += total;
84 }
85
86 return { totalLines, codeLines, files: fileStats };
87}
88
89/**
90 * Stats command implementation
91 */
92export async function statsCommand(includeFull: boolean): Promise<void> {
93 const monorepoRoot = await findMonorepoRoot();
94 const srcDir = await getLibSrcPath();
95 const srcStats = await collectStats(srcDir, monorepoRoot);
96
97 echo.title("\nVolt.js Code Statistics\n");
98
99 echo.label("Source Code (src/):");
100 echo.text(` Files: ${srcStats.files.length}`);
101 echo.text(` Total Lines: ${srcStats.totalLines}`);
102 echo.ok(` Code Lines: ${srcStats.codeLines}`);
103 echo.text(` Doc/Comments: ${srcStats.totalLines - srcStats.codeLines}`);
104
105 let totalCode = srcStats.codeLines;
106 let totalTotal = srcStats.totalLines;
107 let totalFileCount = srcStats.files.length;
108
109 if (includeFull) {
110 const testDir = await getLibTestPath();
111 const testStats = await collectStats(testDir, monorepoRoot);
112
113 echo.label("\nTest Code (test/):");
114 echo.text(` Files: ${testStats.files.length}`);
115 echo.text(` Total Lines: ${testStats.totalLines}`);
116 echo.ok(` Code Lines: ${testStats.codeLines}`);
117 echo.text(` Doc/Comments: ${testStats.totalLines - testStats.codeLines}`);
118
119 totalCode += testStats.codeLines;
120 totalTotal += testStats.totalLines;
121 totalFileCount += testStats.files.length;
122 }
123
124 echo.title("\nTotal:");
125 echo.text(` Files: ${totalFileCount}`);
126 echo.text(` Total Lines: ${totalTotal}`);
127 echo.success(` Code Lines: ${totalCode}`);
128 echo.text(` Doc/Comments: ${totalTotal - totalCode}`);
129
130 if (process.env.VERBOSE) {
131 echo.warn("\n\nFile Breakdown:");
132 for (const file of srcStats.files) {
133 echo.text(` ${file.path}: ${file.lines} lines`);
134 }
135 }
136
137 echo.text();
138}