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 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.10...HEAD
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argsbarg",
3
- "version": "7.0.10",
3
+ "version": "7.0.11",
4
4
  "main": "./src/index.ts",
5
5
  "module": "./src/index.ts",
6
6
  "dependencies": {
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(title: string, lines: string[], hw: number, color: boolean): string[] {
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(title: string, rows: HelpRow[], hw: number, color: boolean): string[] {
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
- const titleChunk = `${kBoxH} ${title} `;
277
- const minimumContentWidth = Math.max(visibleWidth(titleChunk) + 1, labelWidth + 2 + 18);
278
- let contentWidth = Math.max(hw - 2, minimumContentWidth);
279
- const descWidth = Math.max(1, contentWidth - labelWidth - 2);
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 titleLead: string;
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));