Skip to content

Commit de3866f

Browse files
authored
chore(cli): replace chalk and boxen with native formatting (#2003)
Use Node's native styleText for CLI colors and a small local formatter for the experiment summary box. Also pin @braintrust/browser to the workspace braintrust package so local dependency changes are reflected during installs. Our SDK is Node 20+, so these changes are safe.
1 parent 08b95f2 commit de3866f

6 files changed

Lines changed: 74 additions & 158 deletions

File tree

integrations/browser-js/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,10 @@
2525
},
2626
"dependencies": {
2727
"als-browser": "^1.0.1",
28-
"braintrust": ">=3.0.0-rc.29"
28+
"braintrust": "workspace:^"
2929
},
3030
"devDependencies": {
3131
"@types/node": "^20.10.5",
32-
"braintrust": "workspace:*",
3332
"tsup": "^8.5.1",
3433
"typescript": "^5.3.3",
3534
"vitest": "4.1.5"

js/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,6 @@
200200
"@vercel/functions": "^1.0.2",
201201
"ajv": "^8.20.0",
202202
"argparse": "^2.0.1",
203-
"boxen": "^8.0.1",
204-
"chalk": "^4.1.2",
205203
"cli-progress": "^3.12.0",
206204
"cli-table3": "^0.6.5",
207205
"cors": "^2.8.5",

js/src/cli/index.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as dotenv from "dotenv";
55
import fs from "node:fs";
66
import os from "node:os";
77
import path from "node:path";
8-
import util from "node:util";
8+
import util, { styleText } from "node:util";
99
import * as fsWalk from "@nodelib/fs.walk";
1010
import { minimatch } from "minimatch";
1111
import { ArgumentParser } from "argparse";
@@ -25,7 +25,6 @@ import {
2525
BarProgressReporter,
2626
SimpleProgressReporter,
2727
} from "./reporters/progress";
28-
import chalk from "chalk";
2928
import { terminalLink } from "termi-link";
3029

3130
// Re-use the module resolution logic from Jest
@@ -129,8 +128,8 @@ async function initExperiment(
129128
: "locally";
130129
// eslint-disable-next-line no-restricted-properties -- preserving intentional console usage.
131130
console.error(
132-
chalk.cyan("▶") +
133-
` Experiment ${chalk.bold(info.experimentName)} is running at ${linkText}`,
131+
styleText("cyan", "▶") +
132+
` Experiment ${styleText("bold", info.experimentName)} is running at ${linkText}`,
134133
);
135134
return logger;
136135
}
@@ -596,8 +595,9 @@ async function runOnce(
596595

597596
// eslint-disable-next-line no-restricted-properties -- preserving intentional console usage.
598597
console.error(
599-
chalk.dim(
600-
`Processing ${chalk.bold(resultPromises.length)} evaluator${resultPromises.length === 1 ? "" : "s"}...`,
598+
styleText(
599+
"dim",
600+
`Processing ${styleText("bold", String(resultPromises.length))} evaluator${resultPromises.length === 1 ? "" : "s"}...`,
601601
),
602602
);
603603
const allEvalsResults = await Promise.all(resultPromises);

js/src/cli/reporters/eval.ts

Lines changed: 62 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import chalk from "chalk";
1+
import { stripVTControlCharacters, styleText } from "node:util";
22
import { terminalLink } from "termi-link";
3-
import boxen from "boxen";
43
import Table from "cli-table3";
54
import pluralize from "pluralize";
65

@@ -9,10 +8,45 @@ import type { ReporterDef } from "../../reporters/types";
98
import { EvaluatorDef, EvalResultWithSummary } from "../../framework";
109
import { isEmpty } from "../../util";
1110

11+
function visibleLength(text: string) {
12+
return stripVTControlCharacters(text).length;
13+
}
14+
15+
function padEndVisible(text: string, targetLength: number) {
16+
return text + " ".repeat(Math.max(0, targetLength - visibleLength(text)));
17+
}
18+
19+
function formatSummaryBox(content: string) {
20+
const title = styleText("gray", " Experiment summary ");
21+
const lines = content.split("\n");
22+
const contentWidth = Math.max(
23+
visibleLength(title),
24+
...lines.map((line) => visibleLength(line) + 2),
25+
);
26+
27+
const horizontal = "─";
28+
const top =
29+
styleText("gray", "╭") +
30+
title +
31+
styleText(
32+
"gray",
33+
horizontal.repeat(contentWidth - visibleLength(title)) + "╮",
34+
);
35+
const body = lines
36+
.map(
37+
(line) =>
38+
`${styleText("gray", "│")} ${padEndVisible(line, contentWidth - 2)} ${styleText("gray", "│")}`,
39+
)
40+
.join("\n");
41+
const bottom = styleText("gray", "╰" + horizontal.repeat(contentWidth) + "╯");
42+
43+
return top + "\n" + body + "\n" + bottom;
44+
}
45+
1246
function formatExperimentSummaryFancy(summary: ExperimentSummary) {
1347
let comparisonLine = "";
1448
if (summary.comparisonExperimentName) {
15-
comparisonLine = `${summary.comparisonExperimentName} ${chalk.gray("(baseline)")}${summary.experimentName} ${chalk.gray("(comparison)")}\n\n`;
49+
comparisonLine = `${summary.comparisonExperimentName} ${styleText("gray", "(baseline)")}${summary.experimentName} ${styleText("gray", "(comparison)")}\n\n`;
1650
}
1751

1852
const tableParts: string[] = [];
@@ -22,13 +56,13 @@ function formatExperimentSummaryFancy(summary: ExperimentSummary) {
2256
const hasComparison = !!summary.comparisonExperimentName;
2357

2458
if (hasScores || hasMetrics) {
25-
const headers = [chalk.gray("Name"), chalk.gray("Value")];
59+
const headers = [styleText("gray", "Name"), styleText("gray", "Value")];
2660

2761
if (hasComparison) {
2862
headers.push(
29-
chalk.gray("Change"),
30-
chalk.gray("Improvements"),
31-
chalk.gray("Regressions"),
63+
styleText("gray", "Change"),
64+
styleText("gray", "Improvements"),
65+
styleText("gray", "Regressions"),
3266
);
3367
}
3468

@@ -62,28 +96,28 @@ function formatExperimentSummaryFancy(summary: ExperimentSummary) {
6296
const scoreValues: ScoreSummary[] = Object.values(summary.scores);
6397
for (const score of scoreValues) {
6498
const scorePercent = (score.score * 100).toFixed(2);
65-
const scoreValue = chalk.white(`${scorePercent}%`);
99+
const scoreValue = styleText("white", `${scorePercent}%`);
66100

67101
let diffString = "";
68102
if (!isEmpty(score.diff)) {
69103
const diffPercent = (score.diff! * 100).toFixed(2);
70104
const diffSign = score.diff! > 0 ? "+" : "";
71-
const diffColor = score.diff! > 0 ? chalk.green : chalk.red;
72-
diffString = diffColor(`${diffSign}${diffPercent}%`);
105+
const diffColor = score.diff! > 0 ? "green" : "red";
106+
diffString = styleText(diffColor, `${diffSign}${diffPercent}%`);
73107
} else {
74-
diffString = chalk.gray("-");
108+
diffString = styleText("gray", "-");
75109
}
76110

77111
const improvements =
78112
score.improvements > 0
79-
? chalk.dim.green(score.improvements)
80-
: chalk.gray("-");
113+
? styleText(["dim", "green"], String(score.improvements))
114+
: styleText("gray", "-");
81115
const regressions =
82116
score.regressions > 0
83-
? chalk.dim.red(score.regressions)
84-
: chalk.gray("-");
117+
? styleText(["dim", "red"], String(score.regressions))
118+
: styleText("gray", "-");
85119

86-
const row = [`${chalk.blue("◯")} ${score.name}`, scoreValue];
120+
const row = [`${styleText("blue", "◯")} ${score.name}`, scoreValue];
87121
if (hasComparison) {
88122
row.push(diffString, improvements, regressions);
89123
}
@@ -94,7 +128,8 @@ function formatExperimentSummaryFancy(summary: ExperimentSummary) {
94128
for (const metric of metricValues) {
95129
const fractionDigits = Number.isInteger(metric.metric) ? 0 : 2;
96130
const formattedValue = metric.metric.toFixed(fractionDigits);
97-
const metricValue = chalk.white(
131+
const metricValue = styleText(
132+
"white",
98133
metric.unit === "$"
99134
? `${metric.unit}${formattedValue}`
100135
: `${formattedValue}${metric.unit}`,
@@ -104,22 +139,22 @@ function formatExperimentSummaryFancy(summary: ExperimentSummary) {
104139
if (!isEmpty(metric.diff)) {
105140
const diffPercent = (metric.diff! * 100).toFixed(2);
106141
const diffSign = metric.diff! > 0 ? "+" : "";
107-
const diffColor = metric.diff! > 0 ? chalk.green : chalk.red;
108-
diffString = diffColor(`${diffSign}${diffPercent}%`);
142+
const diffColor = metric.diff! > 0 ? "green" : "red";
143+
diffString = styleText(diffColor, `${diffSign}${diffPercent}%`);
109144
} else {
110-
diffString = chalk.gray("-");
145+
diffString = styleText("gray", "-");
111146
}
112147

113148
const improvements =
114149
metric.improvements > 0
115-
? chalk.dim.green(metric.improvements)
116-
: chalk.gray("-");
150+
? styleText(["dim", "green"], String(metric.improvements))
151+
: styleText("gray", "-");
117152
const regressions =
118153
metric.regressions > 0
119-
? chalk.dim.red(metric.regressions)
120-
: chalk.gray("-");
154+
? styleText(["dim", "red"], String(metric.regressions))
155+
: styleText("gray", "-");
121156

122-
const row = [`${chalk.magenta("◯")} ${metric.name}`, metricValue];
157+
const row = [`${styleText("magenta", "◯")} ${metric.name}`, metricValue];
123158
if (hasComparison) {
124159
row.push(diffString, improvements, regressions);
125160
}
@@ -141,23 +176,10 @@ function formatExperimentSummaryFancy(summary: ExperimentSummary) {
141176

142177
const boxContent = [content, footer].filter(Boolean).join("\n\n");
143178

144-
try {
145-
return (
146-
"\n" +
147-
boxen(boxContent, {
148-
title: chalk.gray("Experiment summary"),
149-
titleAlignment: "left",
150-
padding: 0.5,
151-
borderColor: "gray",
152-
borderStyle: "round",
153-
})
154-
);
155-
} catch {
156-
return "\n" + chalk.gray("Experiment summary") + "\n" + boxContent + "\n";
157-
}
179+
return "\n" + formatSummaryBox(boxContent);
158180
}
159181

160-
export const warning = chalk.yellow;
182+
export const warning = (text: string) => styleText("yellow", text);
161183

162184
export const fancyReporter: ReporterDef<boolean> = {
163185
name: "Braintrust fancy reporter",

js/src/cli/reporters/progress.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import chalk from "chalk";
1+
import { styleText } from "node:util";
22
import * as cliProgress from "cli-progress";
33

44
import type { ProgressReporter } from "../../reporters/types";
@@ -22,7 +22,7 @@ export class BarProgressReporter implements ProgressReporter {
2222
constructor() {
2323
this.multiBar = new cliProgress.MultiBar(
2424
{
25-
format: `${chalk.blueBright("{bar}")} ${chalk.blue("{evaluator}")} {percentage}% ${chalk.gray("{value}/{total} {eta_formatted}")}`,
25+
format: `${styleText("blueBright", "{bar}")} ${styleText("blue", "{evaluator}")} {percentage}% ${styleText("gray", "{value}/{total} {eta_formatted}")}`,
2626
hideCursor: true,
2727
barsize: 10,
2828
},

0 commit comments

Comments
 (0)