-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstats.ts
More file actions
164 lines (145 loc) · 5.14 KB
/
Copy pathstats.ts
File metadata and controls
164 lines (145 loc) · 5.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/**
* scripts/stats.ts — Derive canonical product stats at build time.
*
* Source of truth for: "N test files", "N source files", "vX.Y.Z", "N built-in plugins",
* and "NKB bundle" — the numbers that appear in the README and the web app.
*
* Run with: npx tsx scripts/stats.ts
* Output: scripts/stats.json (canonical)
*/
import { execSync } from 'node:child_process';
import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
const ROOT = resolve(SCRIPT_DIR, '..');
const CLI_DIR = join(ROOT, 'packages/cli');
const TEST_DIR = join(CLI_DIR, 'test');
const SRC_DIR = join(CLI_DIR, 'src');
const PLUGINS_INDEX = join(SRC_DIR, 'plugins/index.ts');
const VERSION_FILE = join(ROOT, 'VERSION');
const TEST_EXCLUDES = ['fixtures', 'fixture-app', 'node_modules'];
function walk(dir: string, predicate: (path: string) => boolean, excludeDirs: string[] = []): string[] {
const out: string[] = [];
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (entry.name.startsWith('.')) continue;
const full = join(dir, entry.name);
if (entry.isDirectory()) {
if (excludeDirs.includes(entry.name)) continue;
out.push(...walk(full, predicate, excludeDirs));
} else if (entry.isFile() && predicate(full)) {
out.push(full);
}
}
return out;
}
function countTestFiles(): number {
return walk(TEST_DIR, (p) => p.endsWith('.test.ts'), TEST_EXCLUDES).length;
}
function countSourceFiles(): number {
return walk(SRC_DIR, (p) => p.endsWith('.ts'), ['node_modules']).length;
}
function readVersion(): string {
return readFileSync(VERSION_FILE, 'utf8').trim();
}
function countPlugins(): number {
const txt = readFileSync(PLUGINS_INDEX, 'utf8');
// Built-in plugins are exported via `create<Name>Plugin` factory functions.
const matches = txt.match(/export\s*\{[^}]*create[A-Z][A-Za-z]+Plugin/g) ?? [];
// Each export block may export multiple factories — explode and re-match.
const factories = new Set<string>();
for (const block of matches) {
for (const m of block.matchAll(/create([A-Z][A-Za-z]+)Plugin/g)) {
factories.add(m[1]);
}
}
return factories.size;
}
interface PackResult {
size: number; // tarball size in bytes
unpackedSize: number;
}
function ensureBuilt(): void {
// npm pack reflects the contents of `files` (which lists dist/). When dist is
// missing the tarball reports an artificially tiny size — guard against that
// by running the workspace build first.
if (!existsSync(join(CLI_DIR, 'dist'))) {
execSync('npm run build:cli', { cwd: ROOT, stdio: 'inherit' });
}
}
function bundleSize(): { tarballBytes: number; tarballKB: number; unpackedBytes: number; unpackedKB: number } {
ensureBuilt();
const tmp = mkdtempSync(join(tmpdir(), 'frontguard-stats-'));
try {
const raw = execSync(`npm pack "${CLI_DIR}" --pack-destination "${tmp}" --json`, {
stdio: ['ignore', 'pipe', 'pipe'],
encoding: 'utf8',
});
const parsed = JSON.parse(raw) as PackResult[];
const first = parsed[0];
if (!first) throw new Error('npm pack returned empty JSON');
return {
tarballBytes: first.size,
tarballKB: Math.round(first.size / 1024),
unpackedBytes: first.unpackedSize,
unpackedKB: Math.round(first.unpackedSize / 1024),
};
} finally {
rmSync(tmp, { recursive: true, force: true });
}
}
function fileSizeBytes(path: string): number {
try {
return statSync(path).size;
} catch {
return 0;
}
}
function generated(): string {
// Avoid Date.now()/new Date() — deterministic stamp from VERSION.
// The generated timestamp is intentionally absent; the JSON is regenerated on every build.
return 'derived-from-source';
}
function main(): void {
const testFiles = countTestFiles();
const sourceFiles = countSourceFiles();
const version = readVersion();
const plugins = countPlugins();
const bundle = bundleSize();
const payload = {
version,
testFiles,
sourceFiles,
plugins,
bundleSize: {
tarballBytes: bundle.tarballBytes,
tarballKB: bundle.tarballKB,
unpackedBytes: bundle.unpackedBytes,
unpackedKB: bundle.unpackedKB,
},
// Convenience shortcuts used throughout the docs prose.
display: {
testFiles: `${testFiles} test files`,
sourceFiles: `${sourceFiles} source files`,
plugins: `${plugins} built-in plugins`,
version: `v${version}`,
bundle: `${bundle.tarballKB}KB`,
},
source: generated(),
};
const canonicalPath = join(SCRIPT_DIR, 'stats.json');
writeFileSync(canonicalPath, JSON.stringify(payload, null, 2) + '\n');
// eslint-disable-next-line no-console
console.log(
[
`Frontguard stats (v${version})`,
` test files: ${testFiles}`,
` source files: ${sourceFiles}`,
` plugins: ${plugins}`,
` bundle: ${bundle.tarballKB}KB (tarball) / ${bundle.unpackedKB}KB (unpacked)`,
` → ${canonicalPath}`,
].join('\n'),
);
}
main();