[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.

Merge branch 'main' into good-spinner

James Garbutt (May 31, 2026, 11:14 AM +0100) 6ff6b63b c8125115

+2044 -415
-5
.changeset/afraid-pets-notice.md
··· 1 - --- 2 - "@clack/core": minor 3 - --- 4 - 5 - Remove unused `debug` option key.
+2 -2
.changeset/config.json
··· 1 1 { 2 - "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 - "changelog": "@changesets/cli/changelog", 2 + "$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json", 3 + "changelog": ["@changesets/changelog-github", { "repo": "bombshell-dev/clack" }], 4 4 "commit": false, 5 5 "fixed": [], 6 6 "linked": [],
-6
.changeset/mean-squids-slide.md
··· 1 - --- 2 - "@clack/prompts": minor 3 - "@clack/core": minor 4 - --- 5 - 6 - fix: add engines field expressing node >=20.12 requirement
-6
.changeset/ninety-seals-teach.md
··· 1 - --- 2 - "@clack/prompts": minor 3 - "@clack/core": minor 4 - --- 5 - 6 - Add new multiline prompt for multi-line text input.
+6
.github/dependabot.yml
··· 1 + version: 2 2 + updates: 3 + - package-ecosystem: github-actions 4 + directory: / 5 + schedule: 6 + interval: weekly
+3 -2
.github/workflows/ci.yml
··· 6 6 - main 7 7 pull_request: 8 8 9 + permissions: {} 10 + 9 11 # Automatically cancel in-progress actions on the same branch 10 12 concurrency: 11 13 group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} ··· 14 16 jobs: 15 17 scripts: 16 18 if: github.repository_owner == 'bombshell-dev' 17 - uses: bombshell-dev/automation/.github/workflows/run.yml@main 18 - secrets: inherit 19 + uses: bombshell-dev/automation/.github/workflows/run.yml@3a8b4a38fe464b0b51d14962ae416a169517fba9 # main as of 2026-05-12 19 20 with: 20 21 commands: > 21 22 [
+11 -7
.github/workflows/detect-agent.yml
··· 5 5 types: [opened] 6 6 workflow_dispatch: {} 7 7 8 - permissions: 9 - issues: write 10 - pull-requests: write 8 + permissions: {} 11 9 12 10 jobs: 13 11 detect: 14 - if: github.event_name != 'workflow_dispatch' 15 - uses: bombshell-dev/automation/.github/workflows/detect-agent.yml@main 12 + if: github.repository_owner == 'bombshell-dev' && github.event_name != 'workflow_dispatch' 13 + uses: bombshell-dev/automation/.github/workflows/detect-agent.yml@3a8b4a38fe464b0b51d14962ae416a169517fba9 # main as of 2026-05-12 14 + permissions: 15 + issues: write 16 + pull-requests: write 16 17 17 18 backfill: 18 - if: github.event_name == 'workflow_dispatch' 19 - uses: bombshell-dev/automation/.github/workflows/detect-agent-backfill.yml@main 19 + if: github.repository_owner == 'bombshell-dev' && github.event_name == 'workflow_dispatch' 20 + uses: bombshell-dev/automation/.github/workflows/detect-agent-backfill.yml@3a8b4a38fe464b0b51d14962ae416a169517fba9 # main as of 2026-05-12 21 + permissions: 22 + issues: write 23 + pull-requests: write
+6 -2
.github/workflows/format.yml
··· 6 6 branches: 7 7 - main 8 8 9 + permissions: {} 10 + 9 11 jobs: 10 12 format: 11 13 if: github.repository_owner == 'bombshell-dev' 12 - uses: bombshell-dev/automation/.github/workflows/format.yml@main 14 + uses: bombshell-dev/automation/.github/workflows/format.yml@3a8b4a38fe464b0b51d14962ae416a169517fba9 # main as of 2026-05-12 13 15 permissions: 14 16 contents: write 15 17 pull-requests: write 16 - secrets: inherit 18 + secrets: 19 + BOT_APP_ID: ${{ secrets.BOT_APP_ID }} 20 + BOT_PRIVATE_KEY: ${{ secrets.BOT_PRIVATE_KEY }}
+10 -4
.github/workflows/issue.yml
··· 4 4 issues: 5 5 types: [opened, edited, labeled, reopened] 6 6 7 + permissions: {} 8 + 7 9 jobs: 8 10 backlog: 9 11 if: github.event.action == 'edited' || github.event.action == 'labeled' 10 - uses: bombshell-dev/automation/.github/workflows/move-issue-to-backlog.yml@main 11 - secrets: inherit 12 + uses: bombshell-dev/automation/.github/workflows/move-issue-to-backlog.yml@3a8b4a38fe464b0b51d14962ae416a169517fba9 # main as of 2026-05-12 13 + secrets: 14 + BOT_APP_ID: ${{ secrets.BOT_APP_ID }} 15 + BOT_PRIVATE_KEY: ${{ secrets.BOT_PRIVATE_KEY }} 12 16 13 17 project: 14 18 if: github.event.action == 'opened' || github.event.action == 'reopened' 15 - uses: bombshell-dev/automation/.github/workflows/add-issue-to-project.yml@main 16 - secrets: inherit 19 + uses: bombshell-dev/automation/.github/workflows/add-issue-to-project.yml@3a8b4a38fe464b0b51d14962ae416a169517fba9 # main as of 2026-05-12 20 + secrets: 21 + BOT_APP_ID: ${{ secrets.BOT_APP_ID }} 22 + BOT_PRIVATE_KEY: ${{ secrets.BOT_PRIVATE_KEY }}
+4 -2
.github/workflows/preview.yml
··· 5 5 branches: 6 6 - main 7 7 pull_request: 8 + types: [opened, synchronize, reopened] 8 9 workflow_dispatch: 9 10 11 + permissions: {} 10 12 11 13 jobs: 12 14 preview: 13 15 if: github.repository_owner == 'bombshell-dev' 14 - uses: bombshell-dev/automation/.github/workflows/preview.yml@main 15 - permissions: 16 + uses: bombshell-dev/automation/.github/workflows/preview.yml@3a8b4a38fe464b0b51d14962ae416a169517fba9 # main as of 2026-05-12 17 + permissions: 16 18 contents: write 17 19 pull-requests: write 18 20 id-token: write
+10 -7
.github/workflows/publish.yml
··· 5 5 branches: [main, v0] 6 6 workflow_dispatch: 7 7 8 - permissions: 9 - id-token: write 10 - contents: write 11 - pull-requests: write 12 - packages: write 8 + permissions: {} 13 9 14 10 jobs: 15 11 publish: 16 12 if: github.repository_owner == 'bombshell-dev' 17 - uses: bombshell-dev/automation/.github/workflows/publish.yml@main 18 - secrets: inherit 13 + uses: bombshell-dev/automation/.github/workflows/publish.yml@3a8b4a38fe464b0b51d14962ae416a169517fba9 # main as of 2026-05-12 14 + permissions: 15 + id-token: write 16 + contents: write 17 + pull-requests: write 18 + packages: write 19 + secrets: 20 + BOT_APP_ID: ${{ secrets.BOT_APP_ID }} 21 + BOT_PRIVATE_KEY: ${{ secrets.BOT_PRIVATE_KEY }}
+4 -5
.github/workflows/require-allow-edits.yml
··· 1 - name: Require “Allow Edits” 1 + name: Require "Allow Edits" 2 2 3 3 on: [pull_request_target] 4 4 5 - permissions: 6 - contents: read 5 + permissions: {} 7 6 8 7 jobs: 9 8 _: 10 9 permissions: 11 10 pull-requests: read 12 11 13 - name: "Require “Allow Edits”" 12 + name: "Require \"Allow Edits\"" 14 13 15 14 runs-on: ubuntu-latest 16 15 17 16 steps: 18 - - uses: ljharb/require-allow-edits@v2 17 + - uses: ljharb/require-allow-edits@be4a9d13001dfa5bfc10af68313bad753d7bdc6a # v2
-5
.npmrc
··· 1 - # Important! Never install from registry even when new version is available 2 - prefer-workspace-packages=true 3 - link-workspace-packages=true 4 - save-workspace-protocol=false 5 - auto-install-peers=false
+1 -1
.vscode/settings.json
··· 1 1 { 2 - "typescript.tsdk": "node_modules/typescript/lib" 2 + "js/ts.tsdk.path": "node_modules/typescript/lib" 3 3 }
+1
examples/basic/package.json
··· 5 5 "type": "module", 6 6 "dependencies": { 7 7 "@clack/prompts": "workspace:*", 8 + "arktype": "^2.2.0", 8 9 "picocolors": "^1.0.0", 9 10 "jiti": "^1.17.0" 10 11 },
+31
examples/basic/standard-schema-validation.ts
··· 1 + import { setTimeout } from 'node:timers/promises'; 2 + import { isCancel, note, text } from '@clack/prompts'; 3 + import { type } from 'arktype'; 4 + 5 + console.clear(); 6 + 7 + // Example demonstrating the issue with initial value validation 8 + const name = await text({ 9 + message: 'Enter your email', 10 + initialValue: 'aaa', // Invalid initial value without @ 11 + validate: type('string.email').describe('Invalid email'), 12 + }); 13 + 14 + if (!isCancel(name)) { 15 + note(`Valid name: ${name}`, 'Success'); 16 + } 17 + 18 + await setTimeout(1000); 19 + 20 + // Example with a valid initial value for comparison 21 + const validName = await text({ 22 + message: 'Enter another email', 23 + initialValue: 'john.doe@example.com', // Valid initial value 24 + validate: type('string.email').describe('Invalid email'), 25 + }); 26 + 27 + if (!isCancel(validName)) { 28 + note(`Valid name: ${validName}`, 'Success'); 29 + } 30 + 31 + await setTimeout(1000);
+3 -2
package.json
··· 16 16 }, 17 17 "devDependencies": { 18 18 "@biomejs/biome": "^2.1.2", 19 - "@changesets/cli": "^2.29.5", 19 + "@changesets/changelog-github": "^0.7.0", 20 + "@changesets/cli": "^2.31.0", 20 21 "@types/node": "^20.19.39", 21 22 "jsr": "^0.13.4", 22 23 "knip": "^5.62.0", 23 - "typescript": "^5.8.3", 24 + "typescript": "^6.0.3", 24 25 "unbuild": "^3.6.0" 25 26 }, 26 27 "packageManager": "pnpm@10.33.0",
+38
packages/core/CHANGELOG.md
··· 1 1 # @clack/core 2 2 3 + ## 1.4.0 4 + 5 + ### Minor Changes 6 + 7 + - [#543](https://github.com/bombshell-dev/clack/pull/543) [`83428ac`](https://github.com/bombshell-dev/clack/commit/83428ac6d8bc5eda87615cc7b1f14e0c8b16e1b6) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Adds support for Standard Schema validation 8 + 9 + Prompts accept an optional `validate()` function to validate user input. While a function provides more flexibility and customization over your validation, it can be a bit verbose. To help solve this, there are libraries that provide schema-based validation to make shorthand and type-strict validation substantially easier. 10 + 11 + Libraries following the [Standard Schema specification](https://github.com/standard-schema/standard-schema) are now natively supported. For example, using [Arktype](https://arktype.io/): 12 + 13 + ```diff 14 + import { text } from '@clack/prompts'; 15 + import { type } from 'arktype'; 16 + 17 + const name = await text({ 18 + message: 'Enter your email', 19 + + validate: type('string.email').describe('Invalid email'), 20 + }); 21 + ``` 22 + 23 + ### Patch Changes 24 + 25 + - [#534](https://github.com/bombshell-dev/clack/pull/534) [`3dcb31a`](https://github.com/bombshell-dev/clack/commit/3dcb31a7d63827d95a5a52ac630cbd48e3a68364) Thanks [@MattStypa](https://github.com/MattStypa)! - Fixed spaces and uppercase characters in multiline prompt 26 + 27 + ## 1.3.1 28 + 29 + ### Patch Changes 30 + 31 + - 54be8d7: Fix line wrapping and overflow computation in group multi-select and other list-like prompts. 32 + 33 + ## 1.3.0 34 + 35 + ### Minor Changes 36 + 37 + - 78fd3ae: Remove unused `debug` option key. 38 + - ea5702e: fix: add engines field expressing node >=20.12 requirement 39 + - 814ab9a: Add new multiline prompt for multi-line text input. 40 + 3 41 ## 1.2.0 4 42 5 43 ### Minor Changes
+2 -1
packages/core/package.json
··· 1 1 { 2 2 "name": "@clack/core", 3 - "version": "1.2.0", 3 + "version": "1.4.0", 4 4 "type": "module", 5 5 "main": "./dist/index.mjs", 6 6 "module": "./dist/index.mjs", ··· 60 60 "sisteransi": "^1.0.5" 61 61 }, 62 62 "devDependencies": { 63 + "arktype": "^2.2.0", 63 64 "vitest": "^3.2.4" 64 65 } 65 66 }
+2
packages/core/src/index.ts
··· 26 26 export { block, getColumns, getRows, isCancel, wrapTextWithPrefix } from './utils/index.js'; 27 27 export type { ClackSettings } from './utils/settings.js'; 28 28 export { settings, updateSettings } from './utils/settings.js'; 29 + export type { Validate } from './utils/validation.js'; 30 + export { runValidation } from './utils/validation.js';
+6 -4
packages/core/src/prompts/multi-line.ts
··· 1 1 import type { Key } from 'node:readline'; 2 2 import { styleText } from 'node:util'; 3 3 import { findTextCursor } from '../utils/cursor.js'; 4 - import { type Action, settings } from '../utils/index.js'; 5 4 import Prompt, { type PromptOptions } from './prompt.js'; 5 + 6 + type CursorAction = 'up' | 'down' | 'left' | 'right'; 7 + const cursorActions = new Set<CursorAction>(['up', 'down', 'left', 'right']); 6 8 7 9 export interface MultiLineOptions extends PromptOptions<string, MultiLinePrompt> { 8 10 placeholder?: string; ··· 41 43 this.userInput.slice(0, this.cursor) + char + this.userInput.slice(this.cursor) 42 44 ); 43 45 } 44 - #handleCursor(key?: Action) { 46 + #handleCursor(key?: CursorAction) { 45 47 const text = this.value ?? ''; 46 48 switch (key) { 47 49 case 'up': ··· 89 91 this.#showSubmit = opts.showSubmit ?? false; 90 92 91 93 this.on('key', (char, key) => { 92 - if (key?.name && settings.actions.has(key.name as Action)) { 93 - this.#handleCursor(key.name as Action); 94 + if (key?.name && cursorActions.has(key.name as CursorAction)) { 95 + this.#handleCursor(key.name as CursorAction); 94 96 return; 95 97 } 96 98 if (char === '\t' && this.#showSubmit) {
+3 -3
packages/core/src/prompts/multi-select.ts
··· 60 60 0 61 61 ); 62 62 this.cursor = this.options[cursor].disabled ? findCursor<T>(cursor, 1, this.options) : cursor; 63 - this.on('key', (char) => { 64 - if (char === 'a') { 63 + this.on('key', (_char, key) => { 64 + if (key.name === 'a') { 65 65 this.toggleAll(); 66 66 } 67 - if (char === 'i') { 67 + if (key.name === 'i') { 68 68 this.toggleInvert(); 69 69 } 70 70 });
+11 -3
packages/core/src/prompts/prompt.ts
··· 13 13 setRawMode, 14 14 settings, 15 15 } from '../utils/index.js'; 16 + import type { Validate } from '../utils/validation.js'; 17 + import { runValidation } from '../utils/validation.js'; 16 18 17 19 export interface PromptOptions<TValue, Self extends Prompt<TValue>> { 18 20 render(this: Omit<Self, 'prompt'>): string | undefined; 19 21 initialValue?: any; 20 22 initialUserInput?: string; 21 - validate?: ((value: TValue | undefined) => string | Error | undefined) | undefined; 23 + 24 + /** 25 + * A function or a [Standard Schema](https://github.com/standard-schema/standard-schema) 26 + * that validates user input. If a custom function is given, you should return a `string` or `Error` 27 + * to show as a validation error, or `undefined` to accept the result. 28 + */ 29 + validate?: Validate<TValue> | undefined; 22 30 input?: Readable; 23 31 output?: Writable; 24 32 signal?: AbortSignal; ··· 226 234 } 227 235 228 236 // Call the key event handler and emit the key event 229 - this.emit('key', char?.toLowerCase(), key); 237 + this.emit('key', char, key); 230 238 231 239 if (key?.name === 'return' && this._shouldSubmit(char, key)) { 232 240 if (this.opts.validate) { 233 - const problem = this.opts.validate(this.value); 241 + const problem = runValidation(this.opts.validate, this.value); 234 242 if (problem) { 235 243 this.error = problem instanceof Error ? problem.message : problem; 236 244 this.state = 'error';
+3 -3
packages/core/src/prompts/select-key.ts
··· 19 19 }); 20 20 this.cursor = Math.max(keys.indexOf(opts.initialValue), 0); 21 21 22 - this.on('key', (key, keyInfo) => { 22 + this.on('key', (key) => { 23 23 if (!key) { 24 24 return; 25 25 } 26 - const casedKey = caseSensitive && keyInfo.shift ? key.toUpperCase() : key; 26 + const casedKey = caseSensitive ? key : key.toLowerCase(); 27 27 if (!keys.includes(casedKey)) { 28 28 return; 29 29 } 30 30 31 31 const value = this.options.find(({ value: [initial] }) => { 32 - return caseSensitive ? initial === casedKey : initial?.toLowerCase() === key; 32 + return caseSensitive ? initial === casedKey : initial?.toLowerCase() === casedKey; 33 33 }); 34 34 if (value) { 35 35 this.value = value.value;
+8 -2
packages/core/src/utils/index.ts
··· 103 103 text: string, 104 104 prefix: string, 105 105 startPrefix: string = prefix, 106 + endPrefix: string = prefix, 106 107 lineFormatter?: (line: string, index: number) => string 107 108 ): string { 108 109 const columns = getColumns(output ?? stdout); ··· 112 113 }); 113 114 const lines = wrapped 114 115 .split('\n') 115 - .map((line, index) => { 116 + .map((line, index, arr) => { 116 117 const lineString = lineFormatter ? lineFormatter(line, index) : line; 117 - return `${index === 0 ? startPrefix : prefix}${lineString}`; 118 + if (index === 0) { 119 + return `${startPrefix}${lineString}`; 120 + } else if (index === arr.length - 1) { 121 + return `${endPrefix}${lineString}`; 122 + } 123 + return `${prefix}${lineString}`; 118 124 }) 119 125 .join('\n'); 120 126 return lines;
+78
packages/core/src/utils/standard-schema.ts
··· 1 + // https://standardschema.dev/schema 2 + 3 + /** The Standard Schema interface. */ 4 + export interface StandardSchemaV1<Input = unknown, Output = Input> { 5 + /** The Standard Schema properties. */ 6 + readonly '~standard': StandardSchemaV1.Props<Input, Output>; 7 + } 8 + 9 + export declare namespace StandardSchemaV1 { 10 + /** The Standard Schema properties interface. */ 11 + export interface Props<Input = unknown, Output = Input> { 12 + /** The version number of the standard. */ 13 + readonly version: 1; 14 + /** The vendor name of the schema library. */ 15 + readonly vendor: string; 16 + /** Validates unknown input values. */ 17 + readonly validate: ( 18 + value: unknown, 19 + options?: StandardSchemaV1.Options | undefined 20 + ) => Result<Output> | Promise<Result<Output>>; 21 + /** Inferred types associated with the schema. */ 22 + readonly types?: Types<Input, Output> | undefined; 23 + } 24 + 25 + /** The result interface of the validate function. */ 26 + export type Result<Output> = SuccessResult<Output> | FailureResult; 27 + 28 + /** The result interface if validation succeeds. */ 29 + export interface SuccessResult<Output> { 30 + /** The typed output value. */ 31 + readonly value: Output; 32 + /** A falsy value for `issues` indicates success. */ 33 + readonly issues?: undefined; 34 + } 35 + 36 + export interface Options { 37 + /** Explicit support for additional vendor-specific parameters, if needed. */ 38 + readonly libraryOptions?: Record<string, unknown> | undefined; 39 + } 40 + 41 + /** The result interface if validation fails. */ 42 + export interface FailureResult { 43 + /** The issues of failed validation. */ 44 + readonly issues: ReadonlyArray<Issue>; 45 + } 46 + 47 + /** The issue interface of the failure output. */ 48 + export interface Issue { 49 + /** The error message of the issue. */ 50 + readonly message: string; 51 + /** The path of the issue, if any. */ 52 + readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined; 53 + } 54 + 55 + /** The path segment interface of the issue. */ 56 + export interface PathSegment { 57 + /** The key representing a path segment. */ 58 + readonly key: PropertyKey; 59 + } 60 + 61 + /** The Standard Schema types interface. */ 62 + export interface Types<Input = unknown, Output = Input> { 63 + /** The input type of the schema. */ 64 + readonly input: Input; 65 + /** The output type of the schema. */ 66 + readonly output: Output; 67 + } 68 + 69 + /** Infers the input type of a Standard Schema. */ 70 + export type InferInput<Schema extends StandardSchemaV1> = NonNullable< 71 + Schema['~standard']['types'] 72 + >['input']; 73 + 74 + /** Infers the output type of a Standard Schema. */ 75 + export type InferOutput<Schema extends StandardSchemaV1> = NonNullable< 76 + Schema['~standard']['types'] 77 + >['output']; 78 + }
+61
packages/core/src/utils/validation.ts
··· 1 + import type { StandardSchemaV1 } from './standard-schema.js'; 2 + 3 + /** 4 + * A function or [Standard Schema](https://github.com/standard-schema/standard-schema) 5 + * that validates user input. If a custom function is given, you should return a 6 + * `string` or `Error` to show as a validation error, or `undefined` to accept the result. 7 + * 8 + * @example Using arktype 9 + * ```ts 10 + * import { text } from '@clack/prompts'; 11 + * import { type } from 'arktype'; 12 + * 13 + * const name = await text({ 14 + * message: 'Enter your name (letters only)', 15 + * validate: type('string.alpha').describe('Name can only contain letters'), 16 + * }); 17 + * ``` 18 + * 19 + * @example Custom validator 20 + * ```ts 21 + * import { text } from '@clack/prompts'; 22 + * 23 + * const age = await text({ 24 + * message: 'Enter your age:', 25 + * validate(value) { 26 + * if (!value) return 'Please enter a value'; 27 + * const num = parseInt(value); 28 + * if (isNaN(num)) return 'Please enter a valid number'; 29 + * if (num < 0 || num > 120) return 'Age must be between 0 and 120'; 30 + * return undefined; 31 + * }, 32 + * }); 33 + * ``` 34 + */ 35 + export type Validate<TValue> = 36 + | ((value: TValue | undefined) => string | Error | undefined) 37 + | StandardSchemaV1<TValue | undefined, unknown>; 38 + 39 + /** 40 + * Runs the `validate()` option and normalizes the result 41 + * @param validate - The validate option 42 + * @param value - The user input 43 + * @returns the validation result 44 + */ 45 + export function runValidation<TValue>( 46 + validate: Validate<TValue>, 47 + value: TValue | undefined 48 + ): string | Error | undefined { 49 + if ('~standard' in validate) { 50 + const result = validate['~standard'].validate(value); 51 + // https://standardschema.dev/schema#how-to-only-allow-synchronous-validation 52 + // TODO: https://github.com/bombshell-dev/clack/issues/92 53 + if (result instanceof Promise) { 54 + throw new TypeError( 55 + 'Schema validation must be synchronous. Update `validate()` and remove any asynchronous logic.' 56 + ); 57 + } 58 + return result.issues?.at(0)?.message; 59 + } 60 + return validate(value); 61 + }
+24
packages/core/test/prompts/multi-line.test.ts
··· 175 175 expect(result).to.equal('xy'); 176 176 }); 177 177 178 + test('space inserts space', () => { 179 + const instance = new MultiLinePrompt({ 180 + input, 181 + output, 182 + render: () => 'foo', 183 + }); 184 + instance.prompt(); 185 + input.emit('keypress', 'x', { name: 'x' }); 186 + input.emit('keypress', ' ', { name: 'space' }); 187 + expect(instance.userInput).to.equal('x '); 188 + }); 189 + 190 + test('shift modifier inserts uppercase characters', () => { 191 + const instance = new MultiLinePrompt({ 192 + input, 193 + output, 194 + render: () => 'foo', 195 + }); 196 + instance.prompt(); 197 + input.emit('keypress', 'x', { name: 'x' }); 198 + input.emit('keypress', 'X', { name: 'x', shift: true }); 199 + expect(instance.userInput).to.equal('xX'); 200 + }); 201 + 178 202 test('backspace deletes previous char', async () => { 179 203 const instance = new MultiLinePrompt({ 180 204 input,
+94 -58
packages/core/test/prompts/prompt.test.ts
··· 1 + import { type } from 'arktype'; 1 2 import { cursor } from 'sisteransi'; 2 3 import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; 3 4 import { default as Prompt } from '../../src/prompts/prompt.js'; ··· 233 234 expect(instance.state).to.equal('cancel'); 234 235 }); 235 236 236 - test('accepts invalid initial value', () => { 237 - const instance = new Prompt({ 238 - input, 239 - output, 240 - render: () => 'foo', 241 - initialValue: 'invalid', 242 - validate: (value) => (value === 'valid' ? undefined : 'must be valid'), 237 + describe('function validation', () => { 238 + test('accepts invalid initial value', () => { 239 + const instance = new Prompt({ 240 + input, 241 + output, 242 + render: () => 'foo', 243 + initialValue: 'invalid', 244 + validate: (value) => (value === 'valid' ? undefined : 'must be valid'), 245 + }); 246 + instance.prompt(); 247 + 248 + expect(instance.state).to.equal('active'); 249 + expect(instance.error).to.equal(''); 243 250 }); 244 - instance.prompt(); 245 251 246 - expect(instance.state).to.equal('active'); 247 - expect(instance.error).to.equal(''); 248 - }); 252 + test('validates value on return', () => { 253 + const instance = new Prompt({ 254 + input, 255 + output, 256 + render: () => 'foo', 257 + validate: (value) => (value === 'valid' ? undefined : 'must be valid'), 258 + }); 259 + instance.prompt(); 249 260 250 - test('validates value on return', () => { 251 - const instance = new Prompt({ 252 - input, 253 - output, 254 - render: () => 'foo', 255 - validate: (value) => (value === 'valid' ? undefined : 'must be valid'), 256 - }); 257 - instance.prompt(); 261 + instance.value = 'invalid'; 258 262 259 - instance.value = 'invalid'; 263 + input.emit('keypress', '', { name: 'return' }); 260 264 261 - input.emit('keypress', '', { name: 'return' }); 265 + expect(instance.state).to.equal('error'); 266 + expect(instance.error).to.equal('must be valid'); 267 + }); 262 268 263 - expect(instance.state).to.equal('error'); 264 - expect(instance.error).to.equal('must be valid'); 265 - }); 269 + test('validates value with Error object', () => { 270 + const instance = new Prompt({ 271 + input, 272 + output, 273 + render: () => 'foo', 274 + validate: (value) => (value === 'valid' ? undefined : new Error('must be valid')), 275 + }); 276 + instance.prompt(); 266 277 267 - test('validates value with Error object', () => { 268 - const instance = new Prompt({ 269 - input, 270 - output, 271 - render: () => 'foo', 272 - validate: (value) => (value === 'valid' ? undefined : new Error('must be valid')), 278 + instance.value = 'invalid'; 279 + input.emit('keypress', '', { name: 'return' }); 280 + 281 + expect(instance.state).to.equal('error'); 282 + expect(instance.error).to.equal('must be valid'); 273 283 }); 274 - instance.prompt(); 275 284 276 - instance.value = 'invalid'; 277 - input.emit('keypress', '', { name: 'return' }); 285 + test('validates value with regex validation', () => { 286 + const instance = new Prompt<string>({ 287 + input, 288 + output, 289 + render: () => 'foo', 290 + validate: (value) => (/^[A-Z]+$/.test(value ?? '') ? undefined : 'Invalid value'), 291 + }); 292 + instance.prompt(); 278 293 279 - expect(instance.state).to.equal('error'); 280 - expect(instance.error).to.equal('must be valid'); 281 - }); 294 + instance.value = 'Invalid Value $$$'; 295 + input.emit('keypress', '', { name: 'return' }); 282 296 283 - test('validates value with regex validation', () => { 284 - const instance = new Prompt<string>({ 285 - input, 286 - output, 287 - render: () => 'foo', 288 - validate: (value) => (/^[A-Z]+$/.test(value ?? '') ? undefined : 'Invalid value'), 297 + expect(instance.state).to.equal('error'); 298 + expect(instance.error).to.equal('Invalid value'); 289 299 }); 290 - instance.prompt(); 291 300 292 - instance.value = 'Invalid Value $$$'; 293 - input.emit('keypress', '', { name: 'return' }); 301 + test('accepts valid value with regex validation', () => { 302 + const instance = new Prompt<string>({ 303 + input, 304 + output, 305 + render: () => 'foo', 306 + validate: (value) => (/^[A-Z]+$/.test(value ?? '') ? undefined : 'Invalid value'), 307 + }); 308 + instance.prompt(); 309 + 310 + instance.value = 'VALID'; 311 + input.emit('keypress', '', { name: 'return' }); 294 312 295 - expect(instance.state).to.equal('error'); 296 - expect(instance.error).to.equal('Invalid value'); 313 + expect(instance.state).to.equal('submit'); 314 + expect(instance.error).to.equal(''); 315 + }); 297 316 }); 298 317 299 - test('accepts valid value with regex validation', () => { 300 - const instance = new Prompt<string>({ 301 - input, 302 - output, 303 - render: () => 'foo', 304 - validate: (value) => (/^[A-Z]+$/.test(value ?? '') ? undefined : 'Invalid value'), 318 + describe('standard schema', () => { 319 + test('accepts invalid initial value', () => { 320 + const instance = new Prompt<string>({ 321 + input, 322 + output, 323 + render: () => 'foo', 324 + initialValue: 'invalid', 325 + validate: type("'valid'"), 326 + }); 327 + instance.prompt(); 328 + 329 + expect(instance.state).to.equal('active'); 330 + expect(instance.error).to.equal(''); 305 331 }); 306 - instance.prompt(); 332 + 333 + test('validates value on return', () => { 334 + const instance = new Prompt<string>({ 335 + input, 336 + output, 337 + render: () => 'foo', 338 + validate: type("'valid'"), 339 + }); 340 + instance.prompt(); 307 341 308 - instance.value = 'VALID'; 309 - input.emit('keypress', '', { name: 'return' }); 342 + instance.value = 'invalid'; 343 + 344 + input.emit('keypress', '', { name: 'return' }); 310 345 311 - expect(instance.state).to.equal('submit'); 312 - expect(instance.error).to.equal(''); 346 + expect(instance.state).to.equal('error'); 347 + expect(instance.error).to.equal('must be "valid" (was "invalid")'); 348 + }); 313 349 }); 314 350 });
+59
packages/prompts/CHANGELOG.md
··· 1 1 # @clack/prompts 2 2 3 + ## 1.5.0 4 + 5 + ### Minor Changes 6 + 7 + - [#543](https://github.com/bombshell-dev/clack/pull/543) [`83428ac`](https://github.com/bombshell-dev/clack/commit/83428ac6d8bc5eda87615cc7b1f14e0c8b16e1b6) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Adds support for Standard Schema validation 8 + 9 + Prompts accept an optional `validate()` function to validate user input. While a function provides more flexibility and customization over your validation, it can be a bit verbose. To help solve this, there are libraries that provide schema-based validation to make shorthand and type-strict validation substantially easier. 10 + 11 + Libraries following the [Standard Schema specification](https://github.com/standard-schema/standard-schema) are now natively supported. For example, using [Arktype](https://arktype.io/): 12 + 13 + ```diff 14 + import { text } from '@clack/prompts'; 15 + import { type } from 'arktype'; 16 + 17 + const name = await text({ 18 + message: 'Enter your email', 19 + + validate: type('string.email').describe('Invalid email'), 20 + }); 21 + ``` 22 + 23 + ### Patch Changes 24 + 25 + - [#542](https://github.com/bombshell-dev/clack/pull/542) [`adb6af9`](https://github.com/bombshell-dev/clack/commit/adb6af9f5fb39408934323a7415beb46b63ecd9a) Thanks [@ghostdevv](https://github.com/ghostdevv)! - docs: add jsdoc for `box`, `group`, and `group-multi-select` 26 + 27 + - [#534](https://github.com/bombshell-dev/clack/pull/534) [`3dcb31a`](https://github.com/bombshell-dev/clack/commit/3dcb31a7d63827d95a5a52ac630cbd48e3a68364) Thanks [@MattStypa](https://github.com/MattStypa)! - Fixed spaces and uppercase characters in multiline prompt 28 + 29 + - [#540](https://github.com/bombshell-dev/clack/pull/540) [`3170ed9`](https://github.com/bombshell-dev/clack/commit/3170ed94dc2a6ed7973228d46c664fb7461969ad) Thanks [@ghostdevv](https://github.com/ghostdevv)! - docs: add jsdoc for `autocomplete`, `confirm`, and `path` prompts 30 + 31 + - Updated dependencies [[`83428ac`](https://github.com/bombshell-dev/clack/commit/83428ac6d8bc5eda87615cc7b1f14e0c8b16e1b6), [`3dcb31a`](https://github.com/bombshell-dev/clack/commit/3dcb31a7d63827d95a5a52ac630cbd48e3a68364)]: 32 + - @clack/core@1.4.0 33 + 34 + ## 1.4.0 35 + 36 + ### Minor Changes 37 + 38 + - 284677e: Support scrolling and `maxItems` option for `groupMultiselect`, and removes indent when `withGuide` is set to `false` 39 + 40 + ### Patch Changes 41 + 42 + - aab46a2: docs: add jsdoc for `text`, `password`, and `multiline` prompts 43 + - 54be8d7: Fix line wrapping and overflow computation in group multi-select and other list-like prompts. 44 + - Updated dependencies [54be8d7] 45 + - @clack/core@1.3.1 46 + 47 + ## 1.3.0 48 + 49 + ### Minor Changes 50 + 51 + - ea5702e: fix: add engines field expressing node >=20.12 requirement 52 + - 814ab9a: Add new multiline prompt for multi-line text input. 53 + 54 + ### Patch Changes 55 + 56 + - 5b897a7: Fix mixed type-only and runtime exports from @clack/core. 57 + - Updated dependencies [78fd3ae] 58 + - Updated dependencies [ea5702e] 59 + - Updated dependencies [814ab9a] 60 + - @clack/core@1.3.0 61 + 3 62 ## 1.2.0 4 63 5 64 ### Minor Changes
+26 -5
packages/prompts/README.md
··· 65 65 66 66 ### Password 67 67 68 - The password component behaves like `text`, but masks the input as the user types. 68 + The password prompt behaves like the [`text`](#text) prompt, but masks the input as the user types. 69 69 70 70 ```js 71 71 import { password } from '@clack/prompts'; ··· 81 81 82 82 ### Confirm 83 83 84 - The confirm component accepts a yes or no answer. The result is a boolean value of `true` or `false`. 84 + The `confirm` prompt accepts a yes or no choice, returning a boolean value corresponding to the user's selection. 85 85 86 86 ```js 87 87 import { confirm } from '@clack/prompts'; ··· 125 125 126 126 ### Autocomplete 127 127 128 - The autocomplete component lets a user filter a list by typing, then choose one option from the matching results. By default, matching uses each option's `label`, `hint`, and `value`. The result is the selected option's `value`. 128 + The `autocomplete` prompt combines text input with a searchable list of options. It's perfect for when you have a large list of options and want to help users find what they're looking for quickly. 129 129 130 130 ```js 131 131 import { autocomplete } from '@clack/prompts'; ··· 142 142 }); 143 143 ``` 144 144 145 + ### Autocomplete Multi-Select 146 + 147 + The `autocompleteMultiselect` prompt combines the search functionality of [autocomplete](#autocomplete) with the ability to select multiple options. 148 + 149 + ```js 150 + import { autocomplete } from '@clack/prompts'; 151 + 152 + const framework = await autocomplete({ 153 + message: 'Search for a framework', 154 + options: [ 155 + { value: 'next', label: 'Next.js', hint: 'React framework' }, 156 + { value: 'astro', label: 'Astro', hint: 'Content-focused' }, 157 + { value: 'svelte', label: 'SvelteKit', hint: 'Compile-time framework' }, 158 + { value: 'remix', label: 'Remix', hint: 'Full stack framework' }, 159 + { value: 'nuxt', label: 'Nuxt', hint: 'Vue framework' }, 160 + ], 161 + placeholder: 'Type to search...', 162 + maxItems: 5, 163 + }); 164 + ``` 165 + 145 166 ### Select Key 146 167 147 168 The `selectKey` component lets a user choose an option by pressing its single-character string `value` key directly. ··· 201 222 202 223 ### Multi-Line Text 203 224 204 - The multi-line text component accepts multiple lines of text input. By default, pressing `Enter` twice submits the input. 225 + The multi-line prompt accepts multiple lines of text input. By default, pressing `Enter` twice submits the input. 205 226 206 227 ```js 207 228 import { multiline } from '@clack/prompts'; ··· 226 247 227 248 ### Path 228 249 229 - The path component offers filesystem path suggestions and returns the selected path as a string. When `directory: true` is set, only directories can be selected. 250 + The `path` prompt extends [`autocomplete`](#autocomplete) to provide file and directory suggestions. 230 251 231 252 ```js 232 253 import { path } from '@clack/prompts';
+1 -1
packages/prompts/package.json
··· 1 1 { 2 2 "name": "@clack/prompts", 3 - "version": "1.2.0", 3 + "version": "1.5.0", 4 4 "type": "module", 5 5 "main": "./dist/index.mjs", 6 6 "module": "./dist/index.mjs",
+79 -14
packages/prompts/src/autocomplete.ts
··· 1 1 import { styleText } from 'node:util'; 2 + import type { Validate } from '@clack/core'; 2 3 import { AutocompletePrompt, settings } from '@clack/core'; 3 4 import { 4 5 type CommonOptions, ··· 41 42 return results; 42 43 } 43 44 45 + /** 46 + * Options for the {@link autocomplete} prompt. 47 + */ 44 48 interface AutocompleteSharedOptions<Value> extends CommonOptions { 45 49 /** 46 - * The message to display to the user. 50 + * The message or question shown to the user above the input. 47 51 */ 48 52 message: string; 53 + 49 54 /** 50 - * Available options for the autocomplete prompt. 55 + * The options to present, or a function that returns the options to present 56 + * allowing for custom search/filtering. 57 + * 58 + * @see https://bomb.sh/docs/clack/packages/prompts/#dynamic-options-getter 51 59 */ 52 60 options: Option<Value>[] | ((this: AutocompletePrompt<Option<Value>>) => Option<Value>[]); 61 + 53 62 /** 54 - * Maximum number of items to display at once. 63 + * The maximum number of items/options to display in the autocomplete list at once. 55 64 */ 56 65 maxItems?: number; 66 + 57 67 /** 58 - * Placeholder text to display when no input is provided. 68 + * Placeholder text displayed when the search field is empty. When set, pressing 69 + * tab copies the placeholder into the input. 59 70 */ 60 71 placeholder?: string; 72 + 61 73 /** 62 - * Validates the value 74 + * A function or a [Standard Schema](https://github.com/standard-schema/standard-schema) 75 + * that validates user input. If a custom function is given, you should return a `string` or `Error` 76 + * to show as a validation error, or `undefined` to accept the result. 63 77 */ 64 - validate?: (value: Value | Value[] | undefined) => string | Error | undefined; 78 + validate?: Validate<Value | Value[]>; 79 + 65 80 /** 66 - * Custom filter function to match options against search input. 67 - * If not provided, a default filter that matches label, hint, and value is used. 81 + * Custom filter function to match options against the search input. 68 82 */ 69 83 filter?: (search: string, option: Option<Value>) => boolean; 70 84 } 71 85 72 86 export interface AutocompleteOptions<Value> extends AutocompleteSharedOptions<Value> { 73 87 /** 74 - * The initial selected value. 88 + * The initially selected option from the list. 75 89 */ 76 90 initialValue?: Value; 91 + 77 92 /** 78 - * The initial user input 93 + * The starting value shown in the users input box. 79 94 */ 80 95 initialUserInput?: string; 81 96 } 82 97 98 + /** 99 + * The `autocomplete` prompt combines a text input with a searchable list of options. 100 + * It's perfect for when you have a large list of options and want to help users 101 + * find what they're looking for quickly. 102 + * 103 + * @see https://bomb.sh/docs/clack/packages/prompts/#autocomplete 104 + * 105 + * @example 106 + * ```ts 107 + * import { autocomplete } from '@clack/prompts'; 108 + * 109 + * const framework = await autocomplete({ 110 + * message: 'Search for a framework', 111 + * options: [ 112 + * { value: 'next', label: 'Next.js', hint: 'React framework' }, 113 + * { value: 'astro', label: 'Astro', hint: 'Content-focused' }, 114 + * { value: 'svelte', label: 'SvelteKit', hint: 'Compile-time framework' }, 115 + * { value: 'remix', label: 'Remix', hint: 'Full stack framework' }, 116 + * { value: 'nuxt', label: 'Nuxt', hint: 'Vue framework' }, 117 + * ], 118 + * placeholder: 'Type to search...', 119 + * maxItems: 5, 120 + * }); 121 + * ``` 122 + */ 83 123 export const autocomplete = <Value>(opts: AutocompleteOptions<Value>) => { 84 124 const prompt = new AutocompletePrompt({ 85 125 options: opts.options, ··· 223 263 return prompt.prompt() as Promise<Value | symbol>; 224 264 }; 225 265 226 - // Type definition for the autocompleteMultiselect component 266 + /** 267 + * Options for the {@link autocompleteMultiselect} prompt 268 + */ 227 269 export interface AutocompleteMultiSelectOptions<Value> extends AutocompleteSharedOptions<Value> { 228 270 /** 229 - * The initial selected values 271 + * The initially selected option(s) from the list. 230 272 */ 231 273 initialValues?: Value[]; 274 + 232 275 /** 233 - * If true, at least one option must be selected 276 + * When `true` at least one option must be selected. 277 + * @default false 234 278 */ 235 279 required?: boolean; 236 280 } 237 281 238 282 /** 239 - * Integrated autocomplete multiselect - combines type-ahead filtering with multiselect in one UI 283 + * The `autocompleteMultiselect` prompt combines the search functionality of autocomplete 284 + * with the ability to select multiple options. 285 + * 286 + * @see https://bomb.sh/docs/clack/packages/prompts/#autocomplete-multiselect 287 + * 288 + * @example 289 + * ```ts 290 + * import { autocompleteMultiselect } from '@clack/prompts'; 291 + * 292 + * const frameworks = await autocompleteMultiselect({ 293 + * message: 'Select frameworks', 294 + * options: [ 295 + * { value: 'next', label: 'Next.js', hint: 'React framework' }, 296 + * { value: 'astro', label: 'Astro', hint: 'Content-focused' }, 297 + * { value: 'svelte', label: 'SvelteKit', hint: 'Compile-time framework' }, 298 + * { value: 'remix', label: 'Remix', hint: 'Full stack framework' }, 299 + * { value: 'nuxt', label: 'Nuxt', hint: 'Vue framework' }, 300 + * ], 301 + * placeholder: 'Type to search...', 302 + * maxItems: 5, 303 + * }); 304 + * ``` 240 305 */ 241 306 export const autocompleteMultiselect = <Value>(opts: AutocompleteMultiSelectOptions<Value>) => { 242 307 const formatOption = (
+61
packages/prompts/src/box.ts
··· 16 16 S_CORNER_TOP_RIGHT, 17 17 } from './common.js'; 18 18 19 + /** 20 + * Alignment for content or titles within the box. 21 + */ 19 22 export type BoxAlignment = 'left' | 'center' | 'right'; 20 23 21 24 type BoxSymbols = [topLeft: string, topRight: string, bottomLeft: string, bottomRight: string]; ··· 28 31 ]; 29 32 const squareSymbols: BoxSymbols = [S_BAR_START, S_BAR_START_RIGHT, S_BAR_END, S_BAR_END_RIGHT]; 30 33 34 + /** 35 + * Options for the {@link box} prompt. 36 + */ 31 37 export interface BoxOptions extends CommonOptions { 38 + /** 39 + * Alignment of the content (`'left'`, `'center'`, or `'right'`). 40 + * @default 'left' 41 + */ 32 42 contentAlign?: BoxAlignment; 43 + 44 + /** 45 + * Alignment of the title (`'left'`, `'center'`, or `'right'`). 46 + * @default 'left' 47 + */ 33 48 titleAlign?: BoxAlignment; 49 + 50 + /** 51 + * The width of the box, either `'auto'` to fit the content or a number for a fixed width. 52 + * @default 'auto' 53 + */ 34 54 width?: number | 'auto'; 55 + 56 + /** 57 + * Padding around the title. 58 + * @default 1 59 + */ 35 60 titlePadding?: number; 61 + 62 + /** 63 + * Padding around the content. 64 + * @default 2 65 + */ 36 66 contentPadding?: number; 67 + 68 + /** 69 + * Use rounded corners when `true`, square corners when `false`. 70 + * @default true 71 + */ 37 72 rounded?: boolean; 73 + 74 + /** 75 + * Custom function to style the border characters. 76 + */ 38 77 formatBorder?: (text: string) => string; 39 78 } 40 79 ··· 59 98 60 99 const defaultFormatBorder = (text: string) => text; 61 100 101 + /** 102 + * Renders a customizable box around text content. It's similar to {@link note} but offers 103 + * more styling options. 104 + * 105 + * @see https://bomb.sh/docs/clack/packages/prompts/#box 106 + * 107 + * @param message - The content to display inside the box. 108 + * @param title - The title to display in the top border of the box. 109 + * @param opts - Optional configuration for the box styling and behavior. 110 + * 111 + * @example 112 + * ```ts 113 + * import { box } from '@clack/prompts'; 114 + * 115 + * box('This is the content of the box', 'Box Title', { 116 + * contentAlign: 'center', 117 + * titleAlign: 'center', 118 + * width: 'auto', 119 + * rounded: true, 120 + * }); 121 + * ``` 122 + */ 62 123 export const box = (message = '', title = '', opts?: BoxOptions) => { 63 124 const output: Writable = opts?.output ?? process.stdout; 64 125 const columns = getColumns(output);
+42
packages/prompts/src/confirm.ts
··· 9 9 symbol, 10 10 } from './common.js'; 11 11 12 + /** 13 + * Options for the {@link confirm} prompt. 14 + */ 12 15 export interface ConfirmOptions extends CommonOptions { 16 + /** 17 + * The message or question shown to the user above the input. 18 + */ 13 19 message: string; 20 + 21 + /** 22 + * The label to use for the active (true) option. 23 + * @default 'Yes' 24 + */ 14 25 active?: string; 26 + 27 + /** 28 + * The label to use for the inactive (false) option. 29 + * @default 'No' 30 + */ 15 31 inactive?: string; 32 + 33 + /** 34 + * The initial selected value (true or false). 35 + * @default true 36 + */ 16 37 initialValue?: boolean; 38 + 39 + /** 40 + * Whether to render the options vertically instead of horizontally. 41 + * @default false 42 + */ 17 43 vertical?: boolean; 18 44 } 45 + 46 + /** 47 + * The `confirm` prompt accepts a yes or no choice, returning a boolean value 48 + * corresponding to the user's selection. 49 + * 50 + * @see https://bomb.sh/docs/clack/packages/prompts/#confirmation 51 + * 52 + * @example 53 + * ```ts 54 + * import { confirm } from '@clack/prompts'; 55 + * 56 + * const shouldProceed = await confirm({ 57 + * message: 'Do you want to continue?', 58 + * }); 59 + * ``` 60 + */ 19 61 export const confirm = (opts: ConfirmOptions) => { 20 62 const active = opts.active ?? 'Yes'; 21 63 const inactive = opts.inactive ?? 'No';
+11 -5
packages/prompts/src/date.ts
··· 1 1 import { styleText } from 'node:util'; 2 - import type { DateFormat, State } from '@clack/core'; 3 - import { DatePrompt, settings } from '@clack/core'; 2 + import type { DateFormat, State, Validate } from '@clack/core'; 3 + import { DatePrompt, runValidation, settings } from '@clack/core'; 4 4 import { type CommonOptions, S_BAR, S_BAR_END, symbol } from './common.js'; 5 5 6 6 export type { DateFormat }; ··· 13 13 initialValue?: Date; 14 14 minDate?: Date; 15 15 maxDate?: Date; 16 - validate?: (value: Date | undefined) => string | Error | undefined; 16 + 17 + /** 18 + * A function or a [Standard Schema](https://github.com/standard-schema/standard-schema) 19 + * that validates user input. If a custom function is given, you should return a `string` or `Error` 20 + * to show as a validation error, or `undefined` to accept the result. 21 + */ 22 + validate?: Validate<Date>; 17 23 } 18 24 19 25 export const date = (opts: DateOptions) => { ··· 23 29 validate(value: Date | undefined) { 24 30 if (value === undefined) { 25 31 if (opts.defaultValue !== undefined) return undefined; 26 - if (validate) return validate(value); 32 + if (validate) return runValidation(validate, value); 27 33 return settings.date.messages.required; 28 34 } 29 35 const iso = (d: Date) => d.toISOString().slice(0, 10); ··· 33 39 if (opts.maxDate && iso(value) > iso(opts.maxDate)) { 34 40 return settings.date.messages.beforeMax(opts.maxDate); 35 41 } 36 - if (validate) return validate(value); 42 + if (validate) return runValidation(validate, value); 37 43 return undefined; 38 44 }, 39 45 render() {
+177 -68
packages/prompts/src/group-multi-select.ts
··· 1 1 import { styleText } from 'node:util'; 2 - import { GroupMultiSelectPrompt, settings } from '@clack/core'; 2 + import { GroupMultiSelectPrompt, settings, wrapTextWithPrefix } from '@clack/core'; 3 3 import { 4 4 type CommonOptions, 5 5 S_BAR, ··· 9 9 S_CHECKBOX_SELECTED, 10 10 symbol, 11 11 } from './common.js'; 12 + import { limitOptions } from './limit-options.js'; 12 13 import type { Option } from './select.js'; 13 14 15 + /** 16 + * Options for the {@link groupMultiselect} prompt. 17 + */ 14 18 export interface GroupMultiSelectOptions<Value> extends CommonOptions { 19 + /** 20 + * The message or question shown to the user above the input. 21 + */ 15 22 message: string; 23 + 24 + /** 25 + * Grouped options to display. Each key is a group label, and each value is an array of options. 26 + */ 16 27 options: Record<string, Option<Value>[]>; 28 + 29 + /** 30 + * The initially selected option(s). 31 + */ 17 32 initialValues?: Value[]; 33 + 34 + /** 35 + * The maximum number of items/options to display at once. 36 + */ 37 + maxItems?: number; 38 + 39 + /** 40 + * When `true` at least one option must be selected. 41 + * @default true 42 + */ 18 43 required?: boolean; 44 + 45 + /** 46 + * The value the cursor should be positioned at initially. 47 + */ 19 48 cursorAt?: Value; 49 + 50 + /** 51 + * Whether entire groups can be selected at once. 52 + * @default true 53 + */ 20 54 selectableGroups?: boolean; 55 + 56 + /** 57 + * Number of blank lines between groups. 58 + * @default 0 59 + */ 21 60 groupSpacing?: number; 22 61 } 62 + 63 + /** 64 + * The `groupMultiselect` prompt extends the {@link multiselect} prompt to allow 65 + * arranging distinct Multi-Selects, whilst keeping all of them interactive. 66 + * 67 + * @see https://bomb.sh/docs/clack/packages/prompts/#group-multiselect 68 + * 69 + * @example 70 + * ```ts 71 + * import { groupMultiselect } from '@clack/prompts'; 72 + * 73 + * const result = await groupMultiselect({ 74 + * message: 'Define your project', 75 + * options: { 76 + * 'Testing': [ 77 + * { value: 'Jest', hint: 'JavaScript testing framework' }, 78 + * { value: 'Playwright', hint: 'End-to-end testing' }, 79 + * ], 80 + * 'Language': [ 81 + * { value: 'js', label: 'JavaScript', hint: 'Dynamic typing' }, 82 + * { value: 'ts', label: 'TypeScript', hint: 'Static typing' }, 83 + * ], 84 + * }, 85 + * }); 86 + * ``` 87 + * 88 + * @param opts The options for the group multiselect prompt 89 + */ 23 90 export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) => { 24 91 const { selectableGroups = true, groupSpacing = 0 } = opts; 25 92 const opt = ( ··· 39 106 const isItem = typeof option.group === 'string'; 40 107 const next = isItem && (options[options.indexOf(option) + 1] ?? { group: true }); 41 108 const isLast = isItem && next && next.group === true; 42 - const prefix = isItem ? (selectableGroups ? `${isLast ? S_BAR_END : S_BAR} ` : ' ') : ''; 109 + let prefix = ''; 110 + let prefixEnd = ''; 111 + if (isItem) { 112 + if (selectableGroups) { 113 + prefix = isLast ? `${S_BAR_END} ` : `${S_BAR} `; 114 + prefixEnd = isLast ? ` ` : `${S_BAR} `; 115 + } else { 116 + prefix = ' '; 117 + } 118 + } 43 119 let spacingPrefix = ''; 44 120 if (groupSpacing > 0 && !isItem) { 45 - const spacingPrefixText = `\n${styleText('cyan', S_BAR)}`; 46 - spacingPrefix = `${spacingPrefixText.repeat(groupSpacing - 1)}${spacingPrefixText} `; 121 + spacingPrefix = '\n'.repeat(groupSpacing); 47 122 } 48 123 49 124 if (state === 'active') { 50 - return `${spacingPrefix}${styleText('dim', prefix)}${styleText('cyan', S_CHECKBOX_ACTIVE)} ${label}${ 51 - option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' 52 - }`; 125 + return wrapTextWithPrefix( 126 + opts.output, 127 + `${label}${option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : ''}`, 128 + `${spacingPrefix}${styleText('dim', prefix)} `, 129 + `${spacingPrefix}${styleText('dim', prefix)}${styleText('cyan', S_CHECKBOX_ACTIVE)} `, 130 + `${spacingPrefix}${styleText('dim', prefixEnd)} ` 131 + ); 53 132 } 54 133 if (state === 'group-active') { 55 - return `${spacingPrefix}${prefix}${styleText('cyan', S_CHECKBOX_ACTIVE)} ${styleText('dim', label)}`; 134 + return wrapTextWithPrefix( 135 + opts.output, 136 + label, 137 + `${spacingPrefix}${prefix} `, 138 + `${spacingPrefix}${prefix}${styleText('cyan', S_CHECKBOX_ACTIVE)} `, 139 + `${spacingPrefix}${prefixEnd} `, 140 + (str) => styleText('dim', str) 141 + ); 56 142 } 57 143 if (state === 'group-active-selected') { 58 - return `${spacingPrefix}${prefix}${styleText('green', S_CHECKBOX_SELECTED)} ${styleText('dim', label)}`; 144 + return wrapTextWithPrefix( 145 + opts.output, 146 + label, 147 + `${spacingPrefix}${prefix} `, 148 + `${spacingPrefix}${prefix}${styleText('green', S_CHECKBOX_SELECTED)} `, 149 + `${spacingPrefix}${prefixEnd} `, 150 + (str) => styleText('dim', str) 151 + ); 59 152 } 60 153 if (state === 'selected') { 61 154 const selectedCheckbox = 62 155 isItem || selectableGroups ? styleText('green', S_CHECKBOX_SELECTED) : ''; 63 - return `${spacingPrefix}${styleText('dim', prefix)}${selectedCheckbox} ${styleText('dim', label)}${ 64 - option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' 65 - }`; 156 + return wrapTextWithPrefix( 157 + opts.output, 158 + `${label}${option.hint ? ` (${option.hint})` : ''}`, 159 + `${spacingPrefix}${styleText('dim', prefix)} `, 160 + `${spacingPrefix}${styleText('dim', prefix)}${selectedCheckbox} `, 161 + `${spacingPrefix}${styleText('dim', prefixEnd)} `, 162 + (str) => styleText('dim', str) 163 + ); 66 164 } 67 165 if (state === 'cancelled') { 68 166 return `${styleText(['strikethrough', 'dim'], label)}`; 69 167 } 70 168 if (state === 'active-selected') { 71 - return `${spacingPrefix}${styleText('dim', prefix)}${styleText('green', S_CHECKBOX_SELECTED)} ${label}${ 72 - option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : '' 73 - }`; 169 + return wrapTextWithPrefix( 170 + opts.output, 171 + `${label}${option.hint ? ` ${styleText('dim', `(${option.hint})`)}` : ''}`, 172 + `${spacingPrefix}${styleText('dim', prefix)} `, 173 + `${spacingPrefix}${styleText('dim', prefix)}${styleText('green', S_CHECKBOX_SELECTED)} `, 174 + `${spacingPrefix}${styleText('dim', prefixEnd)} ` 175 + ); 74 176 } 75 177 if (state === 'submitted') { 76 178 return `${styleText('dim', label)}`; 77 179 } 78 180 const unselectedCheckbox = 79 181 isItem || selectableGroups ? styleText('dim', S_CHECKBOX_INACTIVE) : ''; 80 - return `${spacingPrefix}${styleText('dim', prefix)}${unselectedCheckbox} ${styleText('dim', label)}`; 182 + return wrapTextWithPrefix( 183 + opts.output, 184 + label, 185 + `${spacingPrefix}${styleText('dim', prefix)} `, 186 + `${spacingPrefix}${styleText('dim', prefix)}${unselectedCheckbox} `, 187 + `${spacingPrefix}${styleText('dim', prefixEnd)} `, 188 + (str) => styleText('dim', str) 189 + ); 81 190 }; 82 191 const required = opts.required ?? true; 83 192 ··· 108 217 const title = `${hasGuide ? `${styleText('gray', S_BAR)}\n` : ''}${symbol(this.state)} ${opts.message}\n`; 109 218 const value = this.value ?? []; 110 219 220 + const styleOption = ( 221 + option: Option<Value> & { group: string | boolean }, 222 + active: boolean 223 + ) => { 224 + const options = this.options; 225 + const selected = 226 + value.includes(option.value) || 227 + (option.group === true && this.isGroupSelected(`${option.value}`)); 228 + const groupActive = 229 + !active && 230 + typeof option.group === 'string' && 231 + this.options[this.cursor].value === option.group; 232 + if (groupActive) { 233 + return opt(option, selected ? 'group-active-selected' : 'group-active', options); 234 + } 235 + if (active && selected) { 236 + return opt(option, 'active-selected', options); 237 + } 238 + if (selected) { 239 + return opt(option, 'selected', options); 240 + } 241 + return opt(option, active ? 'active' : 'inactive', options); 242 + }; 243 + 111 244 switch (this.state) { 112 245 case 'submit': { 113 246 const selectedOptions = this.options ··· 127 260 }`; 128 261 } 129 262 case 'error': { 263 + const guidePrefix = hasGuide ? `${styleText('yellow', S_BAR)} ` : ''; 130 264 const footer = this.error 131 265 .split('\n') 132 266 .map((ln, i) => ··· 135 269 : ` ${ln}` 136 270 ) 137 271 .join('\n'); 138 - return `${title}${hasGuide ? `${styleText('yellow', S_BAR)} ` : ''}${this.options 139 - .map((option, i, options) => { 140 - const selected = 141 - value.includes(option.value) || 142 - (option.group === true && this.isGroupSelected(`${option.value}`)); 143 - const active = i === this.cursor; 144 - const groupActive = 145 - !active && 146 - typeof option.group === 'string' && 147 - this.options[this.cursor].value === option.group; 148 - if (groupActive) { 149 - return opt(option, selected ? 'group-active-selected' : 'group-active', options); 150 - } 151 - if (active && selected) { 152 - return opt(option, 'active-selected', options); 153 - } 154 - if (selected) { 155 - return opt(option, 'selected', options); 156 - } 157 - return opt(option, active ? 'active' : 'inactive', options); 158 - }) 159 - .join(`\n${hasGuide ? `${styleText('yellow', S_BAR)} ` : ''}`)}\n${footer}\n`; 272 + // Calculate rowPadding: title lines + footer lines (error message + trailing newline) 273 + const titleLineCount = title.split('\n').length; 274 + const footerLineCount = footer.split('\n').length + 1; // footer + trailing newline 275 + const optionsText = limitOptions({ 276 + output: opts.output, 277 + options: this.options, 278 + cursor: this.cursor, 279 + maxItems: opts.maxItems, 280 + columnPadding: guidePrefix.length, 281 + rowPadding: titleLineCount + footerLineCount, 282 + style: styleOption, 283 + }).join(`\n${guidePrefix}`); 284 + return `${title}${guidePrefix}${optionsText}\n${footer}\n`; 160 285 } 161 286 default: { 162 - const optionsText = this.options 163 - .map((option, i, options) => { 164 - const selected = 165 - value.includes(option.value) || 166 - (option.group === true && this.isGroupSelected(`${option.value}`)); 167 - const active = i === this.cursor; 168 - const groupActive = 169 - !active && 170 - typeof option.group === 'string' && 171 - this.options[this.cursor].value === option.group; 172 - let optionText = ''; 173 - if (groupActive) { 174 - optionText = opt( 175 - option, 176 - selected ? 'group-active-selected' : 'group-active', 177 - options 178 - ); 179 - } else if (active && selected) { 180 - optionText = opt(option, 'active-selected', options); 181 - } else if (selected) { 182 - optionText = opt(option, 'selected', options); 183 - } else { 184 - optionText = opt(option, active ? 'active' : 'inactive', options); 185 - } 186 - const prefix = i !== 0 && !optionText.startsWith('\n') ? ' ' : ''; 187 - return `${prefix}${optionText}`; 188 - }) 189 - .join(`\n${hasGuide ? styleText('cyan', S_BAR) : ''}`); 190 - const optionsPrefix = optionsText.startsWith('\n') ? '' : ' '; 191 - return `${title}${hasGuide ? styleText('cyan', S_BAR) : ''}${optionsPrefix}${optionsText}\n${ 287 + const guidePrefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : ''; 288 + // Calculate rowPadding: title lines + footer lines (S_BAR_END + trailing newline) 289 + const titleLineCount = title.split('\n').length; 290 + const footerLineCount = (hasGuide ? 1 : 0) + 1; // guide line + trailing newline 291 + const optionsText = limitOptions({ 292 + output: opts.output, 293 + options: this.options, 294 + cursor: this.cursor, 295 + maxItems: opts.maxItems, 296 + columnPadding: guidePrefix.length, 297 + rowPadding: titleLineCount + footerLineCount, 298 + style: styleOption, 299 + }).join(`\n${guidePrefix}`); 300 + return `${title}${guidePrefix}${optionsText}\n${ 192 301 hasGuide ? styleText('cyan', S_BAR_END) : '' 193 302 }\n`; 194 303 }
+35 -4
packages/prompts/src/group.ts
··· 4 4 [P in keyof T]: T[P]; 5 5 } & {}; 6 6 7 + /** 8 + * The return type of a {@link PromptGroup}. 9 + * Resolves all prompt results, excluding the cancel symbol. 10 + */ 7 11 export type PromptGroupAwaitedReturn<T> = { 8 12 [P in keyof T]: Exclude<Awaited<T[P]>, symbol>; 9 13 }; 10 14 15 + /** 16 + * Options for the {@link group} utility. 17 + */ 11 18 export interface PromptGroupOptions<T> { 12 19 /** 13 - * Control how the group can be canceled 14 - * if one of the prompts is canceled. 20 + * Called when any one of the prompts is canceled. 15 21 */ 16 22 onCancel?: (opts: { results: Prettify<Partial<PromptGroupAwaitedReturn<T>>> }) => void; 17 23 } 18 24 25 + /** 26 + * A group of prompts to be displayed sequentially, with each prompt receiving 27 + * the results of all previous prompts in the group. 28 + */ 19 29 export type PromptGroup<T> = { 20 30 [P in keyof T]: (opts: { 21 31 results: Prettify<Partial<PromptGroupAwaitedReturn<Omit<T, P>>>>; ··· 23 33 }; 24 34 25 35 /** 26 - * Define a group of prompts to be displayed 27 - * and return a results of objects within the group 36 + * The `group` utility provides a consistent way to combine a series of prompts, 37 + * combining each answer into one object. Each prompt receives the results of 38 + * all previously completed prompts, and are displayed sequentially. 39 + * 40 + * @see https://bomb.sh/docs/clack/packages/prompts/#group 41 + * 42 + * @example 43 + * ```ts 44 + * import { group, text, password } from '@clack/prompts'; 45 + * 46 + * const account = await group({ 47 + * email: () => text({ 48 + * message: 'What is your email address?', 49 + * }), 50 + * username: ({ results }) => text({ 51 + * message: 'What is your username?', 52 + * placeholder: results.email?.replace(/@.+$/, '').toLowerCase() ?? '', 53 + * }), 54 + * password: () => password({ 55 + * message: 'Define your password', 56 + * }), 57 + * }); 58 + * ``` 28 59 */ 29 60 export const group = async <T>( 30 61 prompts: PromptGroup<T>,
+2 -1
packages/prompts/src/index.ts
··· 1 - export { type ClackSettings, isCancel, settings, updateSettings } from '@clack/core'; 1 + export type { ClackSettings } from '@clack/core'; 2 + export { isCancel, settings, updateSettings } from '@clack/core'; 2 3 3 4 export * from './autocomplete.js'; 4 5 export * from './box.js';
+34 -27
packages/prompts/src/limit-options.ts
··· 17 17 initialLineCount: number, 18 18 startIndex: number, 19 19 endIndex: number, 20 - maxLines: number 20 + maxLines: number, 21 + fromEnd = false 21 22 ) => { 22 23 let lineCount = initialLineCount; 23 24 let removals = 0; 24 - for (let i = startIndex; i < endIndex; i++) { 25 - const group = groups[i]; 26 - lineCount = lineCount - group.length; 27 - removals++; 28 - if (lineCount <= maxLines) { 29 - break; 25 + if (fromEnd) { 26 + for (let i = endIndex - 1; i >= startIndex; i--) { 27 + lineCount -= groups[i].length; 28 + removals++; 29 + if (lineCount <= maxLines) break; 30 + } 31 + } else { 32 + for (let i = startIndex; i < endIndex; i++) { 33 + lineCount -= groups[i].length; 34 + removals++; 35 + if (lineCount <= maxLines) break; 30 36 } 31 37 } 32 38 return { lineCount, removals }; ··· 94 100 let followingRemovals = 0; 95 101 let newLineCount = lineCount; 96 102 const cursorGroupIndex = cursor - slidingWindowLocationWithEllipsis; 97 - const trimLinesLocal = (startIndex: number, endIndex: number) => 98 - trimLines(lineGroups, newLineCount, startIndex, endIndex, outputMaxItems); 103 + let adjustedMax = outputMaxItems; 104 + const trimPreceding = () => 105 + trimLines(lineGroups, newLineCount, 0, cursorGroupIndex, adjustedMax); 106 + const trimFollowing = () => 107 + trimLines( 108 + lineGroups, 109 + newLineCount, 110 + cursorGroupIndex + 1, 111 + lineGroups.length, 112 + adjustedMax, 113 + true 114 + ); 99 115 100 116 if (shouldRenderTopEllipsis) { 101 - ({ lineCount: newLineCount, removals: precedingRemovals } = trimLinesLocal( 102 - 0, 103 - cursorGroupIndex 104 - )); 105 - if (newLineCount > outputMaxItems) { 106 - ({ lineCount: newLineCount, removals: followingRemovals } = trimLinesLocal( 107 - cursorGroupIndex + 1, 108 - lineGroups.length 109 - )); 117 + ({ lineCount: newLineCount, removals: precedingRemovals } = trimPreceding()); 118 + if (newLineCount > adjustedMax) { 119 + if (!shouldRenderBottomEllipsis) adjustedMax -= 1; 120 + ({ lineCount: newLineCount, removals: followingRemovals } = trimFollowing()); 110 121 } 111 122 } else { 112 - ({ lineCount: newLineCount, removals: followingRemovals } = trimLinesLocal( 113 - cursorGroupIndex + 1, 114 - lineGroups.length 115 - )); 116 - if (newLineCount > outputMaxItems) { 117 - ({ lineCount: newLineCount, removals: precedingRemovals } = trimLinesLocal( 118 - 0, 119 - cursorGroupIndex 120 - )); 123 + if (!shouldRenderBottomEllipsis) adjustedMax -= 1; 124 + ({ lineCount: newLineCount, removals: followingRemovals } = trimFollowing()); 125 + if (newLineCount > adjustedMax) { 126 + adjustedMax -= 1; 127 + ({ lineCount: newLineCount, removals: precedingRemovals } = trimPreceding()); 121 128 } 122 129 } 123 130
+28 -2
packages/prompts/src/multi-line.ts
··· 3 3 import { S_BAR, S_BAR_END, symbol } from './common.js'; 4 4 import type { TextOptions } from './text.js'; 5 5 6 + /** 7 + * Options for the {@link multiline} prompt 8 + */ 6 9 export interface MultiLineOptions extends TextOptions { 10 + /** 11 + * When enabled it shows a `[ submit ]` button that can be focused with tab. 12 + * By default, pressing `Enter` twice submits the input 13 + * 14 + * @default false 15 + */ 7 16 showSubmit?: boolean; 8 17 } 9 18 19 + /** 20 + * The multi-line prompt accepts multiple lines of text input. 21 + * By default, pressing `Enter` twice submits the input. 22 + * 23 + * @see https://bomb.sh/docs/clack/packages/prompts/#multi-line-text 24 + * 25 + * @example 26 + * ```ts 27 + * import { multiline } from '@clack/prompts'; 28 + * 29 + * const bio = await multiline({ 30 + * message: 'Enter your bio', 31 + * placeholder: 'Tell us about yourself...', 32 + * showSubmit: true, 33 + * }); 34 + * ``` 35 + */ 10 36 export const multiline = (opts: MultiLineOptions) => { 11 37 return new MultiLinePrompt({ 12 38 validate: opts.validate, ··· 41 67 case 'submit': { 42 68 const submitPrefix = `${styleText('gray', S_BAR)} `; 43 69 const lines = hasGuide 44 - ? wrapTextWithPrefix(opts.output, value, submitPrefix, undefined, (str) => 70 + ? wrapTextWithPrefix(opts.output, value, submitPrefix, undefined, undefined, (str) => 45 71 styleText('dim', str) 46 72 ) 47 73 : value ··· 52 78 case 'cancel': { 53 79 const cancelPrefix = `${styleText('gray', S_BAR)} `; 54 80 const lines = hasGuide 55 - ? wrapTextWithPrefix(opts.output, value, cancelPrefix, undefined, (str) => 81 + ? wrapTextWithPrefix(opts.output, value, cancelPrefix, undefined, undefined, (str) => 56 82 styleText(['strikethrough', 'dim'], str) 57 83 ) 58 84 : value
+39 -1
packages/prompts/src/password.ts
··· 1 1 import { styleText } from 'node:util'; 2 + import type { Validate } from '@clack/core'; 2 3 import { PasswordPrompt, settings } from '@clack/core'; 3 4 import { type CommonOptions, S_BAR, S_BAR_END, S_PASSWORD_MASK, symbol } from './common.js'; 4 5 6 + /** 7 + * Options for the {@link password} prompt 8 + */ 5 9 export interface PasswordOptions extends CommonOptions { 10 + /** 11 + * The prompt message or question shown to the user above the input. 12 + */ 6 13 message: string; 14 + 15 + /** 16 + * Character to use for masking input. 17 + * @default ▪/• 18 + */ 7 19 mask?: string; 8 - validate?: (value: string | undefined) => string | Error | undefined; 20 + 21 + /** 22 + * A function or a [Standard Schema](https://github.com/standard-schema/standard-schema) 23 + * that validates user input. If a custom function is given, you should return a `string` or `Error` 24 + * to show as a validation error, or `undefined` to accept the result. 25 + */ 26 + validate?: Validate<string>; 27 + 28 + /** 29 + * When enabled it causes the input to be cleared if/when validation fails. 30 + * @default false 31 + */ 9 32 clearOnError?: boolean; 10 33 } 34 + 35 + /** 36 + * The password prompt behaves like the {@link text} prompt, but the input is masked. 37 + * 38 + * @see https://bomb.sh/docs/clack/packages/prompts/#password-input 39 + * 40 + * @example 41 + * ```ts 42 + * import { password } from '@clack/prompts'; 43 + * 44 + * const result = await password({ 45 + * message: 'Enter your password', 46 + * }); 47 + * ``` 48 + */ 11 49 export const password = (opts: PasswordOptions) => { 12 50 return new PasswordPrompt({ 13 51 validate: opts.validate,
+50 -3
packages/prompts/src/path.ts
··· 1 1 import { existsSync, lstatSync, readdirSync } from 'node:fs'; 2 2 import { dirname, join } from 'node:path'; 3 + import type { Validate } from '@clack/core'; 4 + import { runValidation } from '@clack/core'; 3 5 import { autocomplete } from './autocomplete.js'; 4 6 import type { CommonOptions } from './common.js'; 5 7 8 + /** 9 + * Options for the {@link path} prompt. 10 + */ 6 11 export interface PathOptions extends CommonOptions { 12 + /** 13 + * The message or question shown to the user above the input. 14 + */ 15 + message: string; 16 + 17 + /** 18 + * The starting directory for path suggestions (defaults to current working directory). 19 + */ 7 20 root?: string; 21 + 22 + /** 23 + * When `true` only **directories** appear in suggestions while you navigate. 24 + */ 8 25 directory?: boolean; 26 + 27 + /** 28 + * The starting path shown when the prompt first renders, which users can edit 29 + * before submitting. If not provided it will fall back to the given `root`, 30 + * or the current working directory. 31 + * 32 + * In `directory` mode, if the initial value points to a directory that exists, 33 + * pressing enter will submit the input instead of jumping to the first child. 34 + */ 9 35 initialValue?: string; 10 - message: string; 11 - validate?: (value: string | undefined) => string | Error | undefined; 36 + 37 + /** 38 + * A function or a [Standard Schema](https://github.com/standard-schema/standard-schema) 39 + * that validates user input. If a custom function is given, you should return a `string` or `Error` 40 + * to show as a validation error, or `undefined` to accept the result. 41 + */ 42 + validate?: Validate<string>; 12 43 } 13 44 45 + /** 46 + * The `path` prompt extends `autocomplete` to provide file and directory suggestions. 47 + * 48 + * @see https://bomb.sh/docs/clack/packages/prompts/#path-selection 49 + * 50 + * @example 51 + * ```ts 52 + * import { path } from '@clack/prompts'; 53 + * 54 + * const result = await path({ 55 + * message: 'Select a file:', 56 + * root: process.cwd(), 57 + * directory: false, 58 + * }); 59 + * ``` 60 + */ 14 61 export const path = (opts: PathOptions) => { 15 62 const validate = opts.validate; 16 63 ··· 27 74 return 'Please select a path'; 28 75 } 29 76 if (validate) { 30 - return validate(value); 77 + return runValidation(validate, value); 31 78 } 32 79 return undefined; 33 80 },
+46 -1
packages/prompts/src/text.ts
··· 1 1 import { styleText } from 'node:util'; 2 + import type { Validate } from '@clack/core'; 2 3 import { settings, TextPrompt } from '@clack/core'; 3 4 import { type CommonOptions, S_BAR, S_BAR_END, symbol } from './common.js'; 4 5 6 + /** 7 + * Options for the {@link text} prompt 8 + */ 5 9 export interface TextOptions extends CommonOptions { 10 + /** 11 + * The prompt message or question shown to the user above the input. 12 + */ 6 13 message: string; 14 + 15 + /** 16 + * A visual hint shown when the field has no content. 17 + */ 7 18 placeholder?: string; 19 + 20 + /** 21 + * A fallback value returned when the user provides nothing (empty input). 22 + */ 8 23 defaultValue?: string; 24 + 25 + /** 26 + * The starting value shown when the prompt first renders. 27 + * Users can edit this value before submitting. 28 + */ 9 29 initialValue?: string; 10 - validate?: (value: string | undefined) => string | Error | undefined; 30 + 31 + /** 32 + * A function or a [Standard Schema](https://github.com/standard-schema/standard-schema) 33 + * that validates user input. If a custom function is given, you should return a `string` or `Error` 34 + * to show as a validation error, or `undefined` to accept the result. 35 + */ 36 + validate?: Validate<string>; 11 37 } 12 38 39 + /** 40 + * The text prompt accepts a single line of text. 41 + * 42 + * @see https://bomb.sh/docs/clack/packages/prompts/#text-input 43 + * 44 + * @example 45 + * ```ts 46 + * import { text } from '@clack/prompts'; 47 + * 48 + * const name = await text({ 49 + * message: 'What is your name?', 50 + * placeholder: 'John Doe', 51 + * validate: (value) => { 52 + * if (!value || value.length < 2) return 'Name must be at least 2 characters'; 53 + * return undefined; 54 + * }, 55 + * }); 56 + * ``` 57 + */ 13 58 export const text = (opts: TextOptions) => { 14 59 return new TextPrompt({ 15 60 validate: opts.validate,
+658 -44
packages/prompts/test/__snapshots__/group-multi-select.test.ts.snap
··· 252 252 [ 253 253 "<cursor.hide>", 254 254 "◆ foo 255 - ◻ group1 256 - │ ◻ group1value0 257 - └ ◻ group1value1 255 + ◻ group1 256 + │ ◻ group1value0 257 + └ ◻ group1value1 258 258 259 259 ", 260 260 "<cursor.backward count=999><cursor.up count=5>", 261 261 "<cursor.down count=1>", 262 262 "<erase.down>", 263 - " ◻ group1 264 - │ ◻ group1value0 265 - └ ◻ group1value1 263 + "◻ group1 264 + │ ◻ group1value0 265 + └ ◻ group1value1 266 266 267 267 ", 268 268 "<cursor.backward count=999><cursor.up count=5>", 269 269 "<cursor.down count=2>", 270 270 "<erase.line><cursor.left count=1>", 271 - " │ ◼ group1value0", 271 + "│ ◼ group1value0", 272 272 "<cursor.down count=3>", 273 273 "<cursor.backward count=999><cursor.up count=5>", 274 274 "<erase.down>", ··· 325 325 "<cursor.hide>", 326 326 "│ 327 327 ◆ foo 328 - │ 329 - │ 328 + │ 329 + │ 330 330 │ ◻ group1 331 331 │ └ ◻ group1value0 332 - │ 333 - │ 332 + │ 333 + │ 334 334 │ ◻ group2 335 335 │ └ ◻ group2value0 336 336 └ ··· 340 340 "<erase.down>", 341 341 "│ ◻ group1 342 342 │ └ ◻ group1value0 343 - │ 344 - │ 343 + │ 344 + │ 345 345 │ ◻ group2 346 346 │ └ ◻ group2value0 347 347 └ ··· 351 351 "<erase.down>", 352 352 "│ ◼ group1 353 353 │ └ ◼ group1value0 354 - │ 355 - │ 354 + │ 355 + │ 356 356 │ ◻ group2 357 357 │ └ ◻ group2value0 358 358 └ ··· 389 389 ] 390 390 `; 391 391 392 + exports[`groupMultiselect (isCI = false) > maxItems renders a sliding window 1`] = ` 393 + [ 394 + "<cursor.hide>", 395 + "│ 396 + ◆ foo 397 + │ ◻ group1 398 + │ │ ◻ group1value0 399 + │ │ ◻ group1value1 400 + │ │ ◻ group1value2 401 + │ │ ◻ group1value3 402 + │ ... 403 + └ 404 + ", 405 + "<cursor.backward count=999><cursor.up count=9>", 406 + "<cursor.down count=2>", 407 + "<erase.down>", 408 + "│ ◻ group1 409 + │ │ ◻ group1value0 410 + │ │ ◻ group1value1 411 + │ │ ◻ group1value2 412 + │ │ ◻ group1value3 413 + │ ... 414 + └ 415 + ", 416 + "<cursor.backward count=999><cursor.up count=9>", 417 + "<cursor.down count=3>", 418 + "<erase.down>", 419 + "│ │ ◻ group1value0 420 + │ │ ◻ group1value1 421 + │ │ ◻ group1value2 422 + │ │ ◻ group1value3 423 + │ ... 424 + └ 425 + ", 426 + "<cursor.backward count=999><cursor.up count=9>", 427 + "<cursor.down count=4>", 428 + "<erase.down>", 429 + "│ │ ◻ group1value1 430 + │ │ ◻ group1value2 431 + │ │ ◻ group1value3 432 + │ ... 433 + └ 434 + ", 435 + "<cursor.backward count=999><cursor.up count=9>", 436 + "<cursor.down count=2>", 437 + "<erase.down>", 438 + "│ ... 439 + │ │ ◻ group1value1 440 + │ │ ◻ group1value2 441 + │ │ ◻ group1value3 442 + │ │ ◻ group1value4 443 + │ ... 444 + └ 445 + ", 446 + "<cursor.backward count=999><cursor.up count=9>", 447 + "<cursor.down count=3>", 448 + "<erase.down>", 449 + "│ │ ◻ group1value2 450 + │ │ ◻ group1value3 451 + │ │ ◻ group1value4 452 + │ └ ◻ group1value5 453 + │ ... 454 + └ 455 + ", 456 + "<cursor.backward count=999><cursor.up count=9>", 457 + "<cursor.down count=3>", 458 + "<erase.down>", 459 + "│ │ ◻ group1value3 460 + │ │ ◻ group1value4 461 + │ └ ◻ group1value5 462 + │ ◻ group2 463 + │ ... 464 + └ 465 + ", 466 + "<cursor.backward count=999><cursor.up count=9>", 467 + "<cursor.down count=5>", 468 + "<erase.line><cursor.left count=1>", 469 + "│ └ ◼ group1value5", 470 + "<cursor.down count=4>", 471 + "<cursor.backward count=999><cursor.up count=9>", 472 + "<cursor.down count=1>", 473 + "<erase.down>", 474 + "◇ foo 475 + │ group1value5", 476 + " 477 + ", 478 + "<cursor.show>", 479 + ] 480 + `; 481 + 392 482 exports[`groupMultiselect (isCI = false) > renders error when nothing selected 1`] = ` 393 483 [ 394 484 "<cursor.hide>", ··· 536 626 ] 537 627 `; 538 628 629 + exports[`groupMultiselect (isCI = false) > sliding window loops downwards 1`] = ` 630 + [ 631 + "<cursor.hide>", 632 + "│ 633 + ◆ foo 634 + │ ◻ group1 635 + │ │ ◻ group1value0 636 + │ │ ◻ group1value1 637 + │ │ ◻ group1value2 638 + │ │ ◻ group1value3 639 + │ ... 640 + └ 641 + ", 642 + "<cursor.backward count=999><cursor.up count=9>", 643 + "<cursor.down count=2>", 644 + "<erase.down>", 645 + "│ ◻ group1 646 + │ │ ◻ group1value0 647 + │ │ ◻ group1value1 648 + │ │ ◻ group1value2 649 + │ │ ◻ group1value3 650 + │ ... 651 + └ 652 + ", 653 + "<cursor.backward count=999><cursor.up count=9>", 654 + "<cursor.down count=3>", 655 + "<erase.down>", 656 + "│ │ ◻ group1value0 657 + │ │ ◻ group1value1 658 + │ │ ◻ group1value2 659 + │ │ ◻ group1value3 660 + │ ... 661 + └ 662 + ", 663 + "<cursor.backward count=999><cursor.up count=9>", 664 + "<cursor.down count=4>", 665 + "<erase.down>", 666 + "│ │ ◻ group1value1 667 + │ │ ◻ group1value2 668 + │ │ ◻ group1value3 669 + │ ... 670 + └ 671 + ", 672 + "<cursor.backward count=999><cursor.up count=9>", 673 + "<cursor.down count=2>", 674 + "<erase.down>", 675 + "│ ... 676 + │ │ ◻ group1value1 677 + │ │ ◻ group1value2 678 + │ │ ◻ group1value3 679 + │ │ ◻ group1value4 680 + │ ... 681 + └ 682 + ", 683 + "<cursor.backward count=999><cursor.up count=9>", 684 + "<cursor.down count=3>", 685 + "<erase.down>", 686 + "│ │ ◻ group1value2 687 + │ │ ◻ group1value3 688 + │ │ ◻ group1value4 689 + │ └ ◻ group1value5 690 + │ ... 691 + └ 692 + ", 693 + "<cursor.backward count=999><cursor.up count=9>", 694 + "<cursor.down count=3>", 695 + "<erase.down>", 696 + "│ │ ◻ group1value3 697 + │ │ ◻ group1value4 698 + │ └ ◻ group1value5 699 + │ ◻ group2 700 + │ ... 701 + └ 702 + ", 703 + "<cursor.backward count=999><cursor.up count=9>", 704 + "<cursor.down count=3>", 705 + "<erase.down>", 706 + "│ │ ◻ group1value4 707 + │ └ ◻ group1value5 708 + │ ◻ group2 709 + │ │ ◻ group2value0 710 + │ ... 711 + └ 712 + ", 713 + "<cursor.backward count=999><cursor.up count=9>", 714 + "<cursor.down count=3>", 715 + "<erase.down>", 716 + "│ └ ◻ group1value5 717 + │ ◻ group2 718 + │ │ ◻ group2value0 719 + │ │ ◻ group2value1 720 + │ ... 721 + └ 722 + ", 723 + "<cursor.backward count=999><cursor.up count=9>", 724 + "<cursor.down count=3>", 725 + "<erase.down>", 726 + "│ ◻ group2 727 + │ │ ◻ group2value0 728 + │ │ ◻ group2value1 729 + │ │ ◻ group2value2 730 + │ ... 731 + └ 732 + ", 733 + "<cursor.backward count=999><cursor.up count=9>", 734 + "<cursor.down count=3>", 735 + "<erase.down>", 736 + "│ │ ◻ group2value0 737 + │ │ ◻ group2value1 738 + │ │ ◻ group2value2 739 + │ │ ◻ group2value3 740 + │ ... 741 + └ 742 + ", 743 + "<cursor.backward count=999><cursor.up count=9>", 744 + "<cursor.down count=3>", 745 + "<erase.down>", 746 + "│ │ ◻ group2value1 747 + │ │ ◻ group2value2 748 + │ │ ◻ group2value3 749 + │ │ ◻ group2value4 750 + │ └ ◻ group2value5 751 + └ 752 + ", 753 + "<cursor.backward count=999><cursor.up count=9>", 754 + "<cursor.down count=5>", 755 + "<erase.down>", 756 + "│ │ ◻ group2value3 757 + │ │ ◻ group2value4 758 + │ └ ◻ group2value5 759 + └ 760 + ", 761 + "<cursor.backward count=999><cursor.up count=9>", 762 + "<cursor.down count=6>", 763 + "<erase.down>", 764 + "│ │ ◻ group2value4 765 + │ └ ◻ group2value5 766 + └ 767 + ", 768 + "<cursor.backward count=999><cursor.up count=9>", 769 + "<cursor.down count=2>", 770 + "<erase.down>", 771 + "│ ◻ group1 772 + │ │ ◻ group1value0 773 + │ │ ◻ group1value1 774 + │ │ ◻ group1value2 775 + │ │ ◻ group1value3 776 + │ ... 777 + └ 778 + ", 779 + "<cursor.backward count=999><cursor.up count=9>", 780 + "<cursor.down count=2>", 781 + "<erase.down>", 782 + "│ ◻ group1 783 + │ │ ◻ group1value0 784 + │ │ ◻ group1value1 785 + │ │ ◻ group1value2 786 + │ │ ◻ group1value3 787 + │ ... 788 + └ 789 + ", 790 + "<cursor.backward count=999><cursor.up count=9>", 791 + "<cursor.down count=3>", 792 + "<erase.line><cursor.left count=1>", 793 + "│ │ ◼ group1value0", 794 + "<cursor.down count=6>", 795 + "<cursor.backward count=999><cursor.up count=9>", 796 + "<cursor.down count=1>", 797 + "<erase.down>", 798 + "◇ foo 799 + │ group1value0", 800 + " 801 + ", 802 + "<cursor.show>", 803 + ] 804 + `; 805 + 806 + exports[`groupMultiselect (isCI = false) > sliding window loops upwards 1`] = ` 807 + [ 808 + "<cursor.hide>", 809 + "│ 810 + ◆ foo 811 + │ ◻ group1 812 + │ │ ◻ group1value0 813 + │ │ ◻ group1value1 814 + │ │ ◻ group1value2 815 + │ │ ◻ group1value3 816 + │ ... 817 + └ 818 + ", 819 + "<cursor.backward count=999><cursor.up count=9>", 820 + "<cursor.down count=2>", 821 + "<erase.down>", 822 + "│ ... 823 + │ │ ◻ group2value1 824 + │ │ ◻ group2value2 825 + │ │ ◻ group2value3 826 + │ │ ◻ group2value4 827 + │ └ ◻ group2value5 828 + └ 829 + ", 830 + "<cursor.backward count=999><cursor.up count=9>", 831 + "<cursor.down count=7>", 832 + "<erase.line><cursor.left count=1>", 833 + "│ └ ◼ group2value5", 834 + "<cursor.down count=2>", 835 + "<cursor.backward count=999><cursor.up count=9>", 836 + "<cursor.down count=1>", 837 + "<erase.down>", 838 + "◇ foo 839 + │ group2value5", 840 + " 841 + ", 842 + "<cursor.show>", 843 + ] 844 + `; 845 + 539 846 exports[`groupMultiselect (isCI = false) > values can be non-primitive 1`] = ` 540 847 [ 541 848 "<cursor.hide>", ··· 574 881 [ 575 882 "<cursor.hide>", 576 883 "◆ foo 577 - ◻ group1 578 - │ ◻ group1value0 579 - └ ◻ group1value1 884 + ◻ group1 885 + │ ◻ group1value0 886 + └ ◻ group1value1 580 887 581 888 ", 582 889 "<cursor.backward count=999><cursor.up count=5>", 583 890 "<cursor.down count=1>", 584 891 "<erase.down>", 585 - " ◻ group1 586 - │ ◻ group1value0 587 - └ ◻ group1value1 892 + "◻ group1 893 + │ ◻ group1value0 894 + └ ◻ group1value1 588 895 589 896 ", 590 897 "<cursor.backward count=999><cursor.up count=5>", 591 898 "<cursor.down count=2>", 592 899 "<erase.line><cursor.left count=1>", 593 - " │ ◼ group1value0", 900 + "│ ◼ group1value0", 594 901 "<cursor.down count=3>", 595 902 "<cursor.backward count=999><cursor.up count=5>", 596 903 "<erase.down>", ··· 854 1161 [ 855 1162 "<cursor.hide>", 856 1163 "◆ foo 857 - ◻ group1 858 - │ ◻ group1value0 859 - └ ◻ group1value1 1164 + ◻ group1 1165 + │ ◻ group1value0 1166 + └ ◻ group1value1 860 1167 861 1168 ", 862 1169 "<cursor.backward count=999><cursor.up count=5>", 863 1170 "<cursor.down count=1>", 864 1171 "<erase.down>", 865 - " ◻ group1 866 - │ ◻ group1value0 867 - └ ◻ group1value1 1172 + "◻ group1 1173 + │ ◻ group1value0 1174 + └ ◻ group1value1 868 1175 869 1176 ", 870 1177 "<cursor.backward count=999><cursor.up count=5>", 871 1178 "<cursor.down count=2>", 872 1179 "<erase.line><cursor.left count=1>", 873 - " │ ◼ group1value0", 1180 + "│ ◼ group1value0", 874 1181 "<cursor.down count=3>", 875 1182 "<cursor.backward count=999><cursor.up count=5>", 876 1183 "<erase.down>", ··· 927 1234 "<cursor.hide>", 928 1235 "│ 929 1236 ◆ foo 930 - │ 931 - │ 1237 + │ 1238 + │ 932 1239 │ ◻ group1 933 1240 │ └ ◻ group1value0 934 - │ 935 - │ 1241 + │ 1242 + │ 936 1243 │ ◻ group2 937 1244 │ └ ◻ group2value0 938 1245 └ ··· 942 1249 "<erase.down>", 943 1250 "│ ◻ group1 944 1251 │ └ ◻ group1value0 945 - │ 946 - │ 1252 + │ 1253 + │ 947 1254 │ ◻ group2 948 1255 │ └ ◻ group2value0 949 1256 └ ··· 953 1260 "<erase.down>", 954 1261 "│ ◼ group1 955 1262 │ └ ◼ group1value0 956 - │ 957 - │ 1263 + │ 1264 + │ 958 1265 │ ◻ group2 959 1266 │ └ ◻ group2value0 960 1267 └ ··· 991 1298 ] 992 1299 `; 993 1300 1301 + exports[`groupMultiselect (isCI = true) > maxItems renders a sliding window 1`] = ` 1302 + [ 1303 + "<cursor.hide>", 1304 + "│ 1305 + ◆ foo 1306 + │ ◻ group1 1307 + │ │ ◻ group1value0 1308 + │ │ ◻ group1value1 1309 + │ │ ◻ group1value2 1310 + │ │ ◻ group1value3 1311 + │ ... 1312 + └ 1313 + ", 1314 + "<cursor.backward count=999><cursor.up count=9>", 1315 + "<cursor.down count=2>", 1316 + "<erase.down>", 1317 + "│ ◻ group1 1318 + │ │ ◻ group1value0 1319 + │ │ ◻ group1value1 1320 + │ │ ◻ group1value2 1321 + │ │ ◻ group1value3 1322 + │ ... 1323 + └ 1324 + ", 1325 + "<cursor.backward count=999><cursor.up count=9>", 1326 + "<cursor.down count=3>", 1327 + "<erase.down>", 1328 + "│ │ ◻ group1value0 1329 + │ │ ◻ group1value1 1330 + │ │ ◻ group1value2 1331 + │ │ ◻ group1value3 1332 + │ ... 1333 + └ 1334 + ", 1335 + "<cursor.backward count=999><cursor.up count=9>", 1336 + "<cursor.down count=4>", 1337 + "<erase.down>", 1338 + "│ │ ◻ group1value1 1339 + │ │ ◻ group1value2 1340 + │ │ ◻ group1value3 1341 + │ ... 1342 + └ 1343 + ", 1344 + "<cursor.backward count=999><cursor.up count=9>", 1345 + "<cursor.down count=2>", 1346 + "<erase.down>", 1347 + "│ ... 1348 + │ │ ◻ group1value1 1349 + │ │ ◻ group1value2 1350 + │ │ ◻ group1value3 1351 + │ │ ◻ group1value4 1352 + │ ... 1353 + └ 1354 + ", 1355 + "<cursor.backward count=999><cursor.up count=9>", 1356 + "<cursor.down count=3>", 1357 + "<erase.down>", 1358 + "│ │ ◻ group1value2 1359 + │ │ ◻ group1value3 1360 + │ │ ◻ group1value4 1361 + │ └ ◻ group1value5 1362 + │ ... 1363 + └ 1364 + ", 1365 + "<cursor.backward count=999><cursor.up count=9>", 1366 + "<cursor.down count=3>", 1367 + "<erase.down>", 1368 + "│ │ ◻ group1value3 1369 + │ │ ◻ group1value4 1370 + │ └ ◻ group1value5 1371 + │ ◻ group2 1372 + │ ... 1373 + └ 1374 + ", 1375 + "<cursor.backward count=999><cursor.up count=9>", 1376 + "<cursor.down count=5>", 1377 + "<erase.line><cursor.left count=1>", 1378 + "│ └ ◼ group1value5", 1379 + "<cursor.down count=4>", 1380 + "<cursor.backward count=999><cursor.up count=9>", 1381 + "<cursor.down count=1>", 1382 + "<erase.down>", 1383 + "◇ foo 1384 + │ group1value5", 1385 + " 1386 + ", 1387 + "<cursor.show>", 1388 + ] 1389 + `; 1390 + 994 1391 exports[`groupMultiselect (isCI = true) > renders error when nothing selected 1`] = ` 995 1392 [ 996 1393 "<cursor.hide>", ··· 1138 1535 ] 1139 1536 `; 1140 1537 1538 + exports[`groupMultiselect (isCI = true) > sliding window loops downwards 1`] = ` 1539 + [ 1540 + "<cursor.hide>", 1541 + "│ 1542 + ◆ foo 1543 + │ ◻ group1 1544 + │ │ ◻ group1value0 1545 + │ │ ◻ group1value1 1546 + │ │ ◻ group1value2 1547 + │ │ ◻ group1value3 1548 + │ ... 1549 + └ 1550 + ", 1551 + "<cursor.backward count=999><cursor.up count=9>", 1552 + "<cursor.down count=2>", 1553 + "<erase.down>", 1554 + "│ ◻ group1 1555 + │ │ ◻ group1value0 1556 + │ │ ◻ group1value1 1557 + │ │ ◻ group1value2 1558 + │ │ ◻ group1value3 1559 + │ ... 1560 + └ 1561 + ", 1562 + "<cursor.backward count=999><cursor.up count=9>", 1563 + "<cursor.down count=3>", 1564 + "<erase.down>", 1565 + "│ │ ◻ group1value0 1566 + │ │ ◻ group1value1 1567 + │ │ ◻ group1value2 1568 + │ │ ◻ group1value3 1569 + │ ... 1570 + └ 1571 + ", 1572 + "<cursor.backward count=999><cursor.up count=9>", 1573 + "<cursor.down count=4>", 1574 + "<erase.down>", 1575 + "│ │ ◻ group1value1 1576 + │ │ ◻ group1value2 1577 + │ │ ◻ group1value3 1578 + │ ... 1579 + └ 1580 + ", 1581 + "<cursor.backward count=999><cursor.up count=9>", 1582 + "<cursor.down count=2>", 1583 + "<erase.down>", 1584 + "│ ... 1585 + │ │ ◻ group1value1 1586 + │ │ ◻ group1value2 1587 + │ │ ◻ group1value3 1588 + │ │ ◻ group1value4 1589 + │ ... 1590 + └ 1591 + ", 1592 + "<cursor.backward count=999><cursor.up count=9>", 1593 + "<cursor.down count=3>", 1594 + "<erase.down>", 1595 + "│ │ ◻ group1value2 1596 + │ │ ◻ group1value3 1597 + │ │ ◻ group1value4 1598 + │ └ ◻ group1value5 1599 + │ ... 1600 + └ 1601 + ", 1602 + "<cursor.backward count=999><cursor.up count=9>", 1603 + "<cursor.down count=3>", 1604 + "<erase.down>", 1605 + "│ │ ◻ group1value3 1606 + │ │ ◻ group1value4 1607 + │ └ ◻ group1value5 1608 + │ ◻ group2 1609 + │ ... 1610 + └ 1611 + ", 1612 + "<cursor.backward count=999><cursor.up count=9>", 1613 + "<cursor.down count=3>", 1614 + "<erase.down>", 1615 + "│ │ ◻ group1value4 1616 + │ └ ◻ group1value5 1617 + │ ◻ group2 1618 + │ │ ◻ group2value0 1619 + │ ... 1620 + └ 1621 + ", 1622 + "<cursor.backward count=999><cursor.up count=9>", 1623 + "<cursor.down count=3>", 1624 + "<erase.down>", 1625 + "│ └ ◻ group1value5 1626 + │ ◻ group2 1627 + │ │ ◻ group2value0 1628 + │ │ ◻ group2value1 1629 + │ ... 1630 + └ 1631 + ", 1632 + "<cursor.backward count=999><cursor.up count=9>", 1633 + "<cursor.down count=3>", 1634 + "<erase.down>", 1635 + "│ ◻ group2 1636 + │ │ ◻ group2value0 1637 + │ │ ◻ group2value1 1638 + │ │ ◻ group2value2 1639 + │ ... 1640 + └ 1641 + ", 1642 + "<cursor.backward count=999><cursor.up count=9>", 1643 + "<cursor.down count=3>", 1644 + "<erase.down>", 1645 + "│ │ ◻ group2value0 1646 + │ │ ◻ group2value1 1647 + │ │ ◻ group2value2 1648 + │ │ ◻ group2value3 1649 + │ ... 1650 + └ 1651 + ", 1652 + "<cursor.backward count=999><cursor.up count=9>", 1653 + "<cursor.down count=3>", 1654 + "<erase.down>", 1655 + "│ │ ◻ group2value1 1656 + │ │ ◻ group2value2 1657 + │ │ ◻ group2value3 1658 + │ │ ◻ group2value4 1659 + │ └ ◻ group2value5 1660 + └ 1661 + ", 1662 + "<cursor.backward count=999><cursor.up count=9>", 1663 + "<cursor.down count=5>", 1664 + "<erase.down>", 1665 + "│ │ ◻ group2value3 1666 + │ │ ◻ group2value4 1667 + │ └ ◻ group2value5 1668 + └ 1669 + ", 1670 + "<cursor.backward count=999><cursor.up count=9>", 1671 + "<cursor.down count=6>", 1672 + "<erase.down>", 1673 + "│ │ ◻ group2value4 1674 + │ └ ◻ group2value5 1675 + └ 1676 + ", 1677 + "<cursor.backward count=999><cursor.up count=9>", 1678 + "<cursor.down count=2>", 1679 + "<erase.down>", 1680 + "│ ◻ group1 1681 + │ │ ◻ group1value0 1682 + │ │ ◻ group1value1 1683 + │ │ ◻ group1value2 1684 + │ │ ◻ group1value3 1685 + │ ... 1686 + └ 1687 + ", 1688 + "<cursor.backward count=999><cursor.up count=9>", 1689 + "<cursor.down count=2>", 1690 + "<erase.down>", 1691 + "│ ◻ group1 1692 + │ │ ◻ group1value0 1693 + │ │ ◻ group1value1 1694 + │ │ ◻ group1value2 1695 + │ │ ◻ group1value3 1696 + │ ... 1697 + └ 1698 + ", 1699 + "<cursor.backward count=999><cursor.up count=9>", 1700 + "<cursor.down count=3>", 1701 + "<erase.line><cursor.left count=1>", 1702 + "│ │ ◼ group1value0", 1703 + "<cursor.down count=6>", 1704 + "<cursor.backward count=999><cursor.up count=9>", 1705 + "<cursor.down count=1>", 1706 + "<erase.down>", 1707 + "◇ foo 1708 + │ group1value0", 1709 + " 1710 + ", 1711 + "<cursor.show>", 1712 + ] 1713 + `; 1714 + 1715 + exports[`groupMultiselect (isCI = true) > sliding window loops upwards 1`] = ` 1716 + [ 1717 + "<cursor.hide>", 1718 + "│ 1719 + ◆ foo 1720 + │ ◻ group1 1721 + │ │ ◻ group1value0 1722 + │ │ ◻ group1value1 1723 + │ │ ◻ group1value2 1724 + │ │ ◻ group1value3 1725 + │ ... 1726 + └ 1727 + ", 1728 + "<cursor.backward count=999><cursor.up count=9>", 1729 + "<cursor.down count=2>", 1730 + "<erase.down>", 1731 + "│ ... 1732 + │ │ ◻ group2value1 1733 + │ │ ◻ group2value2 1734 + │ │ ◻ group2value3 1735 + │ │ ◻ group2value4 1736 + │ └ ◻ group2value5 1737 + └ 1738 + ", 1739 + "<cursor.backward count=999><cursor.up count=9>", 1740 + "<cursor.down count=7>", 1741 + "<erase.line><cursor.left count=1>", 1742 + "│ └ ◼ group2value5", 1743 + "<cursor.down count=2>", 1744 + "<cursor.backward count=999><cursor.up count=9>", 1745 + "<cursor.down count=1>", 1746 + "<erase.down>", 1747 + "◇ foo 1748 + │ group2value5", 1749 + " 1750 + ", 1751 + "<cursor.show>", 1752 + ] 1753 + `; 1754 + 1141 1755 exports[`groupMultiselect (isCI = true) > values can be non-primitive 1`] = ` 1142 1756 [ 1143 1757 "<cursor.hide>", ··· 1176 1790 [ 1177 1791 "<cursor.hide>", 1178 1792 "◆ foo 1179 - ◻ group1 1180 - │ ◻ group1value0 1181 - └ ◻ group1value1 1793 + ◻ group1 1794 + │ ◻ group1value0 1795 + └ ◻ group1value1 1182 1796 1183 1797 ", 1184 1798 "<cursor.backward count=999><cursor.up count=5>", 1185 1799 "<cursor.down count=1>", 1186 1800 "<erase.down>", 1187 - " ◻ group1 1188 - │ ◻ group1value0 1189 - └ ◻ group1value1 1801 + "◻ group1 1802 + │ ◻ group1value0 1803 + └ ◻ group1value1 1190 1804 1191 1805 ", 1192 1806 "<cursor.backward count=999><cursor.up count=5>", 1193 1807 "<cursor.down count=2>", 1194 1808 "<erase.line><cursor.left count=1>", 1195 - " │ ◼ group1value0", 1809 + "│ ◼ group1value0", 1196 1810 "<cursor.down count=3>", 1197 1811 "<cursor.backward count=999><cursor.up count=5>", 1198 1812 "<erase.down>",
+2 -4
packages/prompts/test/__snapshots__/select.test.ts.snap
··· 220 220 "│ 221 221 ◆ Whatever 222 222 │ ... 223 - │ ○ Option 0 224 223 │ ○ Option 1 225 224 │ ○ Option 2 226 225 │ ● Option 3 227 226 └ 228 227 ", 229 - "<cursor.backward count=999><cursor.up count=8>", 228 + "<cursor.backward count=999><cursor.up count=7>", 230 229 "<cursor.down count=1>", 231 230 "<erase.down>", 232 231 "◇ Whatever ··· 700 699 "│ 701 700 ◆ Whatever 702 701 │ ... 703 - │ ○ Option 0 704 702 │ ○ Option 1 705 703 │ ○ Option 2 706 704 │ ● Option 3 707 705 └ 708 706 ", 709 - "<cursor.backward count=999><cursor.up count=8>", 707 + "<cursor.backward count=999><cursor.up count=7>", 710 708 "<cursor.down count=1>", 711 709 "<erase.down>", 712 710 "◇ Whatever
+70
packages/prompts/test/group-multi-select.test.ts
··· 306 306 expect(output.buffer).toMatchSnapshot(); 307 307 }); 308 308 309 + test('maxItems renders a sliding window', async () => { 310 + const result = prompts.groupMultiselect({ 311 + message: 'foo', 312 + input, 313 + output, 314 + options: { 315 + group1: [...Array(6).keys()].map((k) => ({ value: `group1value${k}` })), 316 + group2: [...Array(6).keys()].map((k) => ({ value: `group2value${k}` })), 317 + }, 318 + maxItems: 6, 319 + }); 320 + 321 + for (let i = 0; i < 6; i++) { 322 + input.emit('keypress', '', { name: 'down' }); 323 + } 324 + input.emit('keypress', '', { name: 'space' }); 325 + input.emit('keypress', '', { name: 'return' }); 326 + 327 + const value = await result; 328 + 329 + expect(value).toEqual(['group1value5']); 330 + expect(output.buffer).toMatchSnapshot(); 331 + }); 332 + 333 + test('sliding window loops upwards', async () => { 334 + const result = prompts.groupMultiselect({ 335 + message: 'foo', 336 + input, 337 + output, 338 + options: { 339 + group1: [...Array(6).keys()].map((k) => ({ value: `group1value${k}` })), 340 + group2: [...Array(6).keys()].map((k) => ({ value: `group2value${k}` })), 341 + }, 342 + maxItems: 6, 343 + }); 344 + 345 + input.emit('keypress', '', { name: 'up' }); 346 + input.emit('keypress', '', { name: 'space' }); 347 + input.emit('keypress', '', { name: 'return' }); 348 + 349 + const value = await result; 350 + 351 + expect(value).toEqual(['group2value5']); 352 + expect(output.buffer).toMatchSnapshot(); 353 + }); 354 + 355 + test('sliding window loops downwards', async () => { 356 + const result = prompts.groupMultiselect({ 357 + message: 'foo', 358 + input, 359 + output, 360 + options: { 361 + group1: [...Array(6).keys()].map((k) => ({ value: `group1value${k}` })), 362 + group2: [...Array(6).keys()].map((k) => ({ value: `group2value${k}` })), 363 + }, 364 + maxItems: 6, 365 + }); 366 + 367 + for (let i = 0; i < 15; i++) { 368 + input.emit('keypress', '', { name: 'down' }); 369 + } 370 + input.emit('keypress', '', { name: 'space' }); 371 + input.emit('keypress', '', { name: 'return' }); 372 + 373 + const value = await result; 374 + 375 + expect(value).toEqual(['group1value0']); 376 + expect(output.buffer).toMatchSnapshot(); 377 + }); 378 + 309 379 describe('groupSpacing', () => { 310 380 test('renders spaced groups', async () => { 311 381 const result = prompts.groupMultiselect({
-6
packages/prompts/test/limit-options.test.ts
··· 142 142 'Item 4', 143 143 'Item 5', 144 144 'Item 6', 145 - 'Item 7', 146 - 'Item 8', 147 145 styleText('dim', '...'), 148 146 ]); 149 147 }); ··· 171 169 const result = limitOptions(options); 172 170 expect(result).toEqual([ 173 171 styleText('dim', '...'), 174 - 'Item 2', 175 - 'Item 3', 176 172 'Item 4', 177 173 'A long item that will take up a lot of space (line 0)', 178 174 'A long item that will take up a lot of space (line 1)', ··· 208 204 const result = limitOptions(options); 209 205 expect(result).toEqual([ 210 206 styleText('dim', '...'), 211 - 'Item 4', 212 - 'Item 5', 213 207 'Item 6', 214 208 'Item 7', 215 209 'A long item that will take up a lot of space (line 0)',
+2 -2
packages/prompts/test/select-key.test.ts
··· 114 114 output, 115 115 }); 116 116 117 - input.emit('keypress', 'a', { name: 'a', shift: true }); 117 + input.emit('keypress', 'A', { name: 'a', shift: true }); 118 118 119 119 const value = await result; 120 120 ··· 156 156 output, 157 157 }); 158 158 159 - input.emit('keypress', 'a', { name: 'a', shift: true }); 159 + input.emit('keypress', 'A', { name: 'a', shift: true }); 160 160 161 161 const value = await result; 162 162
+191 -96
pnpm-lock.yaml
··· 11 11 '@biomejs/biome': 12 12 specifier: ^2.1.2 13 13 version: 2.1.2 14 + '@changesets/changelog-github': 15 + specifier: ^0.7.0 16 + version: 0.7.0 14 17 '@changesets/cli': 15 - specifier: ^2.29.5 16 - version: 2.29.5 18 + specifier: ^2.31.0 19 + version: 2.31.0(@types/node@20.19.39) 17 20 '@types/node': 18 21 specifier: ^20.19.39 19 22 version: 20.19.39 ··· 22 25 version: 0.13.4 23 26 knip: 24 27 specifier: ^5.62.0 25 - version: 5.62.0(@types/node@20.19.39)(typescript@5.8.3) 28 + version: 5.62.0(@types/node@20.19.39)(typescript@6.0.3) 26 29 typescript: 27 - specifier: ^5.8.3 28 - version: 5.8.3 30 + specifier: ^6.0.3 31 + version: 6.0.3 29 32 unbuild: 30 33 specifier: ^3.6.0 31 - version: 3.6.0(typescript@5.8.3) 34 + version: 3.6.0(typescript@6.0.3) 32 35 33 36 examples/basic: 34 37 dependencies: 35 38 '@clack/prompts': 36 39 specifier: workspace:* 37 40 version: link:../../packages/prompts 41 + arktype: 42 + specifier: ^2.2.0 43 + version: 2.2.0 38 44 jiti: 39 45 specifier: ^1.17.0 40 46 version: 1.21.7 ··· 67 73 specifier: ^1.0.5 68 74 version: 1.0.5 69 75 devDependencies: 76 + arktype: 77 + specifier: ^2.2.0 78 + version: 2.2.0 70 79 vitest: 71 80 specifier: ^3.2.4 72 81 version: 3.2.4(@types/node@20.19.39)(jiti@2.5.0) ··· 100 109 version: 0.1.2(vitest@3.2.4(@types/node@20.19.39)(jiti@2.5.0)) 101 110 102 111 packages: 112 + 113 + '@ark/schema@0.56.0': 114 + resolution: {integrity: sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA==} 115 + 116 + '@ark/util@0.56.0': 117 + resolution: {integrity: sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA==} 103 118 104 119 '@babel/code-frame@7.26.2': 105 120 resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} ··· 170 185 cpu: [x64] 171 186 os: [win32] 172 187 173 - '@changesets/apply-release-plan@7.0.12': 174 - resolution: {integrity: sha512-EaET7As5CeuhTzvXTQCRZeBUcisoYPDDcXvgTE/2jmmypKp0RC7LxKj/yzqeh/1qFTZI7oDGFcL1PHRuQuketQ==} 188 + '@changesets/apply-release-plan@7.1.1': 189 + resolution: {integrity: sha512-9qPCm/rLx/xoOFXIHGB229+4GOL76S4MC+7tyOuTsR6+1jYlfFDQORdvwR5hDA6y4FL2BPt3qpbcQIS+dW85LA==} 175 190 176 - '@changesets/assemble-release-plan@6.0.9': 177 - resolution: {integrity: sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==} 191 + '@changesets/assemble-release-plan@6.0.10': 192 + resolution: {integrity: sha512-rSDcqdJ9KbVyjpBIuCidhvZNIiVt1XaIYp73ycVQRIA5n/j6wQaEk0ChRLMUQ1vkxZe51PTQ9OIhbg6HQMW45A==} 178 193 179 194 '@changesets/changelog-git@0.2.1': 180 195 resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} 181 196 182 - '@changesets/cli@2.29.5': 183 - resolution: {integrity: sha512-0j0cPq3fgxt2dPdFsg4XvO+6L66RC0pZybT9F4dG5TBrLA3jA/1pNkdTXH9IBBVHkgsKrNKenI3n1mPyPlIydg==} 197 + '@changesets/changelog-github@0.7.0': 198 + resolution: {integrity: sha512-rBsbRvc4TVn+FvFnOVM3LxlFJfTXXCp8gfVJ+0BubxWNSVnLuAzowi5j+IEraLLP52w8AAs9QfKbPS3MMiXQJA==} 199 + 200 + '@changesets/cli@2.31.0': 201 + resolution: {integrity: sha512-AhI4enNTgHu2IZr6K4WZyf0EPch4XVMn1yOMFmCD9gsfBGqMYaHXls5HyDv6/CL5axVQABz68eG30eCtbr2wFg==} 184 202 hasBin: true 185 203 186 - '@changesets/config@3.1.1': 187 - resolution: {integrity: sha512-bd+3Ap2TKXxljCggI0mKPfzCQKeV/TU4yO2h2C6vAihIo8tzseAn2e7klSuiyYYXvgu53zMN1OeYMIQkaQoWnA==} 204 + '@changesets/config@3.1.4': 205 + resolution: {integrity: sha512-pf0bvD/v6WI2cRlZ6hzpjtZdSlXDXMAJ+Iz7xfFzV4ZxJ8OGGAON+1qYc99ZPrijnt4xp3VGG7eNvAOGS24V1Q==} 188 206 189 207 '@changesets/errors@0.2.0': 190 208 resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} 191 209 192 - '@changesets/get-dependents-graph@2.1.3': 193 - resolution: {integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==} 210 + '@changesets/get-dependents-graph@2.1.4': 211 + resolution: {integrity: sha512-ZsS00x6WvmHq3sQv8oCMwL0f/z3wbXCVuSVTJwCnnmbC/iBdNJGFx1EcbMG4PC6sXRyH69liM4A2WKXzn/kRPg==} 212 + 213 + '@changesets/get-github-info@0.8.0': 214 + resolution: {integrity: sha512-cRnC+xdF0JIik7coko3iUP9qbnfi1iJQ3sAa6dE+Tx3+ET8bjFEm63PA4WEohgjYcmsOikPHWzPsMWWiZmntOQ==} 194 215 195 - '@changesets/get-release-plan@4.0.13': 196 - resolution: {integrity: sha512-DWG1pus72FcNeXkM12tx+xtExyH/c9I1z+2aXlObH3i9YA7+WZEVaiHzHl03thpvAgWTRaH64MpfHxozfF7Dvg==} 216 + '@changesets/get-release-plan@4.0.16': 217 + resolution: {integrity: sha512-2K5Om6CrMPm45rtvckfzWo7e9jOVCKLCnXia5eUPaURH7/LWzri7pK1TycdzAuAtehLkW7VPbWLCSExTHmiI6g==} 197 218 198 219 '@changesets/get-version-range-type@0.4.0': 199 220 resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} ··· 204 225 '@changesets/logger@0.1.1': 205 226 resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} 206 227 207 - '@changesets/parse@0.4.1': 208 - resolution: {integrity: sha512-iwksMs5Bf/wUItfcg+OXrEpravm5rEd9Bf4oyIPL4kVTmJQ7PNDSd6MDYkpSJR1pn7tz/k8Zf2DhTCqX08Ou+Q==} 228 + '@changesets/parse@0.4.3': 229 + resolution: {integrity: sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A==} 209 230 210 231 '@changesets/pre@2.0.2': 211 232 resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==} 212 233 213 - '@changesets/read@0.6.5': 214 - resolution: {integrity: sha512-UPzNGhsSjHD3Veb0xO/MwvasGe8eMyNrR/sT9gR8Q3DhOQZirgKhhXv/8hVsI0QpPjR004Z9iFxoJU6in3uGMg==} 234 + '@changesets/read@0.6.7': 235 + resolution: {integrity: sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA==} 215 236 216 237 '@changesets/should-skip-package@0.1.2': 217 238 resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==} ··· 390 411 cpu: [x64] 391 412 os: [win32] 392 413 414 + '@inquirer/external-editor@1.0.3': 415 + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} 416 + engines: {node: '>=18'} 417 + peerDependencies: 418 + '@types/node': '>=18' 419 + peerDependenciesMeta: 420 + '@types/node': 421 + optional: true 422 + 393 423 '@jridgewell/sourcemap-codec@1.5.0': 394 424 resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 395 425 ··· 773 803 argparse@2.0.1: 774 804 resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 775 805 806 + arkregex@0.0.5: 807 + resolution: {integrity: sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw==} 808 + 809 + arktype@2.2.0: 810 + resolution: {integrity: sha512-t54MZ7ti5BhOEvzEkgKnWvqj+UbDfWig+DHr5I34xatymPusKLS0lQpNJd8M6DzmIto2QGszHfNKoFIT8tMCZQ==} 811 + 776 812 array-union@2.1.0: 777 813 resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 778 814 engines: {node: '>=8'} ··· 818 854 resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} 819 855 engines: {node: '>=12'} 820 856 821 - chardet@0.7.0: 822 - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} 857 + chardet@2.1.1: 858 + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} 823 859 824 860 check-error@2.1.1: 825 861 resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} 826 862 engines: {node: '>= 16'} 827 - 828 - ci-info@3.9.0: 829 - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} 830 - engines: {node: '>=8'} 831 863 832 864 citty@0.1.6: 833 865 resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} ··· 909 941 resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} 910 942 engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} 911 943 944 + dataloader@1.4.0: 945 + resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} 946 + 912 947 debug@4.4.1: 913 948 resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} 914 949 engines: {node: '>=6.0'} ··· 949 984 950 985 domutils@3.1.0: 951 986 resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} 987 + 988 + dotenv@8.6.0: 989 + resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} 990 + engines: {node: '>=10'} 952 991 953 992 electron-to-chromium@1.5.190: 954 993 resolution: {integrity: sha512-k4McmnB2091YIsdCgkS0fMVMPOJgxl93ltFzaryXqwip1AaxeDqKCGLxkXODDA5Ab/D+tV5EL5+aTx76RvLRxw==} ··· 993 1032 994 1033 extendable-error@0.1.7: 995 1034 resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} 996 - 997 - external-editor@3.1.0: 998 - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} 999 - engines: {node: '>=4'} 1000 1035 1001 1036 fast-glob@3.3.3: 1002 1037 resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} ··· 1086 1121 resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} 1087 1122 engines: {node: '>=10.18'} 1088 1123 1089 - iconv-lite@0.4.24: 1090 - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} 1124 + iconv-lite@0.7.2: 1125 + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} 1091 1126 engines: {node: '>=0.10.0'} 1092 1127 1093 1128 ignore@5.3.2: ··· 1151 1186 1152 1187 js-yaml@4.1.0: 1153 1188 resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 1189 + hasBin: true 1190 + 1191 + js-yaml@4.1.1: 1192 + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} 1154 1193 hasBin: true 1155 1194 1156 1195 jsonfile@4.0.0: ··· 1259 1298 engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} 1260 1299 hasBin: true 1261 1300 1301 + node-fetch@2.7.0: 1302 + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} 1303 + engines: {node: 4.x || >=6.0.0} 1304 + peerDependencies: 1305 + encoding: ^0.1.0 1306 + peerDependenciesMeta: 1307 + encoding: 1308 + optional: true 1309 + 1262 1310 node-releases@2.0.19: 1263 1311 resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} 1264 1312 ··· 1272 1320 1273 1321 nth-check@2.1.1: 1274 1322 resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} 1275 - 1276 - os-tmpdir@1.0.2: 1277 - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} 1278 - engines: {node: '>=0.10.0'} 1279 1323 1280 1324 outdent@0.5.0: 1281 1325 resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} ··· 1697 1741 resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} 1698 1742 engines: {node: '>=14.0.0'} 1699 1743 1700 - tmp@0.0.33: 1701 - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} 1702 - engines: {node: '>=0.6.0'} 1703 - 1704 1744 to-regex-range@5.0.1: 1705 1745 resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1706 1746 engines: {node: '>=8.0'} 1747 + 1748 + tr46@0.0.3: 1749 + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} 1707 1750 1708 1751 tree-dump@1.0.2: 1709 1752 resolution: {integrity: sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==} ··· 1714 1757 tslib@2.8.1: 1715 1758 resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 1716 1759 1717 - typescript@5.8.3: 1718 - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} 1760 + typescript@6.0.3: 1761 + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} 1719 1762 engines: {node: '>=14.17'} 1720 1763 hasBin: true 1721 1764 ··· 1833 1876 resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} 1834 1877 engines: {node: 20 || >=22} 1835 1878 1879 + webidl-conversions@3.0.1: 1880 + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} 1881 + 1882 + whatwg-url@5.0.0: 1883 + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} 1884 + 1836 1885 which@2.0.2: 1837 1886 resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1838 1887 engines: {node: '>= 8'} ··· 1853 1902 resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} 1854 1903 1855 1904 snapshots: 1905 + 1906 + '@ark/schema@0.56.0': 1907 + dependencies: 1908 + '@ark/util': 0.56.0 1909 + 1910 + '@ark/util@0.56.0': {} 1856 1911 1857 1912 '@babel/code-frame@7.26.2': 1858 1913 dependencies: ··· 1901 1956 '@biomejs/cli-win32-x64@2.1.2': 1902 1957 optional: true 1903 1958 1904 - '@changesets/apply-release-plan@7.0.12': 1959 + '@changesets/apply-release-plan@7.1.1': 1905 1960 dependencies: 1906 - '@changesets/config': 3.1.1 1961 + '@changesets/config': 3.1.4 1907 1962 '@changesets/get-version-range-type': 0.4.0 1908 1963 '@changesets/git': 3.0.4 1909 1964 '@changesets/should-skip-package': 0.1.2 ··· 1917 1972 resolve-from: 5.0.0 1918 1973 semver: 7.7.2 1919 1974 1920 - '@changesets/assemble-release-plan@6.0.9': 1975 + '@changesets/assemble-release-plan@6.0.10': 1921 1976 dependencies: 1922 1977 '@changesets/errors': 0.2.0 1923 - '@changesets/get-dependents-graph': 2.1.3 1978 + '@changesets/get-dependents-graph': 2.1.4 1924 1979 '@changesets/should-skip-package': 0.1.2 1925 1980 '@changesets/types': 6.1.0 1926 1981 '@manypkg/get-packages': 1.1.3 ··· 1930 1985 dependencies: 1931 1986 '@changesets/types': 6.1.0 1932 1987 1933 - '@changesets/cli@2.29.5': 1988 + '@changesets/changelog-github@0.7.0': 1934 1989 dependencies: 1935 - '@changesets/apply-release-plan': 7.0.12 1936 - '@changesets/assemble-release-plan': 6.0.9 1990 + '@changesets/get-github-info': 0.8.0 1991 + '@changesets/types': 6.1.0 1992 + dotenv: 8.6.0 1993 + transitivePeerDependencies: 1994 + - encoding 1995 + 1996 + '@changesets/cli@2.31.0(@types/node@20.19.39)': 1997 + dependencies: 1998 + '@changesets/apply-release-plan': 7.1.1 1999 + '@changesets/assemble-release-plan': 6.0.10 1937 2000 '@changesets/changelog-git': 0.2.1 1938 - '@changesets/config': 3.1.1 2001 + '@changesets/config': 3.1.4 1939 2002 '@changesets/errors': 0.2.0 1940 - '@changesets/get-dependents-graph': 2.1.3 1941 - '@changesets/get-release-plan': 4.0.13 2003 + '@changesets/get-dependents-graph': 2.1.4 2004 + '@changesets/get-release-plan': 4.0.16 1942 2005 '@changesets/git': 3.0.4 1943 2006 '@changesets/logger': 0.1.1 1944 2007 '@changesets/pre': 2.0.2 1945 - '@changesets/read': 0.6.5 2008 + '@changesets/read': 0.6.7 1946 2009 '@changesets/should-skip-package': 0.1.2 1947 2010 '@changesets/types': 6.1.0 1948 2011 '@changesets/write': 0.4.0 2012 + '@inquirer/external-editor': 1.0.3(@types/node@20.19.39) 1949 2013 '@manypkg/get-packages': 1.1.3 1950 2014 ansi-colors: 4.1.3 1951 - ci-info: 3.9.0 1952 2015 enquirer: 2.4.1 1953 - external-editor: 3.1.0 1954 2016 fs-extra: 7.0.1 1955 2017 mri: 1.2.0 1956 - p-limit: 2.3.0 1957 2018 package-manager-detector: 0.2.11 1958 2019 picocolors: 1.1.1 1959 2020 resolve-from: 5.0.0 1960 2021 semver: 7.7.2 1961 2022 spawndamnit: 3.0.1 1962 2023 term-size: 2.2.1 2024 + transitivePeerDependencies: 2025 + - '@types/node' 1963 2026 1964 - '@changesets/config@3.1.1': 2027 + '@changesets/config@3.1.4': 1965 2028 dependencies: 1966 2029 '@changesets/errors': 0.2.0 1967 - '@changesets/get-dependents-graph': 2.1.3 2030 + '@changesets/get-dependents-graph': 2.1.4 1968 2031 '@changesets/logger': 0.1.1 2032 + '@changesets/should-skip-package': 0.1.2 1969 2033 '@changesets/types': 6.1.0 1970 2034 '@manypkg/get-packages': 1.1.3 1971 2035 fs-extra: 7.0.1 ··· 1975 2039 dependencies: 1976 2040 extendable-error: 0.1.7 1977 2041 1978 - '@changesets/get-dependents-graph@2.1.3': 2042 + '@changesets/get-dependents-graph@2.1.4': 1979 2043 dependencies: 1980 2044 '@changesets/types': 6.1.0 1981 2045 '@manypkg/get-packages': 1.1.3 1982 2046 picocolors: 1.1.1 1983 2047 semver: 7.7.2 1984 2048 1985 - '@changesets/get-release-plan@4.0.13': 2049 + '@changesets/get-github-info@0.8.0': 2050 + dependencies: 2051 + dataloader: 1.4.0 2052 + node-fetch: 2.7.0 2053 + transitivePeerDependencies: 2054 + - encoding 2055 + 2056 + '@changesets/get-release-plan@4.0.16': 1986 2057 dependencies: 1987 - '@changesets/assemble-release-plan': 6.0.9 1988 - '@changesets/config': 3.1.1 2058 + '@changesets/assemble-release-plan': 6.0.10 2059 + '@changesets/config': 3.1.4 1989 2060 '@changesets/pre': 2.0.2 1990 - '@changesets/read': 0.6.5 2061 + '@changesets/read': 0.6.7 1991 2062 '@changesets/types': 6.1.0 1992 2063 '@manypkg/get-packages': 1.1.3 1993 2064 ··· 2005 2076 dependencies: 2006 2077 picocolors: 1.1.1 2007 2078 2008 - '@changesets/parse@0.4.1': 2079 + '@changesets/parse@0.4.3': 2009 2080 dependencies: 2010 2081 '@changesets/types': 6.1.0 2011 - js-yaml: 3.14.1 2082 + js-yaml: 4.1.1 2012 2083 2013 2084 '@changesets/pre@2.0.2': 2014 2085 dependencies: ··· 2017 2088 '@manypkg/get-packages': 1.1.3 2018 2089 fs-extra: 7.0.1 2019 2090 2020 - '@changesets/read@0.6.5': 2091 + '@changesets/read@0.6.7': 2021 2092 dependencies: 2022 2093 '@changesets/git': 3.0.4 2023 2094 '@changesets/logger': 0.1.1 2024 - '@changesets/parse': 0.4.1 2095 + '@changesets/parse': 0.4.3 2025 2096 '@changesets/types': 6.1.0 2026 2097 fs-extra: 7.0.1 2027 2098 p-filter: 2.1.0 ··· 2136 2207 2137 2208 '@esbuild/win32-x64@0.25.8': 2138 2209 optional: true 2210 + 2211 + '@inquirer/external-editor@1.0.3(@types/node@20.19.39)': 2212 + dependencies: 2213 + chardet: 2.1.1 2214 + iconv-lite: 0.7.2 2215 + optionalDependencies: 2216 + '@types/node': 20.19.39 2139 2217 2140 2218 '@jridgewell/sourcemap-codec@1.5.0': {} 2141 2219 ··· 2433 2511 2434 2512 argparse@2.0.1: {} 2435 2513 2514 + arkregex@0.0.5: 2515 + dependencies: 2516 + '@ark/util': 0.56.0 2517 + 2518 + arktype@2.2.0: 2519 + dependencies: 2520 + '@ark/schema': 0.56.0 2521 + '@ark/util': 0.56.0 2522 + arkregex: 0.0.5 2523 + 2436 2524 array-union@2.1.0: {} 2437 2525 2438 2526 assertion-error@2.0.1: {} ··· 2483 2571 loupe: 3.2.0 2484 2572 pathval: 2.0.0 2485 2573 2486 - chardet@0.7.0: {} 2574 + chardet@2.1.1: {} 2487 2575 2488 2576 check-error@2.1.1: {} 2489 - 2490 - ci-info@3.9.0: {} 2491 2577 2492 2578 citty@0.1.6: 2493 2579 dependencies: ··· 2589 2675 dependencies: 2590 2676 css-tree: 2.2.1 2591 2677 2678 + dataloader@1.4.0: {} 2679 + 2592 2680 debug@4.4.1: 2593 2681 dependencies: 2594 2682 ms: 2.1.3 ··· 2622 2710 dom-serializer: 2.0.0 2623 2711 domelementtype: 2.3.0 2624 2712 domhandler: 5.0.3 2713 + 2714 + dotenv@8.6.0: {} 2625 2715 2626 2716 electron-to-chromium@1.5.190: {} 2627 2717 ··· 2679 2769 2680 2770 extendable-error@0.1.7: {} 2681 2771 2682 - external-editor@3.1.0: 2683 - dependencies: 2684 - chardet: 0.7.0 2685 - iconv-lite: 0.4.24 2686 - tmp: 0.0.33 2687 - 2688 2772 fast-glob@3.3.3: 2689 2773 dependencies: 2690 2774 '@nodelib/fs.stat': 2.0.5 ··· 2778 2862 2779 2863 hyperdyperid@1.2.0: {} 2780 2864 2781 - iconv-lite@0.4.24: 2865 + iconv-lite@0.7.2: 2782 2866 dependencies: 2783 2867 safer-buffer: 2.1.2 2784 2868 ··· 2830 2914 dependencies: 2831 2915 argparse: 2.0.1 2832 2916 2917 + js-yaml@4.1.1: 2918 + dependencies: 2919 + argparse: 2.0.1 2920 + 2833 2921 jsonfile@4.0.0: 2834 2922 optionalDependencies: 2835 2923 graceful-fs: 4.2.11 ··· 2840 2928 node-stream-zip: 1.15.0 2841 2929 semiver: 1.1.0 2842 2930 2843 - knip@5.62.0(@types/node@20.19.39)(typescript@5.8.3): 2931 + knip@5.62.0(@types/node@20.19.39)(typescript@6.0.3): 2844 2932 dependencies: 2845 2933 '@nodelib/fs.walk': 1.2.8 2846 2934 '@types/node': 20.19.39 ··· 2854 2942 picomatch: 4.0.3 2855 2943 smol-toml: 1.4.1 2856 2944 strip-json-comments: 5.0.2 2857 - typescript: 5.8.3 2945 + typescript: 6.0.3 2858 2946 zod: 3.22.4 2859 2947 zod-validation-error: 3.4.0(zod@3.22.4) 2860 2948 ··· 2900 2988 2901 2989 minimist@1.2.8: {} 2902 2990 2903 - mkdist@2.3.0(typescript@5.8.3): 2991 + mkdist@2.3.0(typescript@6.0.3): 2904 2992 dependencies: 2905 2993 autoprefixer: 10.4.21(postcss@8.5.6) 2906 2994 citty: 0.1.6 ··· 2916 3004 semver: 7.7.2 2917 3005 tinyglobby: 0.2.14 2918 3006 optionalDependencies: 2919 - typescript: 5.8.3 3007 + typescript: 6.0.3 2920 3008 2921 3009 mlly@1.7.4: 2922 3010 dependencies: ··· 2933 3021 2934 3022 napi-postinstall@0.3.2: {} 2935 3023 3024 + node-fetch@2.7.0: 3025 + dependencies: 3026 + whatwg-url: 5.0.0 3027 + 2936 3028 node-releases@2.0.19: {} 2937 3029 2938 3030 node-stream-zip@1.15.0: {} ··· 2942 3034 nth-check@2.1.1: 2943 3035 dependencies: 2944 3036 boolbase: 1.0.0 2945 - 2946 - os-tmpdir@1.0.2: {} 2947 3037 2948 3038 outdent@0.5.0: {} 2949 3039 ··· 3220 3310 3221 3311 reusify@1.0.4: {} 3222 3312 3223 - rollup-plugin-dts@6.2.1(rollup@4.45.1)(typescript@5.8.3): 3313 + rollup-plugin-dts@6.2.1(rollup@4.45.1)(typescript@6.0.3): 3224 3314 dependencies: 3225 3315 magic-string: 0.30.17 3226 3316 rollup: 4.45.1 3227 - typescript: 5.8.3 3317 + typescript: 6.0.3 3228 3318 optionalDependencies: 3229 3319 '@babel/code-frame': 7.26.2 3230 3320 ··· 3346 3436 3347 3437 tinyspy@4.0.3: {} 3348 3438 3349 - tmp@0.0.33: 3350 - dependencies: 3351 - os-tmpdir: 1.0.2 3352 - 3353 3439 to-regex-range@5.0.1: 3354 3440 dependencies: 3355 3441 is-number: 7.0.0 3442 + 3443 + tr46@0.0.3: {} 3356 3444 3357 3445 tree-dump@1.0.2(tslib@2.8.1): 3358 3446 dependencies: ··· 3360 3448 3361 3449 tslib@2.8.1: {} 3362 3450 3363 - typescript@5.8.3: {} 3451 + typescript@6.0.3: {} 3364 3452 3365 3453 ufo@1.5.4: {} 3366 3454 3367 - unbuild@3.6.0(typescript@5.8.3): 3455 + unbuild@3.6.0(typescript@6.0.3): 3368 3456 dependencies: 3369 3457 '@rollup/plugin-alias': 5.1.1(rollup@4.45.1) 3370 3458 '@rollup/plugin-commonjs': 28.0.6(rollup@4.45.1) ··· 3380 3468 hookable: 5.5.3 3381 3469 jiti: 2.5.0 3382 3470 magic-string: 0.30.17 3383 - mkdist: 2.3.0(typescript@5.8.3) 3471 + mkdist: 2.3.0(typescript@6.0.3) 3384 3472 mlly: 1.7.4 3385 3473 pathe: 2.0.3 3386 3474 pkg-types: 2.2.0 3387 3475 pretty-bytes: 7.0.0 3388 3476 rollup: 4.45.1 3389 - rollup-plugin-dts: 6.2.1(rollup@4.45.1)(typescript@5.8.3) 3477 + rollup-plugin-dts: 6.2.1(rollup@4.45.1)(typescript@6.0.3) 3390 3478 scule: 1.3.0 3391 3479 tinyglobby: 0.2.14 3392 3480 untyped: 2.0.0 3393 3481 optionalDependencies: 3394 - typescript: 5.8.3 3482 + typescript: 6.0.3 3395 3483 transitivePeerDependencies: 3396 3484 - sass 3397 3485 - vue ··· 3498 3586 - yaml 3499 3587 3500 3588 walk-up-path@4.0.0: {} 3589 + 3590 + webidl-conversions@3.0.1: {} 3591 + 3592 + whatwg-url@5.0.0: 3593 + dependencies: 3594 + tr46: 0.0.3 3595 + webidl-conversions: 3.0.1 3501 3596 3502 3597 which@2.0.2: 3503 3598 dependencies:
+7
pnpm-workspace.yaml
··· 1 1 packages: 2 2 - 'examples/*' 3 3 - 'packages/*' 4 + allowBuilds: 5 + esbuild: true 6 + oxc-resolver: true 7 + autoInstallPeers: false 8 + linkWorkspacePackages: true 9 + preferWorkspacePackages: true 10 + saveWorkspaceProtocol: false
+2 -1
tsconfig.json
··· 17 17 "paths": { 18 18 "@clack/core": ["./packages/core/src/index.ts"], 19 19 "@clack/prompts": ["./packages/prompts/src/index.ts"] 20 - } 20 + }, 21 + "types": ["node"] 21 22 }, 22 23 "include": ["packages/*/src/**/*.ts", "packages/*/test/**/*.ts"] 23 24 }