[READ-ONLY] Mirror of https://github.com/bombshell-dev/clack. Effortlessly build beautiful command-line apps bomb.sh/docs/clack/basics/getting-started/
cli command-line command-line-app node prompt prompts
5

Configure Feed

Select the types of activity you want to include in your feed.

chore: fix or suppress linter warnings

Roman (May 6, 2026, 7:23 PM +0100) b85ed4ae 127185c9

+192 -166
+4
.prettierignore
··· 1 + *.json 2 + *.md 3 + *.yml 4 + *.jsonc
+2 -2
examples/basic/autocomplete-multiselect.ts
··· 21 21 - Press ${color.yellow('Enter')} when done selecting all items 22 22 - Press ${color.yellow('Ctrl+C')} to cancel 23 23 `, 24 - 'Instructions' 24 + 'Instructions', 25 25 ); 26 26 27 27 // Frameworks in alphabetical order ··· 80 80 // Display selected frameworks with detailed information 81 81 p.note( 82 82 `You selected ${color.green(selectedFrameworks.length)} frameworks:`, 83 - 'Selection Complete' 83 + 'Selection Complete', 84 84 ); 85 85 86 86 // Show each selected framework with its details
+1 -1
examples/basic/autocomplete.ts
··· 14 14 - Press ${color.yellow('Enter')} to select the highlighted option 15 15 - Press ${color.yellow('Ctrl+C')} to cancel 16 16 `, 17 - 'Instructions' 17 + 'Instructions', 18 18 ); 19 19 20 20 const countries = [
+1 -1
examples/basic/index.ts
··· 73 73 p.cancel('Operation cancelled.'); 74 74 process.exit(0); 75 75 }, 76 - } 76 + }, 77 77 ); 78 78 79 79 if (project.install) {
+2 -2
examples/basic/spinner-cancel-advanced.ts
··· 56 56 onCancel: () => { 57 57 p.note( 58 58 'You cancelled during processing. Any completed work will be saved.', 59 - 'Processing Cancelled' 59 + 'Processing Cancelled', 60 60 ); 61 61 }, 62 62 }); ··· 114 114 onCancel: () => { 115 115 p.note( 116 116 'Final operation was cancelled, but processing results are still valid.', 117 - 'Final Stage Cancelled' 117 + 'Final Stage Cancelled', 118 118 ); 119 119 }, 120 120 });
+1 -1
examples/basic/stream.ts
··· 22 22 await setTimeout(1000); 23 23 } 24 24 } 25 - })() 25 + })(), 26 26 ); 27 27 28 28 p.outro(`Problems? ${color.underline(color.cyan('https://example.com/issues'))}`);
+2 -2
examples/changesets/index.ts
··· 56 56 const major = Array.isArray(results.major) ? results.major : []; 57 57 const minor = Array.isArray(results.minor) ? results.minor : []; 58 58 const possiblePackages = packages.filter( 59 - (pkg) => !major.includes(pkg) && !minor.includes(pkg) 59 + (pkg) => !major.includes(pkg) && !minor.includes(pkg), 60 60 ); 61 61 if (possiblePackages.length === 0) return; 62 62 const note = possiblePackages.join(color.dim(', ')); ··· 67 67 }, 68 68 { 69 69 onCancel, 70 - } 70 + }, 71 71 ); 72 72 73 73 const message = await p.multiline({
+1 -1
package.json
··· 12 12 "pretest": "pnpm run build" 13 13 }, 14 14 "devDependencies": { 15 - "@bomb.sh/tools": "^0.4.2", 15 + "@bomb.sh/tools": "https://pkg.pr.new/@bomb.sh/tools@175116f", 16 16 "@changesets/cli": "^2.29.5", 17 17 "@types/node": "^20.19.39", 18 18 "jsr": "^0.13.4",
+7 -7
packages/core/src/prompts/autocomplete.ts
··· 13 13 14 14 function getCursorForValue<T extends OptionLike>( 15 15 selected: T['value'] | undefined, 16 - items: T[] 16 + items: T[], 17 17 ): number { 18 18 if (selected === undefined) { 19 19 return 0; ··· 46 46 return values[0]; 47 47 } 48 48 49 - export interface AutocompleteOptions<T extends OptionLike> 50 - extends PromptOptions<T['value'] | T['value'][], AutocompletePrompt<T>> { 49 + export interface AutocompleteOptions<T extends OptionLike> extends PromptOptions< 50 + T['value'] | T['value'][], 51 + AutocompletePrompt<T> 52 + > { 51 53 options: T[] | ((this: AutocompletePrompt<T>) => T[]); 52 54 filter?: FilterFunction<T>; 53 55 multiple?: boolean; ··· 60 62 placeholder?: string; 61 63 } 62 64 63 - export class AutocompletePrompt<T extends OptionLike> extends Prompt< 64 - T['value'] | T['value'][] 65 - > { 65 + export class AutocompletePrompt<T extends OptionLike> extends Prompt<T['value'] | T['value'][]> { 66 66 filteredOptions: T[]; 67 67 multiple: boolean; 68 68 isNavigating = false; ··· 162 162 placeholder !== undefined && 163 163 placeholder !== '' && 164 164 options.some( 165 - (opt) => !opt.disabled && (this.#filterFn ? this.#filterFn(placeholder, opt) : true) 165 + (opt) => !opt.disabled && (this.#filterFn ? this.#filterFn(placeholder, opt) : true), 166 166 ); 167 167 if (key.name === 'tab' && isEmptyOrOnlyTab && placeholderMatchesOption) { 168 168 if (this.userInput === '\t') {
+16 -24
packages/core/src/prompts/date.ts
··· 78 78 return p ? new Date(Date.UTC(p.year, p.month - 1, p.day)) : undefined; 79 79 } 80 80 81 + function toParts(date: Date | undefined): { year: number; month: number; day: number } | null { 82 + return date 83 + ? { year: date.getUTCFullYear(), month: date.getUTCMonth() + 1, day: date.getUTCDate() } 84 + : null; 85 + } 86 + 87 + // oxlint-disable-next-line max-params 81 88 function segmentBounds( 82 89 type: 'year' | 'month' | 'day', 83 90 ctx: { year: number; month: number }, 84 - minDate: Date | undefined, 85 - maxDate: Date | undefined 91 + dateConstraints: { minDate: Date | undefined; maxDate: Date | undefined }, 86 92 ): { min: number; max: number } { 87 - const minP = minDate 88 - ? { 89 - year: minDate.getUTCFullYear(), 90 - month: minDate.getUTCMonth() + 1, 91 - day: minDate.getUTCDate(), 92 - } 93 - : null; 94 - const maxP = maxDate 95 - ? { 96 - year: maxDate.getUTCFullYear(), 97 - month: maxDate.getUTCMonth() + 1, 98 - day: maxDate.getUTCDate(), 99 - } 100 - : null; 93 + const minP = toParts(dateConstraints.minDate); 94 + const maxP = toParts(dateConstraints.maxDate); 101 95 102 96 if (type === 'year') { 103 97 return { min: minP?.year ?? 1, max: maxP?.year ?? 9999 }; ··· 205 199 if (!segment) return undefined; 206 200 this.#cursor.positionInSegment = Math.max( 207 201 0, 208 - Math.min(this.#cursor.positionInSegment, segment.len - 1) 202 + Math.min(this.#cursor.positionInSegment, segment.len - 1), 209 203 ); 210 204 return { segment, index }; 211 205 } ··· 217 211 if (!ctx) return; 218 212 this.#cursor.segmentIndex = Math.max( 219 213 0, 220 - Math.min(this.#segments.length - 1, ctx.index + direction) 214 + Math.min(this.#segments.length - 1, ctx.index + direction), 221 215 ); 222 216 this.#cursor.positionInSegment = 0; 223 217 this.#segmentSelected = true; ··· 230 224 const raw = this.#segmentValues[segment.type]; 231 225 const isBlank = !raw || raw.replace(/_/g, '') === ''; 232 226 const num = Number.parseInt((raw || '0').replace(/_/g, '0'), 10) || 0; 233 - const bounds = segmentBounds( 234 - segment.type, 235 - parse(this.#segmentValues), 236 - this.#minDate, 237 - this.#maxDate 238 - ); 227 + const bounds = segmentBounds(segment.type, parse(this.#segmentValues), { 228 + minDate: this.#minDate, 229 + maxDate: this.#maxDate, 230 + }); 239 231 240 232 let next: number; 241 233 if (isBlank) {
+6 -4
packages/core/src/prompts/group-multiselect.ts
··· 1 1 import { Prompt, type PromptOptions } from './prompt.js'; 2 2 3 - export interface GroupMultiSelectOptions<T extends { value: any }> 4 - extends PromptOptions<T['value'][], GroupMultiSelectPrompt<T>> { 3 + export interface GroupMultiSelectOptions<T extends { value: any }> extends PromptOptions< 4 + T['value'][], 5 + GroupMultiSelectPrompt<T> 6 + > { 5 7 options: Record<string, T[]>; 6 8 initialValues?: T['value'][]; 7 9 required?: boolean; ··· 36 38 const groupedItems = this.getGroupItems(group); 37 39 if (this.isGroupSelected(group)) { 38 40 this.value = this.value.filter( 39 - (v: string) => groupedItems.findIndex((i) => i.value === v) === -1 41 + (v: string) => groupedItems.findIndex((i) => i.value === v) === -1, 40 42 ); 41 43 } else { 42 44 this.value = [...this.value, ...groupedItems.map((i) => i.value)]; ··· 61 63 this.value = [...(opts.initialValues ?? [])]; 62 64 this.cursor = Math.max( 63 65 this.options.findIndex(({ value }) => value === opts.cursorAt), 64 - this.#selectableGroups ? 0 : 1 66 + this.#selectableGroups ? 0 : 1, 65 67 ); 66 68 67 69 this.on('cursor', (key) => {
+4 -4
packages/core/src/prompts/multi-line.ts
··· 38 38 return; 39 39 } 40 40 this._setUserInput( 41 - this.userInput.slice(0, this.cursor) + char + this.userInput.slice(this.cursor) 41 + this.userInput.slice(0, this.cursor) + char + this.userInput.slice(this.cursor), 42 42 ); 43 43 } 44 44 #handleCursor(key?: Action) { ··· 73 73 if (wasReturn) { 74 74 if (this.userInput[this.cursor - 1] === '\n') { 75 75 this._setUserInput( 76 - this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor) 76 + this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor), 77 77 ); 78 78 this._cursor--; 79 79 } ··· 103 103 this.#lastKeyWasReturn = false; 104 104 if (key?.name === 'backspace' && this.cursor > 0) { 105 105 this._setUserInput( 106 - this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor) 106 + this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor), 107 107 ); 108 108 this._cursor--; 109 109 return; 110 110 } 111 111 if (key?.name === 'delete' && this.cursor < this.userInput.length) { 112 112 this._setUserInput( 113 - this.userInput.slice(0, this.cursor) + this.userInput.slice(this.cursor + 1) 113 + this.userInput.slice(0, this.cursor) + this.userInput.slice(this.cursor + 1), 114 114 ); 115 115 return; 116 116 }
+5 -3
packages/core/src/prompts/multi-select.ts
··· 6 6 disabled?: boolean; 7 7 } 8 8 9 - export interface MultiSelectOptions<T extends OptionLike> 10 - extends PromptOptions<T['value'][], MultiSelectPrompt<T>> { 9 + export interface MultiSelectOptions<T extends OptionLike> extends PromptOptions< 10 + T['value'][], 11 + MultiSelectPrompt<T> 12 + > { 11 13 options: T[]; 12 14 initialValues?: T['value'][]; 13 15 required?: boolean; ··· 57 59 this.value = [...(opts.initialValues ?? [])]; 58 60 const cursor = Math.max( 59 61 this.options.findIndex(({ value }) => value === opts.cursorAt), 60 - 0 62 + 0, 61 63 ); 62 64 this.cursor = this.options[cursor].disabled ? findCursor<T>(cursor, 1, this.options) : cursor; 63 65 this.on('key', (char) => {
+2 -2
packages/core/src/prompts/prompt.ts
··· 70 70 */ 71 71 private setSubscriber<T extends keyof ClackEvents<TValue>>( 72 72 event: T, 73 - opts: { cb: ClackEvents<TValue>[T]; once?: boolean } 73 + opts: { cb: ClackEvents<TValue>[T]; once?: boolean }, 74 74 ) { 75 75 const params = this._subscribers.get(event) ?? []; 76 76 params.push(opts); ··· 136 136 this.state = 'cancel'; 137 137 this.close(); 138 138 }, 139 - { once: true } 139 + { once: true }, 140 140 ); 141 141 } 142 142
+4 -2
packages/core/src/prompts/select-key.ts
··· 1 1 import { Prompt, type PromptOptions } from './prompt.js'; 2 2 3 - export interface SelectKeyOptions<T extends { value: string }> 4 - extends PromptOptions<T['value'], SelectKeyPrompt<T>> { 3 + export interface SelectKeyOptions<T extends { value: string }> extends PromptOptions< 4 + T['value'], 5 + SelectKeyPrompt<T> 6 + > { 5 7 options: T[]; 6 8 caseSensitive?: boolean; 7 9 }
+5 -5
packages/core/src/prompts/select.ts
··· 1 1 import { findCursor } from '../utils/cursor.js'; 2 2 import { Prompt, type PromptOptions } from './prompt.js'; 3 3 4 - export interface SelectOptions<T extends { value: any; disabled?: boolean }> 5 - extends PromptOptions<T['value'], SelectPrompt<T>> { 4 + export interface SelectOptions<T extends { value: any; disabled?: boolean }> extends PromptOptions< 5 + T['value'], 6 + SelectPrompt<T> 7 + > { 6 8 options: T[]; 7 9 initialValue?: T['value']; 8 10 } 9 - export class SelectPrompt<T extends { value: any; disabled?: boolean }> extends Prompt< 10 - T['value'] 11 - > { 11 + export class SelectPrompt<T extends { value: any; disabled?: boolean }> extends Prompt<T['value']> { 12 12 options: T[]; 13 13 cursor = 0; 14 14
+4 -2
packages/core/src/utils/cursor.ts
··· 1 + // oxlint-disable-next-line max-params 1 2 export function findCursor<T extends { disabled?: boolean }>( 2 3 cursor: number, 3 4 delta: number, 4 - options: T[] 5 + options: T[], 5 6 ) { 6 7 const hasEnabledOptions = options.some((opt) => !opt.disabled); 7 8 if (!hasEnabledOptions) { ··· 17 18 return clampedCursor; 18 19 } 19 20 21 + // oxlint-disable-next-line max-params 20 22 export function findTextCursor( 21 23 cursor: number, 22 24 deltaX: number, 23 25 deltaY: number, 24 - value: string 26 + value: string, 25 27 ): number { 26 28 const lines = value.split('\n'); 27 29 let cursorY = 0;
+2 -1
packages/core/src/utils/index.ts
··· 98 98 return 20; 99 99 }; 100 100 101 + // oxlint-disable-next-line max-params 101 102 export function wrapTextWithPrefix( 102 103 output: Writable | undefined, 103 104 text: string, 104 105 prefix: string, 105 106 startPrefix: string = prefix, 106 - lineFormatter?: (line: string, index: number) => string 107 + lineFormatter?: (line: string, index: number) => string, 107 108 ): string { 108 109 const columns = getColumns(output ?? stdout); 109 110 const wrapped = wrapAnsi(text, columns - prefix.length, {
+1 -1
packages/core/test/mock-writable.ts
··· 6 6 _write( 7 7 chunk: any, 8 8 _encoding: BufferEncoding, 9 - callback: (error?: Error | null | undefined) => void 9 + callback: (error?: Error | null | undefined) => void, 10 10 ): void { 11 11 this.buffer.push(chunk.toString()); 12 12 callback();
+1 -1
packages/core/test/prompts/autocomplete.test.ts
··· 1 1 import { cursor } from 'sisteransi'; 2 2 import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; 3 - import { default as AutocompletePrompt } from '../../src/prompts/autocomplete.js'; 3 + import { AutocompletePrompt } from '../../src/prompts/autocomplete.js'; 4 4 import { MockReadable } from '../mock-readable.js'; 5 5 import { MockWritable } from '../mock-writable.js'; 6 6
+1 -1
packages/core/test/prompts/confirm.test.ts
··· 1 1 import { cursor } from 'sisteransi'; 2 2 import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; 3 - import { default as ConfirmPrompt } from '../../src/prompts/confirm.js'; 3 + import { ConfirmPrompt } from '../../src/prompts/confirm.js'; 4 4 import { MockReadable } from '../mock-readable.js'; 5 5 import { MockWritable } from '../mock-writable.js'; 6 6
+1 -1
packages/core/test/prompts/date.test.ts
··· 1 1 import { cursor } from 'sisteransi'; 2 2 import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; 3 - import { default as DatePrompt } from '../../src/prompts/date.js'; 3 + import { DatePrompt } from '../../src/prompts/date.js'; 4 4 import { isCancel } from '../../src/utils/index.js'; 5 5 import { MockReadable } from '../mock-readable.js'; 6 6 import { MockWritable } from '../mock-writable.js';
+1 -1
packages/core/test/prompts/multi-line.test.ts
··· 1 1 import { styleText } from 'node:util'; 2 2 import { cursor } from 'sisteransi'; 3 3 import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; 4 - import { default as MultiLinePrompt } from '../../src/prompts/multi-line.js'; 4 + import { MultiLinePrompt } from '../../src/prompts/multi-line.js'; 5 5 import { MockReadable } from '../mock-readable.js'; 6 6 import { MockWritable } from '../mock-writable.js'; 7 7
+1 -1
packages/core/test/prompts/multi-select.test.ts
··· 1 1 import { cursor } from 'sisteransi'; 2 2 import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; 3 - import { default as MultiSelectPrompt } from '../../src/prompts/multi-select.js'; 3 + import { MultiSelectPrompt } from '../../src/prompts/multi-select.js'; 4 4 import { MockReadable } from '../mock-readable.js'; 5 5 import { MockWritable } from '../mock-writable.js'; 6 6
+1 -1
packages/core/test/prompts/password.test.ts
··· 1 1 import { styleText } from 'node:util'; 2 2 import { cursor } from 'sisteransi'; 3 3 import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; 4 - import { default as PasswordPrompt } from '../../src/prompts/password.js'; 4 + import { PasswordPrompt } from '../../src/prompts/password.js'; 5 5 import { MockReadable } from '../mock-readable.js'; 6 6 import { MockWritable } from '../mock-writable.js'; 7 7
+2 -2
packages/core/test/prompts/prompt.test.ts
··· 1 1 import { cursor } from 'sisteransi'; 2 2 import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; 3 - import { default as Prompt } from '../../src/prompts/prompt.js'; 3 + import { Prompt } from '../../src/prompts/prompt.js'; 4 4 import { isCancel } from '../../src/utils/index.js'; 5 5 import { MockReadable } from '../mock-readable.js'; 6 6 import { MockWritable } from '../mock-writable.js'; ··· 186 186 output, 187 187 render: () => 'foo', 188 188 }, 189 - false 189 + false, 190 190 ); 191 191 192 192 instance.on('cursor', eventSpy);
+1 -1
packages/core/test/prompts/select.test.ts
··· 1 1 import { cursor } from 'sisteransi'; 2 2 import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; 3 - import { default as SelectPrompt } from '../../src/prompts/select.js'; 3 + import { SelectPrompt } from '../../src/prompts/select.js'; 4 4 import { MockReadable } from '../mock-readable.js'; 5 5 import { MockWritable } from '../mock-writable.js'; 6 6
+1 -1
packages/core/test/prompts/text.test.ts
··· 1 1 import { styleText } from 'node:util'; 2 2 import { cursor } from 'sisteransi'; 3 3 import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; 4 - import { default as TextPrompt } from '../../src/prompts/text.js'; 4 + import { TextPrompt } from '../../src/prompts/text.js'; 5 5 import { MockReadable } from '../mock-readable.js'; 6 6 import { MockWritable } from '../mock-writable.js'; 7 7
+8 -7
packages/prompts/src/autocomplete.ts
··· 158 158 this.filteredOptions.length !== options.length 159 159 ? styleText( 160 160 'dim', 161 - ` (${this.filteredOptions.length} match${this.filteredOptions.length === 1 ? '' : 'es'})` 161 + ` (${this.filteredOptions.length} match${this.filteredOptions.length === 1 ? '' : 'es'})`, 162 162 ) 163 163 : ''; 164 164 ··· 177 177 headings.push( 178 178 `${guidePrefix}${styleText('dim', 'Search:')}${searchText}${matches}`, 179 179 ...noResults, 180 - ...validationError 180 + ...validationError, 181 181 ); 182 182 183 183 // Show instructions ··· 201 201 style: (option, active) => { 202 202 return opt( 203 203 option, 204 - option.disabled ? 'disabled' : active ? 'active' : 'inactive' 204 + option.disabled ? 'disabled' : active ? 'active' : 'inactive', 205 205 ); 206 206 }, 207 207 maxItems: opts.maxItems, ··· 239 239 * Integrated autocomplete multiselect - combines type-ahead filtering with multiselect in one UI 240 240 */ 241 241 export const autocompleteMultiselect = <Value>(opts: AutocompleteMultiSelectOptions<Value>) => { 242 + // oxlint-disable-next-line max-params 242 243 const formatOption = ( 243 244 option: Option<Value>, 244 245 active: boolean, 245 246 selectedValues: Value[], 246 - focusedValue: Value | undefined 247 + focusedValue: Value | undefined, 247 248 ) => { 248 249 const isSelected = selectedValues.includes(option.value); 249 250 const label = option.label ?? String(option.value ?? ''); ··· 308 309 this.filteredOptions.length !== options.length 309 310 ? styleText( 310 311 'dim', 311 - ` (${this.filteredOptions.length} match${this.filteredOptions.length === 1 ? '' : 'es'})` 312 + ` (${this.filteredOptions.length} match${this.filteredOptions.length === 1 ? '' : 'es'})`, 312 313 ) 313 314 : ''; 314 315 ··· 317 318 case 'submit': { 318 319 return `${title}${hasGuide ? `${styleText('gray', S_BAR)} ` : ''}${styleText( 319 320 'dim', 320 - `${this.selectedValues.length} items selected` 321 + `${this.selectedValues.length} items selected`, 321 322 )}`; 322 323 } 323 324 case 'cancel': { 324 325 return `${title}${hasGuide ? `${styleText('gray', S_BAR)} ` : ''}${styleText( 325 326 ['strikethrough', 'dim'], 326 - userInput 327 + userInput, 327 328 )}`; 328 329 } 329 330 default: {
+14 -19
packages/prompts/src/box.ts
··· 39 39 } 40 40 41 41 function getPaddingForLine( 42 - lineLength: number, 43 - innerWidth: number, 44 - padding: number, 45 - contentAlign: BoxAlignment | undefined 42 + sizes: { lineLength: number; innerWidth: number; padding: number }, 43 + contentAlign: BoxAlignment | undefined, 46 44 ): [number, number] { 47 - let leftPadding = padding; 48 - let rightPadding = padding; 45 + let leftPadding = sizes.padding; 46 + 49 47 if (contentAlign === 'center') { 50 - leftPadding = Math.floor((innerWidth - lineLength) / 2); 48 + leftPadding = Math.floor((sizes.innerWidth - sizes.lineLength) / 2); 51 49 } else if (contentAlign === 'right') { 52 - leftPadding = innerWidth - lineLength - padding; 50 + leftPadding = sizes.innerWidth - sizes.lineLength - sizes.padding; 53 51 } 54 52 55 - rightPadding = innerWidth - leftPadding - lineLength; 53 + const rightPadding = sizes.innerWidth - leftPadding - sizes.lineLength; 56 54 57 55 return [leftPadding, rightPadding]; 58 56 } 59 57 60 58 const defaultFormatBorder = (text: string) => text; 61 59 60 + // oxlint-disable-next-line max-params 62 61 export const box = (message = '', title = '', opts?: BoxOptions) => { 63 62 const output: Writable = opts?.output ?? process.stdout; 64 63 const columns = getColumns(output); ··· 103 102 const truncatedTitle = 104 103 titleWidth > maxTitleLength ? `${title.slice(0, maxTitleLength - 3)}...` : title; 105 104 const [titlePaddingLeft, titlePaddingRight] = getPaddingForLine( 106 - stringWidth(truncatedTitle), 107 - innerWidth, 108 - titlePadding, 109 - opts?.titleAlign 105 + { lineLength: stringWidth(truncatedTitle), innerWidth, padding: titlePadding }, 106 + opts?.titleAlign, 110 107 ); 111 108 const wrappedMessage = wrapAnsi(message, innerWidth - contentPadding * 2, { 112 109 hard: true, 113 110 trim: false, 114 111 }); 115 112 output.write( 116 - `${linePrefix}${symbols[0]}${hSymbol.repeat(titlePaddingLeft)}${truncatedTitle}${hSymbol.repeat(titlePaddingRight)}${symbols[1]}\n` 113 + `${linePrefix}${symbols[0]}${hSymbol.repeat(titlePaddingLeft)}${truncatedTitle}${hSymbol.repeat(titlePaddingRight)}${symbols[1]}\n`, 117 114 ); 118 115 const wrappedLines = wrappedMessage.split('\n'); 119 116 for (const line of wrappedLines) { 120 117 const [leftLinePadding, rightLinePadding] = getPaddingForLine( 121 - stringWidth(line), 122 - innerWidth, 123 - contentPadding, 124 - opts?.contentAlign 118 + { lineLength: stringWidth(line), innerWidth, padding: contentPadding }, 119 + opts?.contentAlign, 125 120 ); 126 121 output.write( 127 - `${linePrefix}${vSymbol}${' '.repeat(leftLinePadding)}${line}${' '.repeat(rightLinePadding)}${vSymbol}\n` 122 + `${linePrefix}${vSymbol}${' '.repeat(leftLinePadding)}${line}${' '.repeat(rightLinePadding)}${vSymbol}\n`, 128 123 ); 129 124 } 130 125 output.write(`${linePrefix}${symbols[2]}${hSymbol.repeat(innerWidth)}${symbols[3]}\n`);
+1 -1
packages/prompts/src/confirm.ts
··· 34 34 opts.output, 35 35 opts.message, 36 36 titlePrefixBar, 37 - titlePrefix 37 + titlePrefix, 38 38 ); 39 39 const title = `${hasGuide ? `${styleText('gray', S_BAR)}\n` : ''}${messageLines}\n`; 40 40 const value = this.value ? active : inactive;
+9 -6
packages/prompts/src/group-multi-select.ts
··· 22 22 } 23 23 export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) => { 24 24 const { selectableGroups = true, groupSpacing = 0 } = opts; 25 + // oxlint-disable-next-line max-params 25 26 const opt = ( 26 27 option: Option<Value> & { group: string | boolean }, 27 28 state: ··· 33 34 | 'group-active-selected' 34 35 | 'submitted' 35 36 | 'cancelled', 36 - options: (Option<Value> & { group: string | boolean })[] = [] 37 + options: (Option<Value> & { group: string | boolean })[] = [], 37 38 ) => { 38 39 const label = option.label ?? String(option.value); 39 40 const isItem = typeof option.group === 'string'; ··· 98 99 'dim', 99 100 `Press ${styleText(['gray', 'bgWhite', 'inverse'], ' space ')} to select, ${styleText( 100 101 'gray', 101 - styleText(['bgWhite', 'inverse'], ' enter ') 102 - )} to submit` 103 - ) 102 + styleText(['bgWhite', 'inverse'], ' enter '), 103 + )} to submit`, 104 + ), 104 105 )}`; 105 106 }, 106 107 render() { ··· 132 133 .map((ln, i) => 133 134 i === 0 134 135 ? `${hasGuide ? `${styleText('yellow', S_BAR_END)} ` : ''}${styleText('yellow', ln)}` 135 - : ` ${ln}` 136 + : ` ${ln}`, 136 137 ) 137 138 .join('\n'); 138 139 return `${title}${hasGuide ? `${styleText('yellow', S_BAR)} ` : ''}${this.options 140 + // oxlint-disable-next-line max-params 139 141 .map((option, i, options) => { 140 142 const selected = 141 143 value.includes(option.value) || ··· 160 162 } 161 163 default: { 162 164 const optionsText = this.options 165 + // oxlint-disable-next-line max-params 163 166 .map((option, i, options) => { 164 167 const selected = 165 168 value.includes(option.value) || ··· 174 177 optionText = opt( 175 178 option, 176 179 selected ? 'group-active-selected' : 'group-active', 177 - options 180 + options, 178 181 ); 179 182 } else if (active && selected) { 180 183 optionText = opt(option, 'active-selected', options);
+1 -1
packages/prompts/src/group.ts
··· 28 28 */ 29 29 export const group = async <T>( 30 30 prompts: PromptGroup<T>, 31 - opts?: PromptGroupOptions<T> 31 + opts?: PromptGroupOptions<T>, 32 32 ): Promise<Prettify<PromptGroupAwaitedReturn<T>>> => { 33 33 const results = {} as any; 34 34 const promptNames = Object.keys(prompts);
+21 -14
packages/prompts/src/limit-options.ts
··· 14 14 15 15 const trimLines = ( 16 16 groups: Array<string[]>, 17 - initialLineCount: number, 18 - startIndex: number, 19 - endIndex: number, 20 - maxLines: number 17 + options: { 18 + initialLineCount: number; 19 + startIndex: number; 20 + endIndex: number; 21 + outputMaxItems: number; 22 + }, 21 23 ) => { 22 - let lineCount = initialLineCount; 24 + let lineCount = options.initialLineCount; 23 25 let removals = 0; 24 - for (let i = startIndex; i < endIndex; i++) { 26 + for (let i = options.startIndex; i < options.endIndex; i++) { 25 27 const group = groups[i]; 26 28 lineCount = lineCount - group.length; 27 29 removals++; 28 - if (lineCount <= maxLines) { 30 + if (lineCount <= options.outputMaxItems) { 29 31 break; 30 32 } 31 33 } ··· 54 56 if (cursor >= computedMaxItems - 3) { 55 57 slidingWindowLocation = Math.max( 56 58 Math.min(cursor - computedMaxItems + 3, options.length - computedMaxItems), 57 - 0 59 + 0, 58 60 ); 59 61 } 60 62 ··· 64 66 65 67 const slidingWindowLocationEnd = Math.min( 66 68 slidingWindowLocation + computedMaxItems, 67 - options.length 69 + options.length, 68 70 ); 69 71 const lineGroups: Array<string[]> = []; 70 72 let lineCount = 0; ··· 95 97 let newLineCount = lineCount; 96 98 const cursorGroupIndex = cursor - slidingWindowLocationWithEllipsis; 97 99 const trimLinesLocal = (startIndex: number, endIndex: number) => 98 - trimLines(lineGroups, newLineCount, startIndex, endIndex, outputMaxItems); 100 + trimLines(lineGroups, { 101 + initialLineCount: newLineCount, 102 + startIndex, 103 + endIndex, 104 + outputMaxItems, 105 + }); 99 106 100 107 if (shouldRenderTopEllipsis) { 101 108 ({ lineCount: newLineCount, removals: precedingRemovals } = trimLinesLocal( 102 109 0, 103 - cursorGroupIndex 110 + cursorGroupIndex, 104 111 )); 105 112 if (newLineCount > outputMaxItems) { 106 113 ({ lineCount: newLineCount, removals: followingRemovals } = trimLinesLocal( 107 114 cursorGroupIndex + 1, 108 - lineGroups.length 115 + lineGroups.length, 109 116 )); 110 117 } 111 118 } else { 112 119 ({ lineCount: newLineCount, removals: followingRemovals } = trimLinesLocal( 113 120 cursorGroupIndex + 1, 114 - lineGroups.length 121 + lineGroups.length, 115 122 )); 116 123 if (newLineCount > outputMaxItems) { 117 124 ({ lineCount: newLineCount, removals: precedingRemovals } = trimLinesLocal( 118 125 0, 119 - cursorGroupIndex 126 + cursorGroupIndex, 120 127 )); 121 128 } 122 129 }
+1 -1
packages/prompts/src/log.ts
··· 25 25 output = process.stdout, 26 26 spacing = 1, 27 27 withGuide, 28 - }: LogMessageOptions = {} 28 + }: LogMessageOptions = {}, 29 29 ) => { 30 30 const parts: string[] = []; 31 31 const hasGuide = withGuide ?? settings.withGuide;
+2 -2
packages/prompts/src/multi-line.ts
··· 42 42 const submitPrefix = `${styleText('gray', S_BAR)} `; 43 43 const lines = hasGuide 44 44 ? wrapTextWithPrefix(opts.output, value, submitPrefix, undefined, (str) => 45 - styleText('dim', str) 45 + styleText('dim', str), 46 46 ) 47 47 : value 48 48 ? styleText('dim', value) ··· 53 53 const cancelPrefix = `${styleText('gray', S_BAR)} `; 54 54 const lines = hasGuide 55 55 ? wrapTextWithPrefix(opts.output, value, cancelPrefix, undefined, (str) => 56 - styleText(['strikethrough', 'dim'], str) 56 + styleText(['strikethrough', 'dim'], str), 57 57 ) 58 58 : value 59 59 ? styleText(['strikethrough', 'dim'], value)
+8 -8
packages/prompts/src/multi-select.ts
··· 38 38 | 'active-selected' 39 39 | 'submitted' 40 40 | 'cancelled' 41 - | 'disabled' 41 + | 'disabled', 42 42 ) => { 43 43 const label = option.label ?? String(option.value); 44 44 if (state === 'disabled') { ··· 87 87 'dim', 88 88 `Press ${styleText(['gray', 'bgWhite', 'inverse'], ' space ')} to select, ${styleText( 89 89 'gray', 90 - styleText('bgWhite', styleText('inverse', ' enter ')) 91 - )} to submit` 92 - ) 90 + styleText('bgWhite', styleText('inverse', ' enter ')), 91 + )} to submit`, 92 + ), 93 93 )}`; 94 94 }, 95 95 render() { ··· 98 98 opts.output, 99 99 opts.message, 100 100 hasGuide ? `${symbolBar(this.state)} ` : '', 101 - `${symbol(this.state)} ` 101 + `${symbol(this.state)} `, 102 102 ); 103 103 const title = `${hasGuide ? `${styleText('gray', S_BAR)}\n` : ''}${wrappedMessage}\n`; 104 104 const value = this.value ?? []; ··· 127 127 const wrappedSubmitText = wrapTextWithPrefix( 128 128 opts.output, 129 129 submitText, 130 - hasGuide ? `${styleText('gray', S_BAR)} ` : '' 130 + hasGuide ? `${styleText('gray', S_BAR)} ` : '', 131 131 ); 132 132 return `${title}${wrappedSubmitText}`; 133 133 } ··· 142 142 const wrappedLabel = wrapTextWithPrefix( 143 143 opts.output, 144 144 label, 145 - hasGuide ? `${styleText('gray', S_BAR)} ` : '' 145 + hasGuide ? `${styleText('gray', S_BAR)} ` : '', 146 146 ); 147 147 return `${title}${wrappedLabel}${hasGuide ? `\n${styleText('gray', S_BAR)}` : ''}`; 148 148 } ··· 153 153 .map((ln, i) => 154 154 i === 0 155 155 ? `${hasGuide ? `${styleText('yellow', S_BAR_END)} ` : ''}${styleText('yellow', ln)}` 156 - : ` ${ln}` 156 + : ` ${ln}`, 157 157 ) 158 158 .join('\n'); 159 159 // Calculate rowPadding: title lines + footer lines (error message + trailing newline)
+6 -4
packages/prompts/src/note.ts
··· 22 22 23 23 const defaultNoteFormatter = (line: string): string => styleText('dim', line); 24 24 25 + // oxlint-disable-next-line max-params 25 26 const wrapWithFormat = (message: string, width: number, format: FormatFn): string => { 26 27 const opts: WrapAnsiOptions = { 27 28 hard: true, ··· 34 35 return wrapAnsi(message, wrapWidth, opts); 35 36 }; 36 37 38 + // oxlint-disable-next-line max-params 37 39 export const note = (message = '', title = '', opts?: NoteOptions) => { 38 40 const output: Writable = opts?.output ?? process.stdout; 39 41 const hasGuide = opts?.withGuide ?? settings.withGuide; ··· 47 49 const width = stringWidth(ln); 48 50 return width > sum ? width : sum; 49 51 }, 0), 50 - titleLen 52 + titleLen, 51 53 ) + 2; 52 54 const msg = lines 53 55 .map( 54 56 (ln) => 55 - `${styleText('gray', S_BAR)} ${ln}${' '.repeat(len - stringWidth(ln))}${styleText('gray', S_BAR)}` 57 + `${styleText('gray', S_BAR)} ${ln}${' '.repeat(len - stringWidth(ln))}${styleText('gray', S_BAR)}`, 56 58 ) 57 59 .join('\n'); 58 60 const leadingBorder = hasGuide ? `${styleText('gray', S_BAR)}\n` : ''; ··· 60 62 output.write( 61 63 `${leadingBorder}${styleText('green', S_STEP_SUBMIT)} ${styleText('reset', title)} ${styleText( 62 64 'gray', 63 - S_BAR_H.repeat(Math.max(len - titleLen - 1, 1)) + S_CORNER_TOP_RIGHT 64 - )}\n${msg}\n${styleText('gray', bottomLeft + S_BAR_H.repeat(len + 2) + S_CORNER_BOTTOM_RIGHT)}\n` 65 + S_BAR_H.repeat(Math.max(len - titleLen - 1, 1)) + S_CORNER_TOP_RIGHT, 66 + )}\n${msg}\n${styleText('gray', bottomLeft + S_BAR_H.repeat(len + 2) + S_CORNER_BOTTOM_RIGHT)}\n`, 65 67 ); 66 68 };
+3 -2
packages/prompts/src/path.ts
··· 1 1 import { existsSync, lstatSync, readdirSync } from 'node:fs'; 2 + // oxlint-disable-next-line no-restricted-imports 2 3 import { dirname, join } from 'node:path'; 3 4 import { autocomplete } from './autocomplete.js'; 4 5 import type { CommonOptions } from './common.js'; ··· 66 67 }; 67 68 }) 68 69 .filter( 69 - ({ path, isDirectory }) => path.startsWith(prefix) && (isDirectory || !opts.directory) 70 + ({ path, isDirectory }) => path.startsWith(prefix) && (isDirectory || !opts.directory), 70 71 ); 71 72 72 73 return items.map((item) => ({ 73 74 value: item.path, 74 75 })); 75 - } catch (_e) { 76 + } catch { 76 77 return []; 77 78 } 78 79 },
+5 -5
packages/prompts/src/select-key.ts
··· 13 13 export const selectKey = <Value extends string>(opts: SelectKeyOptions<Value>) => { 14 14 const opt = ( 15 15 option: Option<Value>, 16 - state: 'inactive' | 'active' | 'selected' | 'cancelled' = 'inactive' 16 + state: 'inactive' | 'active' | 'selected' | 'cancelled' = 'inactive', 17 17 ) => { 18 18 const label = option.label ?? String(option.value); 19 19 if (state === 'selected') { ··· 51 51 const wrapped = wrapTextWithPrefix( 52 52 opts.output, 53 53 opt(selectedOption, 'selected'), 54 - submitPrefix 54 + submitPrefix, 55 55 ); 56 56 return `${title}${wrapped}`; 57 57 } ··· 60 60 const wrapped = wrapTextWithPrefix( 61 61 opts.output, 62 62 opt(this.options[0], 'cancelled'), 63 - cancelPrefix 63 + cancelPrefix, 64 64 ); 65 65 return `${title}${wrapped}${hasGuide ? `\n${styleText('gray', S_BAR)}` : ''}`; 66 66 } ··· 72 72 wrapTextWithPrefix( 73 73 opts.output, 74 74 opt(option, i === this.cursor ? 'active' : 'inactive'), 75 - defaultPrefix 76 - ) 75 + defaultPrefix, 76 + ), 77 77 ) 78 78 .join('\n'); 79 79 return `${title}${wrapped}\n${defaultPrefixEnd}\n`;
+4 -4
packages/prompts/src/select.ts
··· 85 85 export const select = <Value>(opts: SelectOptions<Value>) => { 86 86 const opt = ( 87 87 option: Option<Value>, 88 - state: 'inactive' | 'active' | 'selected' | 'cancelled' | 'disabled' 88 + state: 'inactive' | 'active' | 'selected' | 'cancelled' | 'disabled', 89 89 ) => { 90 90 const label = option.label ?? String(option.value); 91 91 switch (state) { ··· 120 120 opts.output, 121 121 opts.message, 122 122 titlePrefixBar, 123 - titlePrefix 123 + titlePrefix, 124 124 ); 125 125 const title = `${hasGuide ? `${styleText('gray', S_BAR)}\n` : ''}${messageLines}\n`; 126 126 ··· 130 130 const wrappedLines = wrapTextWithPrefix( 131 131 opts.output, 132 132 opt(this.options[this.cursor], 'selected'), 133 - submitPrefix 133 + submitPrefix, 134 134 ); 135 135 return `${title}${wrappedLines}`; 136 136 } ··· 139 139 const wrappedLines = wrapTextWithPrefix( 140 140 opts.output, 141 141 opt(this.options[this.cursor], 'cancelled'), 142 - cancelPrefix 142 + cancelPrefix, 143 143 ); 144 144 return `${title}${wrappedLines}${hasGuide ? `\n${styleText('gray', S_BAR)}` : ''}`; 145 145 }
+1
packages/prompts/src/spinner.ts
··· 170 170 }, delay); 171 171 }; 172 172 173 + // oxlint-disable-next-line max-params 173 174 const _stop = (msg = '', code = 0, silent: boolean = false): void => { 174 175 if (!isSpinnerActive) return; 175 176 isSpinnerActive = false;
+1 -1
packages/prompts/src/stream.ts
··· 12 12 export const stream = { 13 13 message: async ( 14 14 iterable: Iterable<string> | AsyncIterable<string>, 15 - { symbol = styleText('gray', S_BAR) }: LogMessageOptions = {} 15 + { symbol = styleText('gray', S_BAR) }: LogMessageOptions = {}, 16 16 ) => { 17 17 process.stdout.write(`${styleText('gray', S_BAR)}\n${symbol} `); 18 18 let lineWidth = 3;
+17 -7
packages/prompts/src/task-log.ts
··· 37 37 } 38 38 39 39 const stripDestructiveANSI = (input: string): string => { 40 - // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional 40 + // oxlint-disable-next-line no-control-regex 41 41 return input.replace(/\x1b\[(?:\d+;)*\d*[ABCDEFGHfJKSTsu]|\x1b\[(s|u)/g, ''); 42 42 }; 43 43 ··· 105 105 output.write(erase.lines(lines)); 106 106 } 107 107 }; 108 + // oxlint-disable-next-line max-params 108 109 const printBuffer = (buffer: BufferEntry, messageSpacing?: number, full?: boolean): void => { 109 110 const messages = full ? `${buffer.full}\n${buffer.value}` : buffer.value; 110 111 if (buffer.header !== undefined && buffer.header !== '') { ··· 115 116 secondarySymbol, 116 117 symbol: secondarySymbol, 117 118 spacing: 0, 118 - } 119 + }, 119 120 ); 120 121 } 121 122 log.message( ··· 125 126 secondarySymbol, 126 127 symbol: secondarySymbol, 127 128 spacing: messageSpacing ?? spacing, 128 - } 129 + }, 129 130 ); 130 131 }; 131 132 const renderBuffer = (): void => { ··· 134 135 if ((header === undefined || header.length === 0) && value.length === 0) { 135 136 continue; 136 137 } 137 - printBuffer(buffer, undefined, retainLog === true && full.length > 0); 138 + printBuffer(buffer, undefined, retainLog && full.length > 0); 138 139 } 139 140 }; 140 - const message = (buffer: BufferEntry, msg: string, mopts?: TaskLogMessageOptions) => { 141 + const message = ({ 142 + buffer, 143 + msg, 144 + mopts, 145 + }: { 146 + buffer: BufferEntry; 147 + msg: string; 148 + mopts?: TaskLogMessageOptions; 149 + }) => { 141 150 clear(false); 142 151 if ((mopts?.raw !== true || !lastMessageWasRaw) && buffer.value !== '') { 143 152 buffer.value += '\n'; ··· 184 193 185 194 return { 186 195 message(msg: string, mopts?: TaskLogMessageOptions) { 187 - message(buffers[0], msg, mopts); 196 + const buffer = buffers[0]; 197 + message({ buffer, msg, mopts }); 188 198 }, 189 199 group(name: string) { 190 200 const buffer: BufferEntry = { ··· 195 205 buffers.push(buffer); 196 206 return { 197 207 message(msg: string, mopts?: TaskLogMessageOptions) { 198 - message(buffer, msg, mopts); 208 + message({ buffer, msg, mopts }); 199 209 }, 200 210 error(message: string) { 201 211 completeBuffer(buffer, {
+1 -1
packages/prompts/test/date.test.ts
··· 167 167 await new Promise((r) => setImmediate(r)); 168 168 169 169 const hasError = output.buffer.some( 170 - (s) => typeof s === 'string' && s.includes('Date must be on or after') 170 + (s) => typeof s === 'string' && s.includes('Date must be on or after'), 171 171 ); 172 172 expect(hasError).toBe(true); 173 173
+1 -1
packages/prompts/test/path.test.ts
··· 33 33 './root.zip': '6', 34 34 './bar': '7', 35 35 }, 36 - '/tmp' 36 + '/tmp', 37 37 ); 38 38 }); 39 39
+1 -1
packages/prompts/test/progress-bar.test.ts
··· 325 325 result.stop(); 326 326 327 327 expect(output.buffer).toMatchSnapshot(); 328 - } 328 + }, 329 329 ); 330 330 }); 331 331 });
+1 -1
packages/prompts/test/test-utils.ts
··· 9 9 _write( 10 10 chunk: any, 11 11 _encoding: BufferEncoding, 12 - callback: (error?: Error | null | undefined) => void 12 + callback: (error?: Error | null | undefined) => void, 13 13 ): void { 14 14 this.buffer.push(chunk.toString()); 15 15 callback();
+6 -5
pnpm-lock.yaml
··· 9 9 .: 10 10 devDependencies: 11 11 '@bomb.sh/tools': 12 - specifier: ^0.4.2 13 - version: 0.4.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@20.19.39)(jiti@2.6.1)(oxc-resolver@11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(typescript@6.0.3)(yaml@2.8.3) 12 + specifier: https://pkg.pr.new/@bomb.sh/tools@175116f 13 + version: https://pkg.pr.new/@bomb.sh/tools@175116f(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@20.19.39)(jiti@2.6.1)(oxc-resolver@11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(typescript@6.0.3)(yaml@2.8.3) 14 14 '@changesets/cli': 15 15 specifier: ^2.29.5 16 16 version: 2.29.5 ··· 113 113 '@bomb.sh/args@0.3.1': 114 114 resolution: {integrity: sha512-CwxKrfgcorUPP6KfYD59aRdBYWBTsfsxT+GmoLVnKo5Tmyoqbpo0UNcjngRMyU+6tiPbd18RuIYxhgAn44wU/Q==} 115 115 116 - '@bomb.sh/tools@0.4.2': 117 - resolution: {integrity: sha512-BbjMPPUhUG9ZM2lVbXiCfy5u+IDEDW5q++F3q/RQDiENhyaGqXk3HZhVFK4XqSRQ+fd4VO8KLzbQGdbis4XU2Q==} 116 + '@bomb.sh/tools@https://pkg.pr.new/@bomb.sh/tools@175116f': 117 + resolution: {integrity: sha512-W8WgNHwOJ1tSEFyw2FrqkHFdvGphxo2TXFXYVPmwoztlGW+fZSKHy4Wk3DP1Q4gMk0pqYttcgjwauj5hlTbJrQ==, tarball: https://pkg.pr.new/@bomb.sh/tools@175116f} 118 + version: 0.4.2 118 119 hasBin: true 119 120 120 121 '@changesets/apply-release-plan@7.0.12': ··· 1983 1984 1984 1985 '@bomb.sh/args@0.3.1': {} 1985 1986 1986 - '@bomb.sh/tools@0.4.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@20.19.39)(jiti@2.6.1)(oxc-resolver@11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(typescript@6.0.3)(yaml@2.8.3)': 1987 + '@bomb.sh/tools@https://pkg.pr.new/@bomb.sh/tools@175116f(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@20.19.39)(jiti@2.6.1)(oxc-resolver@11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(typescript@6.0.3)(yaml@2.8.3)': 1987 1988 dependencies: 1988 1989 '@bomb.sh/args': 0.3.1 1989 1990 '@humanfs/node': 0.16.7