Skip to content

Commit 0c4ed93

Browse files
committed
feat(colors): implement minimal chalk replacement for styled console output
1 parent 2abed83 commit 0c4ed93

5 files changed

Lines changed: 101 additions & 78 deletions

File tree

benchmark/colors.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { styleText } from "node:util";
2+
3+
// Minimal chalk replacement built on node:util's styleText (stable since
4+
// v22.13), which handles TTY detection and NO_COLOR/FORCE_COLOR for us.
5+
// Formats are applied one at a time for compatibility with all of Node 22.x.
6+
const style =
7+
(...formats: Parameters<typeof styleText>[0][]) =>
8+
(text: string) =>
9+
formats.reduce((styled, format) => styleText(format, styled), text);
10+
11+
export const colors = {
12+
red: style("red"),
13+
green: style("green"),
14+
yellow: style("yellow"),
15+
gray: style("gray"),
16+
cyan: style("cyan"),
17+
bold: {
18+
red: style("bold", "red"),
19+
yellow: style("bold", "yellow"),
20+
cyan: style("bold", "cyan"),
21+
},
22+
};

benchmark/index.ts

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#!/usr/bin/env tsx
22

3-
import chalk from "chalk";
43
import { mkdtempSync, rmSync } from "node:fs";
54
import { tmpdir } from "node:os";
65
import { join } from "node:path";
@@ -9,6 +8,7 @@ import {
98
parseCacheProfile,
109
type CacheProfile,
1110
} from "./cache-profile.js";
11+
import { colors } from "./colors.js";
1212
import {
1313
createDriver,
1414
getAvailableDrivers,
@@ -109,7 +109,7 @@ for (let i = 0; i < args.length; i++) {
109109
} else if (args[i] === "--iterations" && i + 1 < args.length) {
110110
const iterations = Number(args[i + 1]);
111111
if (!Number.isInteger(iterations) || iterations < 1) {
112-
console.error(chalk.red("--iterations must be a positive integer"));
112+
console.error(colors.red("--iterations must be a positive integer"));
113113
process.exit(1);
114114
}
115115
options.iterations = iterations;
@@ -122,7 +122,7 @@ for (let i = 0; i < args.length; i++) {
122122
}
123123
options.cacheProfile = parseCacheProfile(profile);
124124
} catch (error) {
125-
console.error(chalk.red((error as Error).message));
125+
console.error(colors.red((error as Error).message));
126126
process.exit(1);
127127
}
128128
i++;
@@ -163,7 +163,7 @@ const driversToTest = options.drivers ?? getAvailableDrivers();
163163
const scenarios = getScenarios(options.filter);
164164

165165
if (scenarios.length === 0) {
166-
console.error(chalk.red(`No scenarios found matching: ${options.filter}`));
166+
console.error(colors.red(`No scenarios found matching: ${options.filter}`));
167167
process.exit(1);
168168
}
169169

@@ -268,9 +268,9 @@ async function inspectBenchmarkSettings(
268268

269269
// Main benchmark function wrapped in async IIFE for CJS compatibility
270270
(async () => {
271-
console.log(chalk.bold.cyan("🚀 SQLite Driver Performance Benchmark\n"));
272-
console.log(chalk.gray(`Testing drivers: ${driversToTest.join(", ")}`));
273-
console.log(chalk.gray(`Scenarios: ${scenarios.length}\n`));
271+
console.log(colors.bold.cyan("🚀 SQLite Driver Performance Benchmark\n"));
272+
console.log(colors.gray(`Testing drivers: ${driversToTest.join(", ")}`));
273+
console.log(colors.gray(`Scenarios: ${scenarios.length}\n`));
274274

275275
// Drivers actually available this run, in the order requested.
276276
const driverList = driversToTest.filter((d) =>
@@ -280,13 +280,13 @@ async function inspectBenchmarkSettings(
280280
throw new Error("No requested benchmark drivers are available");
281281
}
282282

283-
console.log(chalk.gray(`Cache profile: ${options.cacheProfile}`));
283+
console.log(colors.gray(`Cache profile: ${options.cacheProfile}`));
284284
const benchmarkSettings: Record<string, BenchmarkSettings> = {};
285285
for (const driverName of driverList) {
286286
const settings = await inspectBenchmarkSettings(driverName);
287287
benchmarkSettings[driverName] = settings;
288288
console.log(
289-
chalk.gray(
289+
colors.gray(
290290
` ${driverName}: cache_size=${settings.effectiveCacheSize} ` +
291291
`(packaged=${settings.initialCacheSize}), ` +
292292
`journal_mode=${settings.journalMode}, ` +
@@ -315,17 +315,17 @@ async function inspectBenchmarkSettings(
315315
const isWarmup = pass === 0;
316316

317317
if (isWarmup) {
318-
console.log(chalk.gray("Warmup pass...\n"));
318+
console.log(colors.gray("Warmup pass...\n"));
319319
} else {
320-
console.log(chalk.bold.cyan("\nMeasured pass:\n"));
320+
console.log(colors.bold.cyan("\nMeasured pass:\n"));
321321
}
322322

323323
for (const [scenarioKey, scenario] of scenarios) {
324324
if (isWarmup) {
325-
process.stdout.write(chalk.gray(`\n ${scenario.name}:`));
325+
process.stdout.write(colors.gray(`\n ${scenario.name}:`));
326326
} else {
327-
console.log(chalk.bold.yellow(`\n📊 ${scenario.name}`));
328-
console.log(chalk.gray(` ${scenario.description}`));
327+
console.log(colors.bold.yellow(`\n📊 ${scenario.name}`));
328+
console.log(colors.gray(` ${scenario.description}`));
329329
}
330330

331331
if (!isWarmup) {
@@ -382,8 +382,8 @@ async function inspectBenchmarkSettings(
382382
} catch (err) {
383383
failed.add(driverName);
384384
const msg = `✗ Error in ${driverName}: ${(err as Error).message}`;
385-
if (isWarmup) process.stdout.write(chalk.yellow(` [${msg}]`));
386-
else console.error(chalk.red(` ${msg}`));
385+
if (isWarmup) process.stdout.write(colors.yellow(` [${msg}]`));
386+
else console.error(colors.red(` ${msg}`));
387387
}
388388
}
389389
}
@@ -396,13 +396,13 @@ async function inspectBenchmarkSettings(
396396

397397
if (isWarmup) {
398398
process.stdout.write(
399-
chalk.gray(
399+
colors.gray(
400400
` ${driverName}:${Math.round(opsPerSec).toLocaleString()}`,
401401
),
402402
);
403403
} else {
404404
console.log(
405-
chalk.green(
405+
colors.green(
406406
` ${driverName}: ${Math.round(opsPerSec).toLocaleString()} ops/sec ±${rme.toFixed(1)}% (${s.length} trials × ${iters[driverName].toLocaleString()} iters)`,
407407
),
408408
);
@@ -425,7 +425,7 @@ async function inspectBenchmarkSettings(
425425
}
426426

427427
// Summary
428-
console.log(chalk.bold.cyan("\n\n### 📈 Summary\n"));
428+
console.log(colors.bold.cyan("\n\n### 📈 Summary\n"));
429429

430430
// Keep the configuration directly beside the copyable Markdown table. The
431431
// startup preamble is useful interactively, but is easy to omit when results
@@ -521,15 +521,15 @@ async function inspectBenchmarkSettings(
521521
"‡ batched write — one durable commit amortized over ~1000 rows, so " +
522522
"driver differences remain visible (don't read these as ties).",
523523
);
524-
console.log("\n" + chalk.gray(notes.join("\n")));
524+
console.log("\n" + colors.gray(notes.join("\n")));
525525
}
526526

527527
// Memory usage report
528528
if (options.memory && global.gc) {
529529
global.gc();
530530
const memoryFinal = process.memoryUsage();
531531

532-
console.log(chalk.bold.cyan("\n\n### 💾 Memory Usage\n"));
532+
console.log(colors.bold.cyan("\n\n### 💾 Memory Usage\n"));
533533

534534
const formatMB = (bytes: number) =>
535535
`${(bytes / 1024 / 1024).toFixed(1)} MB`;
@@ -548,7 +548,7 @@ async function inspectBenchmarkSettings(
548548

549549
console.log(
550550
"\n" +
551-
chalk.gray(
551+
colors.gray(
552552
"📋 Memory table generated above - copy/paste ready for documentation!",
553553
),
554554
);
@@ -575,8 +575,8 @@ async function inspectBenchmarkSettings(
575575
});
576576
writeCharts(charts, outDir);
577577
console.log(
578-
chalk.bold.cyan(`\n\n### 📊 Charts\n`) +
579-
chalk.gray(`Wrote ${charts.size} SVG chart(s) to ${outDir}`),
578+
colors.bold.cyan(`\n\n### 📊 Charts\n`) +
579+
colors.gray(`Wrote ${charts.size} SVG chart(s) to ${outDir}`),
580580
);
581581
}
582582

benchmark/memory-benchmark.ts

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
import { createDriver, getAvailableDrivers, type Driver } from "./drivers.js";
44
import { LEAK_MIN_R2, MemoryTracker } from "./memory-tracker.js";
55
// import Table from 'cli-table3'; // Removed - using markdown tables instead
6-
import chalk from "chalk";
76
import { mkdtempSync, rmSync } from "node:fs";
87
import { tmpdir } from "node:os";
98
import { join } from "node:path";
9+
import { colors } from "./colors.js";
1010

1111
interface MemoryScenario {
1212
name: string;
@@ -208,23 +208,23 @@ Available scenarios: ${Object.keys(memoryScenarios).join(", ")}
208208
// Check if GC is exposed
209209
if (typeof global.gc !== "function") {
210210
console.log(
211-
chalk.yellow(
211+
colors.yellow(
212212
"⚠️ Warning: GC not exposed. Run with --expose-gc for accurate results.",
213213
),
214214
);
215215
console.log(
216-
chalk.gray(" tsx --expose-gc benchmark/memory-benchmark.ts\n"),
216+
colors.gray(" tsx --expose-gc benchmark/memory-benchmark.ts\n"),
217217
);
218218
}
219219

220220
const driversToTest = options.drivers ?? getAvailableDrivers();
221221
const scenariosToRun = options.scenarios ?? Object.keys(memoryScenarios);
222222

223-
console.log(chalk.bold.cyan("💾 SQLite Driver Memory Benchmark\n"));
224-
console.log(chalk.gray(`Testing drivers: ${driversToTest.join(", ")}`));
225-
console.log(chalk.gray(`Scenarios: ${scenariosToRun.length}`));
223+
console.log(colors.bold.cyan("💾 SQLite Driver Memory Benchmark\n"));
224+
console.log(colors.gray(`Testing drivers: ${driversToTest.join(", ")}`));
225+
console.log(colors.gray(`Scenarios: ${scenariosToRun.length}`));
226226
console.log(
227-
chalk.gray(
227+
colors.gray(
228228
`Iterations: ${options.iterations ?? "auto-calibrated"} (with ${options.warmup} warmup)\n`,
229229
),
230230
);
@@ -234,22 +234,22 @@ Available scenarios: ${Object.keys(memoryScenarios).join(", ")}
234234
// Test each driver
235235
for (const driverName of driversToTest) {
236236
if (!getAvailableDrivers().includes(driverName)) {
237-
console.log(chalk.gray(`Skipping ${driverName} (not available)`));
237+
console.log(colors.gray(`Skipping ${driverName} (not available)`));
238238
continue;
239239
}
240240

241-
console.log(chalk.bold.yellow(`\n📊 Testing ${driverName}`));
241+
console.log(colors.bold.yellow(`\n📊 Testing ${driverName}`));
242242
results[driverName] = {};
243243

244244
// Run each scenario
245245
for (const scenarioKey of scenariosToRun) {
246246
const scenario = memoryScenarios[scenarioKey];
247247
if (!scenario) {
248-
console.log(chalk.gray(` Skipping unknown scenario: ${scenarioKey}`));
248+
console.log(colors.gray(` Skipping unknown scenario: ${scenarioKey}`));
249249
continue;
250250
}
251251

252-
console.log(chalk.gray(`\n ${scenario.name}: ${scenario.description}`));
252+
console.log(colors.gray(`\n ${scenario.name}: ${scenario.description}`));
253253

254254
// Create temporary database
255255
const tempDir = mkdtempSync(join(tmpdir(), "sqlite-mem-"));
@@ -267,7 +267,7 @@ Available scenarios: ${Object.keys(memoryScenarios).join(", ")}
267267
// Calibrate iterations if not specified
268268
let iterations = options.iterations;
269269
if (!iterations) {
270-
console.log(chalk.gray(` Calibrating iterations...`));
270+
console.log(colors.gray(` Calibrating iterations...`));
271271
const calibrationStart = Date.now();
272272
let calibrationIterations = 0;
273273

@@ -279,7 +279,7 @@ Available scenarios: ${Object.keys(memoryScenarios).join(", ")}
279279

280280
// Use calibrated count, but ensure reasonable bounds
281281
iterations = Math.max(20, Math.min(200, calibrationIterations));
282-
console.log(chalk.gray(` Using ${iterations} iterations`));
282+
console.log(colors.gray(` Using ${iterations} iterations`));
283283
}
284284

285285
// Check for memory leaks with configurable threshold
@@ -304,23 +304,23 @@ Available scenarios: ${Object.keys(memoryScenarios).join(", ")}
304304

305305
// Display results
306306
if (leakTest.likelyLeak) {
307-
console.log(chalk.red(` ⚠️ Potential memory leak detected!`));
307+
console.log(colors.red(` ⚠️ Potential memory leak detected!`));
308308
} else {
309-
console.log(chalk.green(` ✓ No memory leak detected`));
309+
console.log(colors.green(` ✓ No memory leak detected`));
310310
}
311311

312312
console.log(
313-
chalk.gray(
313+
colors.gray(
314314
` Heap growth: ${leakTest.summary.heapGrowth} (R²=${leakTest.summary.heapR2})`,
315315
),
316316
);
317317
console.log(
318-
chalk.gray(
318+
colors.gray(
319319
` External growth: ${leakTest.summary.externalGrowth} (R²=${leakTest.summary.externalR2})`,
320320
),
321321
);
322322
console.log(
323-
chalk.gray(
323+
colors.gray(
324324
` Confidence: ${leakTest.heapTrend.r2 > 0.9 ? "High" : leakTest.heapTrend.r2 > 0.7 ? "Medium" : "Low"} (based on R² values)`,
325325
),
326326
);
@@ -332,7 +332,7 @@ Available scenarios: ${Object.keys(memoryScenarios).join(", ")}
332332

333333
await driver.close();
334334
} catch (error) {
335-
console.error(chalk.red(` ✗ Error: ${(error as Error).message}`));
335+
console.error(colors.red(` ✗ Error: ${(error as Error).message}`));
336336
results[driverName][scenarioKey] = { error: (error as Error).message };
337337
} finally {
338338
// Clean up
@@ -346,7 +346,7 @@ Available scenarios: ${Object.keys(memoryScenarios).join(", ")}
346346
}
347347

348348
// Summary table
349-
console.log(chalk.bold.cyan("\n\n📈 Summary\n"));
349+
console.log(colors.bold.cyan("\n\n📈 Summary\n"));
350350

351351
// Generate markdown table
352352
const availableDrivers = driversToTest.filter((d) =>
@@ -381,7 +381,7 @@ Available scenarios: ${Object.keys(memoryScenarios).join(", ")}
381381

382382
console.log(
383383
"\n" +
384-
chalk.gray(
384+
colors.gray(
385385
"📋 Memory table generated above - copy/paste ready for documentation!",
386386
),
387387
);
@@ -397,11 +397,11 @@ Available scenarios: ${Object.keys(memoryScenarios).join(", ")}
397397
}
398398

399399
if (leaks.length > 0) {
400-
console.log(chalk.bold.red("\n\n⚠️ Memory Leak Details\n"));
400+
console.log(colors.bold.red("\n\n⚠️ Memory Leak Details\n"));
401401

402402
for (const { driver, scenario, result } of leaks) {
403403
console.log(
404-
chalk.yellow(`${driver} - ${memoryScenarios[scenario].name}:`),
404+
colors.yellow(`${driver} - ${memoryScenarios[scenario].name}:`),
405405
);
406406
console.log(
407407
` Heap growth: ${result.summary.heapGrowth} (R²=${result.summary.heapR2})`,

benchmark/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
},
1717
"dependencies": {
1818
"@photostructure/sqlite": "file:..",
19-
"better-sqlite3": "13.0.3",
20-
"chalk": "^5.6.2"
19+
"better-sqlite3": "13.0.3"
2120
},
2221
"devDependencies": {
2322
"@types/node": "26.1.1",

0 commit comments

Comments
 (0)