argsbarg 7.0.10 → 7.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -1
- package/package.json +1 -1
- package/src/help.test.ts +36 -0
- package/src/help.ts +32 -19
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [7.0.11] - 2026-09-16
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Help table box sizing and wrapping** — fixed an off-by-two sizing bug in TTY help rendering where table rows wrapped their description column based on `hw - 2` instead of the inner content width `hw - 4`. This caused long table lines to exceed the terminal width and the box borders by up to 2 columns, resulting in the trailing border character `│` wrapping onto a new line in terminals matching `stdout.columns`.
|
|
15
|
+
|
|
10
16
|
## [7.0.10] - 2026-09-16
|
|
11
17
|
|
|
12
18
|
### Added
|
|
@@ -1010,7 +1016,8 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
|
|
|
1010
1016
|
- Migrate schemas: rename every `children` property to **`commands`**; move positional definitions to **`CliPositional`** objects on `positionals` and strip `positional` / `argMin` / `argMax` from flag definitions under `options` (flags only carry `name`, `description`, `kind`, and optional `shortName`).
|
|
1011
1017
|
- Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
|
|
1012
1018
|
|
|
1013
|
-
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v7.0.
|
|
1019
|
+
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v7.0.11...HEAD
|
|
1020
|
+
[7.0.11]: https://github.com/bdombro/bun-argsbarg/releases/tag/v7.0.11
|
|
1014
1021
|
[7.0.10]: https://github.com/bdombro/bun-argsbarg/releases/tag/v7.0.10
|
|
1015
1022
|
[7.0.9]: https://github.com/bdombro/bun-argsbarg/releases/tag/v7.0.9
|
|
1016
1023
|
[7.0.7]: https://github.com/bdombro/bun-argsbarg/releases/tag/v7.0.7
|
package/package.json
CHANGED
package/src/help.test.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
cliPositionalLabel,
|
|
13
13
|
cliResolveNotes,
|
|
14
14
|
schemaToYamlLines,
|
|
15
|
+
visibleWidth,
|
|
15
16
|
} from "./help.ts";
|
|
16
17
|
import { testProgram } from "./test/fixtures.ts";
|
|
17
18
|
|
|
@@ -214,6 +215,41 @@ describe("cliHelpRender", () => {
|
|
|
214
215
|
expect(help).not.toContain("Output Schema");
|
|
215
216
|
});
|
|
216
217
|
|
|
218
|
+
/** Tests that TTY help table boxes constrain line lengths to terminal width without wrapping border characters. */
|
|
219
|
+
test("TTY help table boxes fit terminal width without overflow", () => {
|
|
220
|
+
const origColumns = process.stdout.columns;
|
|
221
|
+
try {
|
|
222
|
+
process.stdout.columns = 120;
|
|
223
|
+
const root = testProgram({
|
|
224
|
+
key: "doc",
|
|
225
|
+
version: "1.0.0",
|
|
226
|
+
description: "Test application.",
|
|
227
|
+
commands: [
|
|
228
|
+
{
|
|
229
|
+
key: "query",
|
|
230
|
+
description:
|
|
231
|
+
"Query the body tape. Prints apply-shaped YAML `{ documentId, tabs: [{ tabId, ops: [], nodes }] }` (or JSON with --json). Fill ops and pipe to apply.",
|
|
232
|
+
handler: () => {},
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
key: "markdown-insert",
|
|
236
|
+
description:
|
|
237
|
+
"Insert markdown into a Google Doc tab as native DOM elements (headings, lists, code, tables).",
|
|
238
|
+
handler: () => {},
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
});
|
|
242
|
+
const help = cliHelpRender(cliPresentationRoot(root), [], false, { isTTY: true });
|
|
243
|
+
const boxLines = help.split("\n").filter((l) => l.includes("╭") || l.includes("│") || l.includes("╰"));
|
|
244
|
+
expect(boxLines.length).toBeGreaterThan(0);
|
|
245
|
+
for (const line of boxLines) {
|
|
246
|
+
expect(visibleWidth(line)).toBe(120);
|
|
247
|
+
}
|
|
248
|
+
} finally {
|
|
249
|
+
process.stdout.columns = origColumns;
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
217
253
|
/** Tests that non-TTY help automatically includes output schema in YAML by default. */
|
|
218
254
|
test("non-TTY help automatically includes output schema in YAML by default", () => {
|
|
219
255
|
const root = testProgram({
|
package/src/help.ts
CHANGED
|
@@ -82,7 +82,7 @@ function isOutputTTY(useStderr: boolean): boolean {
|
|
|
82
82
|
// ── Width Helpers ─────────────────────────────────────────────────────────────
|
|
83
83
|
|
|
84
84
|
/** Counts display columns, skipping ANSI SGR sequences. */
|
|
85
|
-
function visibleWidth(s: string): number {
|
|
85
|
+
export function visibleWidth(s: string): number {
|
|
86
86
|
let w = 0;
|
|
87
87
|
let i = 0;
|
|
88
88
|
while (i < s.length) {
|
|
@@ -230,19 +230,26 @@ interface HelpRow {
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
/** Renders a free-text or notes box with a Unicode border and `title` header. */
|
|
233
|
-
function renderTextBox(
|
|
233
|
+
function renderTextBox(
|
|
234
|
+
/** Section title to render in the top border. */
|
|
235
|
+
title: string,
|
|
236
|
+
/** Lines of text to display inside the box. */
|
|
237
|
+
lines: string[],
|
|
238
|
+
/** Available terminal column width. */
|
|
239
|
+
hw: number,
|
|
240
|
+
/** Whether ANSI color styling is enabled. */
|
|
241
|
+
color: boolean,
|
|
242
|
+
): string[] {
|
|
234
243
|
if (lines.length === 0) return [];
|
|
235
244
|
|
|
236
245
|
const titleLead = color
|
|
237
246
|
? style.gray(`${kBoxH} `) + style.grayBoldTitle(title) + style.gray(" ")
|
|
238
247
|
: `${kBoxH} ${title} `;
|
|
239
248
|
|
|
240
|
-
let contentWidth = visibleWidth(titleLead) + 1;
|
|
249
|
+
let contentWidth = Math.max(visibleWidth(titleLead) + 1, hw - 4);
|
|
241
250
|
for (const line of lines) {
|
|
242
251
|
contentWidth = Math.max(contentWidth, visibleWidth(line));
|
|
243
252
|
}
|
|
244
|
-
contentWidth = Math.max(hw - 2, contentWidth);
|
|
245
|
-
contentWidth = Math.min(contentWidth, hw - 4);
|
|
246
253
|
|
|
247
254
|
const borderWidth = contentWidth + 2;
|
|
248
255
|
const headerFill = Math.max(1, borderWidth - visibleWidth(titleLead));
|
|
@@ -265,7 +272,16 @@ function renderTextBox(title: string, lines: string[], hw: number, color: boolea
|
|
|
265
272
|
}
|
|
266
273
|
|
|
267
274
|
/** Renders a two-column label/description table in a box (options, subcommands, positionals). */
|
|
268
|
-
function renderTableBox(
|
|
275
|
+
function renderTableBox(
|
|
276
|
+
/** Section title to render in the top border. */
|
|
277
|
+
title: string,
|
|
278
|
+
/** Table rows containing labels and descriptions to format. */
|
|
279
|
+
rows: HelpRow[],
|
|
280
|
+
/** Available terminal column width. */
|
|
281
|
+
hw: number,
|
|
282
|
+
/** Whether ANSI color styling is enabled. */
|
|
283
|
+
color: boolean,
|
|
284
|
+
): string[] {
|
|
269
285
|
if (rows.length === 0) return [];
|
|
270
286
|
|
|
271
287
|
let labelWidth = 0;
|
|
@@ -273,10 +289,15 @@ function renderTableBox(title: string, rows: HelpRow[], hw: number, color: boole
|
|
|
273
289
|
labelWidth = Math.max(labelWidth, visibleWidth(row.label));
|
|
274
290
|
}
|
|
275
291
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
292
|
+
let titleLead: string;
|
|
293
|
+
if (color) {
|
|
294
|
+
titleLead = style.gray(`${kBoxH} `) + style.grayBoldTitle(title) + style.gray(" ");
|
|
295
|
+
} else {
|
|
296
|
+
titleLead = `${kBoxH} ${title} `;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const targetContentWidth = Math.max(visibleWidth(titleLead) + 1, hw - 4);
|
|
300
|
+
const descWidth = Math.max(1, targetContentWidth - labelWidth - 2);
|
|
280
301
|
|
|
281
302
|
const bodyLines: string[] = [];
|
|
282
303
|
for (const row of rows) {
|
|
@@ -289,18 +310,10 @@ function renderTableBox(title: string, rows: HelpRow[], hw: number, color: boole
|
|
|
289
310
|
}
|
|
290
311
|
}
|
|
291
312
|
|
|
292
|
-
let
|
|
293
|
-
if (color) {
|
|
294
|
-
titleLead = style.gray(`${kBoxH} `) + style.grayBoldTitle(title) + style.gray(" ");
|
|
295
|
-
} else {
|
|
296
|
-
titleLead = `${kBoxH} ${title} `;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
contentWidth = Math.max(contentWidth, visibleWidth(titleLead) + 1);
|
|
313
|
+
let contentWidth = targetContentWidth;
|
|
300
314
|
for (const line of bodyLines) {
|
|
301
315
|
contentWidth = Math.max(contentWidth, visibleWidth(line));
|
|
302
316
|
}
|
|
303
|
-
contentWidth = Math.min(contentWidth, hw - 4);
|
|
304
317
|
|
|
305
318
|
const borderWidth = contentWidth + 2;
|
|
306
319
|
const headerFill = Math.max(1, borderWidth - visibleWidth(titleLead));
|