Skip to content

Commit b953744

Browse files
author
Benoit COUETIL
committed
fix: use string-width for emoji-aware column alignment in --list and log output
1 parent 36923f2 commit b953744

6 files changed

Lines changed: 422 additions & 402 deletions

File tree

bun.lock

Lines changed: 397 additions & 390 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"re2js": "1.x.x",
4646
"semver": "7.x.x",
4747
"split2": "4.x.x",
48+
"string-width": "^8.2.0",
4849
"terminal-link": "5.x.x",
4950
"yargs": "18.x"
5051
},

src/commander.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export class Commander {
137137
let prefix = "";
138138
if (argv.childPipelineDepth > 0) prefix = `[${argv.variable.GCL_TRIGGERER}] -> `;
139139

140-
const namePad = name.padEnd(jobNamePad);
140+
const namePad = Utils.padEndVisual(name, jobNamePad);
141141
writeStreams.stdout(chalk`{black.bgGreenBright PASS }${renderDuration(prettyDuration)} {blueBright ${prefix}${namePad}}`);
142142
if (coveragePercent) {
143143
writeStreams.stdout(chalk` ${coveragePercent}% {grey coverage}`);
@@ -149,7 +149,7 @@ export class Commander {
149149
if (preScripts.warned.length !== 0) {
150150
preScripts.warned.sort((a, b) => stages.indexOf(a.stage) - stages.indexOf(b.stage));
151151
for (const {name, prettyDuration} of preScripts.warned) {
152-
const namePad = name.padEnd(jobNamePad);
152+
const namePad = Utils.padEndVisual(name, jobNamePad);
153153
const safeName = Utils.safeDockerString(name);
154154
writeStreams.stdout(chalk`{black.bgYellowBright WARN }${renderDuration(prettyDuration)} {blueBright ${namePad}} pre_script\n`);
155155
const outputLog = await fs.readFile(`${cwd}/${stateDir}/output/${safeName}.log`, "utf8");
@@ -163,15 +163,15 @@ export class Commander {
163163
if (afterScripts.warned.length !== 0) {
164164
afterScripts.warned.sort((a, b) => stages.indexOf(a.stage) - stages.indexOf(b.stage));
165165
afterScripts.warned.forEach(({name, prettyDuration}) => {
166-
const namePad = name.padEnd(jobNamePad);
166+
const namePad = Utils.padEndVisual(name, jobNamePad);
167167
writeStreams.stdout(chalk`{black.bgYellowBright WARN }${renderDuration(prettyDuration)} {blueBright ${namePad}} after_script\n`);
168168
});
169169
}
170170

171171
if (preScripts.failed.length !== 0) {
172172
preScripts.failed.sort((a, b) => stages.indexOf(a.stage) - stages.indexOf(b.stage));
173173
for (const {name, prettyDuration} of preScripts.failed) {
174-
const namePad = name.padEnd(jobNamePad);
174+
const namePad = Utils.padEndVisual(name, jobNamePad);
175175
const safeName = Utils.safeDockerString(name);
176176
writeStreams.stdout(chalk`{black.bgRed FAIL }${renderDuration(prettyDuration)} {blueBright ${namePad}}\n`);
177177
const outputLog = await fs.readFile(`${cwd}/${stateDir}/output/${safeName}.log`, "utf8");
@@ -214,7 +214,7 @@ export class Commander {
214214

215215
let descriptionPadEnd = 11;
216216
if (hasDescriptions) {
217-
jobs.forEach(j => descriptionPadEnd = Math.max(j.description.length, descriptionPadEnd));
217+
jobs.forEach(j => descriptionPadEnd = Math.max(Utils.visualWidth(j.description), descriptionPadEnd));
218218
}
219219

220220
const jobNamePad = parser.jobNamePad;
@@ -224,19 +224,19 @@ export class Commander {
224224
}
225225

226226
if (hasDescriptions) {
227-
writeStreams.stdout(chalk`{grey ${"name".padEnd(jobNamePad)} ${"description".padEnd(descriptionPadEnd)}} `);
227+
writeStreams.stdout(chalk`{grey ${Utils.padEndVisual("name", jobNamePad)} ${Utils.padEndVisual("description", descriptionPadEnd)}} `);
228228
} else {
229-
writeStreams.stdout(chalk`{grey ${"name".padEnd(jobNamePad)}} `);
229+
writeStreams.stdout(chalk`{grey ${Utils.padEndVisual("name", jobNamePad)}} `);
230230
}
231231
writeStreams.stdout(chalk`{grey ${"stage".padEnd(stagePadEnd)} ${"when".padEnd(whenPadEnd)}} `);
232232
writeStreams.stdout(chalk`{grey allow_failure needs}\n`);
233233

234234
const renderLine = (job: Job) => {
235235
const needs = job.needs?.filter(n => !n.project && !n.pipeline).map(n => n.job);
236236
const allowFailure = job.allowFailure ? "true " : "false ";
237-
let jobLine = chalk`{blueBright ${job.name.padEnd(jobNamePad)}} `;
237+
let jobLine = chalk`{blueBright ${Utils.padEndVisual(job.name, jobNamePad)}} `;
238238
if (hasDescriptions) {
239-
jobLine += `${job.description.padEnd(descriptionPadEnd)} `;
239+
jobLine += `${Utils.padEndVisual(job.description, descriptionPadEnd)} `;
240240
}
241241
jobLine += chalk`{yellow ${job.stage.padEnd(stagePadEnd)}} ${job.when.padEnd(whenPadEnd)} ${allowFailure.padEnd(11)}`;
242242
if (needs) {

src/job.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ If you know what you're doing and would like to suppress this warning, use one o
448448
"";
449449

450450
// [16:33:19 1.37 min] my-job > hello world
451-
return chalk`${timestampPrefix}{blueBright ${prefix}${this.name.padEnd(this.jobNamePad)}}`;
451+
return chalk`${timestampPrefix}{blueBright ${prefix}${Utils.padEndVisual(this.name, this.jobNamePad)}}`;
452452
}
453453

454454
get safeJobName () {

src/parser.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import * as parallel from "./parallel.js";
1313
import {GitData} from "./git-data.js";
1414
import {ParserIncludes} from "./parser-includes.js";
1515
import {Producers} from "./producers.js";
16+
import stringWidth from "string-width";
1617
import {VariablesFromFiles} from "./variables-from-files.js";
1718
import {Argv} from "./argv.js";
1819
import {WriteStreams} from "./write-streams.js";
@@ -206,10 +207,10 @@ export class Parser {
206207
if (this.argv.needs && this.argv.job.length > 0) {
207208
const found = this.jobs.find(j => j.baseName === job);
208209
if (found?.needs) {
209-
jobNeedsLength = found.needs.map(f => f.job.length);
210+
jobNeedsLength = found.needs.map(f => stringWidth(f.job));
210211
}
211212
}
212-
const jobLength = typeof job == "string" ? job.length : job.name.length;
213+
const jobLength = typeof job == "string" ? stringWidth(job) : stringWidth(job.name);
213214
this._jobNamePad = Math.max(jobLength, this._jobNamePad ?? 0, ...jobNeedsLength);
214215
});
215216
if (this.argv.maxJobNamePadding !== null) {

src/utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import micromatch from "micromatch";
1515
import {AxiosRequestConfig} from "axios";
1616
import path from "path";
1717
import {Argv} from "./argv.js";
18+
import stringWidth from "string-width";
1819

1920
type RuleResultOpt = {
2021
argv: Argv;
@@ -29,6 +30,16 @@ type ExpandWith = {
2930
};
3031

3132
export class Utils {
33+
static visualWidth (str: string): number {
34+
return stringWidth(str);
35+
}
36+
37+
static padEndVisual (str: string, targetWidth: number): string {
38+
const width = stringWidth(str);
39+
if (width >= targetWidth) return str;
40+
return str + " ".repeat(targetWidth - width);
41+
}
42+
3243
static bashMulti (scripts: string[], cwd = process.cwd()): Promise<{stdout: string; stderr: string; exitCode: number}> {
3344
return execa(scripts.join(" && \\"), {shell: "bash", cwd});
3445
}

0 commit comments

Comments
 (0)