[READ-ONLY] Mirror of https://github.com/bombshell-dev/docs. bomb.sh/docs
0

Configure Feed

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

Update core and prompts documentation and examples (#14)

* update: core, prompts and examples

* chore: update @clack/core and @clack/prompts dependencies; enhance best practices and examples documentation

* fix: restore @clack/core@0.4.2 entry in pnpm-lock.yaml and update prompts documentation formatting

* docs(prompts): Enhance groupMultiselect and spinner documentation; add taskLog and internationalization examples

---------

Signed-off-by: Paul Valladares <85648028+dreyfus92@users.noreply.github.com>

authored by

Paul Valladares and committed by
GitHub
(Jun 3, 2025, 10:24 AM -0600) e4b5f8fe c6d60fd5

+975 -156
+2 -2
package.json
··· 11 11 }, 12 12 "dependencies": { 13 13 "@astrojs/starlight": "^0.32.2", 14 + "@clack/core": "^0.4.2", 15 + "@clack/prompts": "1.0.0-alpha.0", 14 16 "@bomb.sh/args": "^0.3.1", 15 - "@clack/core": "^0.4.1", 16 - "@clack/prompts": "^0.10.0", 17 17 "@types/node": "^22.13.11", 18 18 "astro": "^5.1.5", 19 19 "expressive-code-twoslash": "^0.4.0",
+19 -11
pnpm-lock.yaml
··· 15 15 specifier: ^0.3.1 16 16 version: 0.3.1 17 17 '@clack/core': 18 - specifier: ^0.4.1 19 - version: 0.4.1 18 + specifier: ^0.4.2 19 + version: 0.4.2 20 20 '@clack/prompts': 21 - specifier: ^0.10.0 22 - version: 0.10.0 21 + specifier: 1.0.0-alpha.0 22 + version: 1.0.0-alpha.0 23 23 '@types/node': 24 24 specifier: ^22.13.11 25 25 version: 22.13.11 ··· 93 93 '@bomb.sh/args@0.3.1': 94 94 resolution: {integrity: sha512-CwxKrfgcorUPP6KfYD59aRdBYWBTsfsxT+GmoLVnKo5Tmyoqbpo0UNcjngRMyU+6tiPbd18RuIYxhgAn44wU/Q==} 95 95 96 - '@clack/core@0.4.1': 97 - resolution: {integrity: sha512-Pxhij4UXg8KSr7rPek6Zowm+5M22rbd2g1nfojHJkxp5YkFqiZ2+YLEM/XGVIzvGOcM0nqjIFxrpDwWRZYWYjA==} 96 + '@clack/core@0.4.2': 97 + resolution: {integrity: sha512-NYQfcEy8MWIxrT5Fj8nIVchfRFA26yYKJcvBS7WlUIlw2OmQOY9DhGGXMovyI5J5PpxrCPGkgUi207EBrjpBvg==} 98 + 99 + '@clack/core@1.0.0-alpha.0': 100 + resolution: {integrity: sha512-Cp/bPW/pMUCkJ7Lr8VFixvFrlnJ4tQPDHqfTNQ51z50qwX1fSIAstQLfel2NquVHqbfjyrUkBsal8OJRBPJVjw==} 98 101 99 - '@clack/prompts@0.10.0': 100 - resolution: {integrity: sha512-H3rCl6CwW1NdQt9rE3n373t7o5cthPv7yUoxF2ytZvyvlJv89C5RYMJu83Hed8ODgys5vpBU0GKxIRG83jd8NQ==} 102 + '@clack/prompts@1.0.0-alpha.0': 103 + resolution: {integrity: sha512-Aem7r4U2A4jdOh0PIv51Ugi+4vDgzJjGVMnuPUNVVHDGhFHEO//u6F/JY6NsZQFtXrd7ZmfePSiipikr/e5wWg==} 101 104 102 105 '@ctrl/tinycolor@4.1.0': 103 106 resolution: {integrity: sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==} ··· 1899 1902 1900 1903 '@bomb.sh/args@0.3.1': {} 1901 1904 1902 - '@clack/core@0.4.1': 1905 + '@clack/core@0.4.2': 1906 + dependencies: 1907 + picocolors: 1.1.1 1908 + sisteransi: 1.0.5 1909 + 1910 + '@clack/core@1.0.0-alpha.0': 1903 1911 dependencies: 1904 1912 picocolors: 1.1.1 1905 1913 sisteransi: 1.0.5 1906 1914 1907 - '@clack/prompts@0.10.0': 1915 + '@clack/prompts@1.0.0-alpha.0': 1908 1916 dependencies: 1909 - '@clack/core': 0.4.1 1917 + '@clack/core': 1.0.0-alpha.0 1910 1918 picocolors: 1.1.1 1911 1919 sisteransi: 1.0.5 1912 1920
+138 -24
src/content/docs/clack/guides/best-practices.mdx
··· 122 122 Provide sensible defaults when appropriate: 123 123 124 124 ```ts twoslash 125 - import { text } from '@clack/prompts'; 126 - 127 - const configPath = await text({ 128 - message: 'Configuration file path:', 129 - defaultValue: './config.json', 130 - placeholder: 'Enter path to config file', 131 - }); 132 - ``` 133 - 134 - ### 6. Type Safety 135 - 136 - Leverage TypeScript for better type safety: 137 - 138 - ```ts twoslash 139 - // @errors: 2322 2339 125 + // @errors: 2353 140 126 import { text, select, isCancel } from '@clack/prompts'; 141 127 142 - // Define validation functions outside 128 + // Define types for better type safety 129 + interface ProjectConfig { 130 + name: string; 131 + type: 'web' | 'cli'; 132 + framework?: 'react' | 'vue'; 133 + port?: number; 134 + } 135 + 136 + // Define validation functions 143 137 function validateProjectName(value: string): string | undefined { 144 138 if (value.length === 0) return 'Name is required'; 145 139 if (!/^[a-z0-9-]+$/.test(value)) return 'Name can only contain lowercase letters, numbers, and hyphens'; 146 140 return undefined; 147 141 } 148 142 149 - // Define types for better type safety 150 - interface ProjectConfig { 151 - name: string | symbol; 152 - type: 'web' | 'cli'; 153 - framework?: string; 154 - } 155 - 156 143 // Collect project configuration with type safety 157 144 async function collectProjectConfig(): Promise<ProjectConfig> { 158 145 const name = await text({ 159 146 message: 'Project name:', 160 147 validate: validateProjectName, 161 148 }); 149 + 150 + if (isCancel(name)) { 151 + console.log('Operation cancelled'); 152 + process.exit(0); 153 + } 162 154 163 155 const type = await select({ 164 156 message: 'Project type:', ··· 173 165 process.exit(0); 174 166 } 175 167 176 - return { 168 + const config: ProjectConfig = { 177 169 name, 178 170 type, 179 171 }; 172 + 173 + if (type === 'web') { 174 + const framework = await select({ 175 + message: 'Choose a framework:', 176 + options: [ 177 + { value: 'react', label: 'React' }, 178 + { value: 'vue', label: 'Vue' }, 179 + ], 180 + }); 181 + 182 + if (isCancel(framework)) { 183 + console.log('Operation cancelled'); 184 + process.exit(0); 185 + } 186 + 187 + config.framework = framework; 188 + } 189 + 190 + return config; 180 191 } 181 192 ``` 182 193 ··· 285 296 return undefined; 286 297 }, 287 298 }); 299 + ``` 300 + 301 + ## Advanced Patterns 302 + 303 + ### 1. Conditional Prompts 304 + 305 + Use conditional logic to show relevant prompts: 306 + 307 + ```ts twoslash 308 + // @errors: 2339 309 + import { text, select, isCancel } from '@clack/prompts'; 310 + 311 + interface DatabaseConfig { 312 + type: 'postgres' | 'mysql' | 'sqlite'; 313 + host?: string; 314 + port?: number; 315 + } 316 + 317 + async function setupDatabase() { 318 + const dbType = await select({ 319 + message: 'Select database type:', 320 + options: [ 321 + { value: 'postgres', label: 'PostgreSQL' }, 322 + { value: 'mysql', label: 'MySQL' }, 323 + { value: 'sqlite', label: 'SQLite' }, 324 + ], 325 + }); 326 + 327 + if (isCancel(dbType)) { 328 + console.log('Operation cancelled'); 329 + process.exit(0); 330 + } 331 + 332 + const config: DatabaseConfig = { 333 + type: dbType, 334 + }; 335 + 336 + // Only ask for host and port if not SQLite 337 + if (dbType !== 'sqlite') { 338 + const host = await text({ 339 + message: 'Database host:', 340 + defaultValue: 'localhost', 341 + }); 342 + 343 + if (isCancel(host)) { 344 + console.log('Operation cancelled'); 345 + process.exit(0); 346 + } 347 + 348 + const port = await text({ 349 + message: 'Database port:', 350 + defaultValue: dbType === 'postgres' ? '5432' : '3306', 351 + validate: (value) => { 352 + const num = parseInt(value); 353 + if (isNaN(num)) return 'Please enter a valid number'; 354 + if (num < 1 || num > 65535) return 'Port must be between 1 and 65535'; 355 + return undefined; 356 + }, 357 + }); 358 + 359 + if (isCancel(port)) { 360 + console.log('Operation cancelled'); 361 + process.exit(0); 362 + } 363 + 364 + config.host = host; 365 + config.port = parseInt(port); 366 + } 367 + 368 + return config; 369 + } 370 + ``` 371 + 372 + ### 2. Reusable Prompt Functions 373 + 374 + Create reusable functions for common prompt patterns: 375 + 376 + ```ts twoslash 377 + import { text, select, isCancel } from '@clack/prompts'; 378 + 379 + async function promptForPort(defaultPort: string) { 380 + const port = await text({ 381 + message: 'Enter port number:', 382 + defaultValue: defaultPort, 383 + validate: (value) => { 384 + const num = parseInt(value); 385 + if (isNaN(num)) return 'Please enter a valid number'; 386 + if (num < 1 || num > 65535) return 'Port must be between 1 and 65535'; 387 + return undefined; 388 + }, 389 + }); 390 + 391 + if (isCancel(port)) { 392 + console.log('Operation cancelled'); 393 + process.exit(0); 394 + } 395 + 396 + return parseInt(port); 397 + } 398 + 399 + // Use the reusable function 400 + const serverPort = await promptForPort('3000'); 401 + const dbPort = await promptForPort('5432'); 288 402 ```
+403 -42
src/content/docs/clack/guides/examples.mdx
··· 35 35 const framework = await select({ 36 36 message: 'Choose a framework:', 37 37 options: [ 38 - { value: 'react', label: 'React' }, 39 - { value: 'vue', label: 'Vue' }, 40 - { value: 'svelte', label: 'Svelte' }, 38 + { value: 'react', label: 'React', hint: 'A JavaScript library for building user interfaces' }, 39 + { value: 'vue', label: 'Vue', hint: 'The Progressive JavaScript Framework' }, 40 + { value: 'svelte', label: 'Svelte', hint: 'Cybernetically enhanced web apps' }, 41 41 ], 42 42 }); 43 43 ··· 49 49 console.log(`You selected ${framework}`); 50 50 ``` 51 51 52 + ### Autocomplete Selection 53 + 54 + ```ts twoslash 55 + import { autocomplete, isCancel } from '@clack/prompts'; 56 + 57 + const selectedPackage = await autocomplete({ 58 + message: 'Search for a package:', 59 + options: [ 60 + { value: 'react', label: 'React', hint: 'A JavaScript library for building user interfaces' }, 61 + { value: 'vue', label: 'Vue', hint: 'The Progressive JavaScript Framework' }, 62 + { value: 'svelte', label: 'Svelte', hint: 'Cybernetically enhanced web apps' }, 63 + { value: 'angular', label: 'Angular', hint: 'Platform for building mobile & desktop web apps' }, 64 + { value: 'next', label: 'Next.js', hint: 'The React Framework for Production' }, 65 + ], 66 + placeholder: 'Type to search...', 67 + maxItems: 5, 68 + }); 69 + 70 + if (isCancel(selectedPackage)) { 71 + console.log('Operation cancelled'); 72 + process.exit(0); 73 + } 74 + 75 + console.log(`Selected package: ${selectedPackage}`); 76 + ``` 77 + 78 + ### Multi-select with Groups 79 + 80 + ```ts twoslash 81 + import { groupMultiselect, isCancel } from '@clack/prompts'; 82 + 83 + const tools = await groupMultiselect({ 84 + message: 'Select development tools:', 85 + options: { 86 + 'Frontend': [ 87 + { value: 'typescript', label: 'TypeScript', hint: 'JavaScript with syntax for types' }, 88 + { value: 'eslint', label: 'ESLint', hint: 'Find and fix problems in your JavaScript code' }, 89 + { value: 'prettier', label: 'Prettier', hint: 'Code formatter' }, 90 + ], 91 + 'Backend': [ 92 + { value: 'node', label: 'Node.js', hint: 'JavaScript runtime' }, 93 + { value: 'express', label: 'Express', hint: 'Web framework for Node.js' }, 94 + { value: 'prisma', label: 'Prisma', hint: 'Next-generation ORM' }, 95 + ], 96 + 'Testing': [ 97 + { value: 'jest', label: 'Jest', hint: 'JavaScript testing framework' }, 98 + { value: 'cypress', label: 'Cypress', hint: 'End-to-end testing framework' }, 99 + { value: 'vitest', label: 'Vitest', hint: 'Vite-native testing framework' }, 100 + ], 101 + }, 102 + }); 103 + 104 + if (isCancel(tools)) { 105 + console.log('Operation cancelled'); 106 + process.exit(0); 107 + } 108 + 109 + console.log('Selected tools:', tools); 110 + ``` 111 + 52 112 ### Confirmation Dialog 53 113 54 114 ```ts twoslash 55 - import { confirm } from '@clack/prompts'; 115 + import { confirm, isCancel } from '@clack/prompts'; 56 116 57 117 const shouldProceed = await confirm({ 58 118 message: 'Do you want to continue?', 59 119 }); 60 120 121 + if (isCancel(shouldProceed)) { 122 + console.log('Operation cancelled'); 123 + process.exit(0); 124 + } 125 + 61 126 if (shouldProceed) { 62 127 console.log('Proceeding...'); 63 128 } else { ··· 70 135 ### Project Setup Wizard 71 136 72 137 ```ts twoslash 73 - import { text, select, confirm, isCancel } from '@clack/prompts'; 138 + import { text, select, confirm, groupMultiselect, isCancel } from '@clack/prompts'; 74 139 75 140 async function setupProject() { 76 141 // Get project details ··· 78 143 message: 'Project name:', 79 144 validate: (value) => { 80 145 if (value.length === 0) return 'Name is required'; 146 + if (!/^[a-z0-9-]+$/.test(value)) return 'Name can only contain lowercase letters, numbers, and hyphens'; 81 147 return undefined; 82 148 }, 83 149 }); ··· 90 156 const type = await select({ 91 157 message: 'Project type:', 92 158 options: [ 93 - { value: 'web', label: 'Web Application' }, 94 - { value: 'cli', label: 'CLI Tool' }, 95 - { value: 'api', label: 'API Server' }, 159 + { value: 'web', label: 'Web Application', hint: 'Full-stack web application' }, 160 + { value: 'cli', label: 'CLI Tool', hint: 'Command-line interface tool' }, 161 + { value: 'api', label: 'API Server', hint: 'REST/GraphQL API server' }, 96 162 ], 97 163 }); 98 164 ··· 107 173 framework = await select({ 108 174 message: 'Choose a framework:', 109 175 options: [ 110 - { value: 'react', label: 'React' }, 111 - { value: 'vue', label: 'Vue' }, 112 - { value: 'svelte', label: 'Svelte' }, 176 + { value: 'next', label: 'Next.js', hint: 'React framework for production' }, 177 + { value: 'nuxt', label: 'Nuxt', hint: 'Vue framework for production' }, 178 + { value: 'sveltekit', label: 'SvelteKit', hint: 'Svelte framework for production' }, 113 179 ], 114 180 }); 115 181 ··· 119 185 } 120 186 } 121 187 188 + // Select features 189 + const features = await groupMultiselect({ 190 + message: 'Select features:', 191 + options: { 192 + 'Development': [ 193 + { value: 'typescript', label: 'TypeScript', hint: 'Type safety' }, 194 + { value: 'eslint', label: 'ESLint', hint: 'Code linting' }, 195 + { value: 'prettier', label: 'Prettier', hint: 'Code formatting' }, 196 + ], 197 + 'Testing': [ 198 + { value: 'jest', label: 'Jest', hint: 'Unit testing' }, 199 + { value: 'cypress', label: 'Cypress', hint: 'E2E testing' }, 200 + ], 201 + 'Deployment': [ 202 + { value: 'docker', label: 'Docker', hint: 'Containerization' }, 203 + { value: 'ci', label: 'CI/CD', hint: 'Continuous integration' }, 204 + ], 205 + }, 206 + }); 207 + 208 + if (isCancel(features)) { 209 + console.log('Operation cancelled'); 210 + process.exit(0); 211 + } 212 + 122 213 // Confirm setup 123 214 const shouldProceed = await confirm({ 124 215 message: `Create ${type} project "${name}"${framework ? ` with ${framework}` : ''}?`, 125 216 }); 126 217 218 + if (isCancel(shouldProceed)) { 219 + console.log('Operation cancelled'); 220 + process.exit(0); 221 + } 222 + 127 223 if (shouldProceed) { 128 224 // Project creation logic here 129 225 console.log('Creating project...'); ··· 131 227 } 132 228 ``` 133 229 134 - ### Configuration Setup 230 + ### Configuration Setup with Validation 135 231 136 232 ```ts twoslash 137 - import { text, select, confirm, isCancel } from '@clack/prompts'; 233 + import { text, select, groupMultiselect, isCancel } from '@clack/prompts'; 138 234 139 235 interface Config { 140 236 port: number; 141 - host: string | symbol; 237 + host: string; 142 238 mode: 'development' | 'production'; 143 239 features: string[]; 240 + database: { 241 + type: 'postgres' | 'mysql' | 'mongodb'; 242 + url: string; 243 + }; 144 244 } 145 245 146 - async function setupConfig(): Promise<Config> { 246 + async function setupConfig(): Promise<Config | null> { 147 247 const port = await text({ 148 248 message: 'Enter port number:', 149 - defaultValue: '3000', 249 + placeholder: '3000', 150 250 validate: (value) => { 151 251 const num = parseInt(value); 152 252 if (isNaN(num)) return 'Please enter a valid number'; ··· 158 258 if (isCancel(port)) { 159 259 console.log('Operation cancelled'); 160 260 process.exit(0); 261 + return null; 161 262 } 162 263 163 264 const host = await text({ 164 265 message: 'Enter host:', 165 - defaultValue: 'localhost', 266 + placeholder: 'localhost', 267 + validate: (value) => { 268 + if (!value) return 'Host is required'; 269 + if (!/^[a-zA-Z0-9.-]+$/.test(value)) return 'Invalid host format'; 270 + return undefined; 271 + }, 166 272 }); 167 273 168 274 if (isCancel(host)) { 169 275 console.log('Operation cancelled'); 170 276 process.exit(0); 277 + return null; 171 278 } 172 279 173 280 const mode = await select({ 174 281 message: 'Select mode:', 175 282 options: [ 176 - { value: 'development', label: 'Development' }, 177 - { value: 'production', label: 'Production' }, 283 + { value: 'development', label: 'Development', hint: 'For local development' }, 284 + { value: 'production', label: 'Production', hint: 'For production deployment' }, 178 285 ], 179 286 }); 180 287 181 288 if (isCancel(mode)) { 182 289 console.log('Operation cancelled'); 183 290 process.exit(0); 291 + return null; 184 292 } 185 293 186 - const features = await select({ 187 - message: 'Select features:', 294 + const database = await select({ 295 + message: 'Select database:', 188 296 options: [ 189 - { value: 'auth', label: 'Authentication' }, 190 - { value: 'logging', label: 'Logging' }, 191 - { value: 'metrics', label: 'Metrics' }, 297 + { value: 'postgres', label: 'PostgreSQL', hint: 'Advanced open source database' }, 298 + { value: 'mysql', label: 'MySQL', hint: 'Most popular open source database' }, 299 + { value: 'mongodb', label: 'MongoDB', hint: 'Document-oriented database' }, 192 300 ], 193 301 }); 194 302 303 + if (isCancel(database)) { 304 + console.log('Operation cancelled'); 305 + process.exit(0); 306 + return null; 307 + } 308 + 309 + const dbUrl = await text({ 310 + message: 'Enter database URL:', 311 + placeholder: `postgresql://user:pass@localhost:5432/db`, 312 + validate: (value) => { 313 + if (!value) return 'Database URL is required'; 314 + try { 315 + new URL(value); 316 + return undefined; 317 + } catch { 318 + return 'Invalid URL format'; 319 + } 320 + }, 321 + }); 322 + 323 + if (isCancel(dbUrl)) { 324 + console.log('Operation cancelled'); 325 + process.exit(0); 326 + return null; 327 + } 328 + 329 + const features = await groupMultiselect({ 330 + message: 'Select features:', 331 + options: { 332 + 'Security': [ 333 + { value: 'auth', label: 'Authentication', hint: 'User authentication' }, 334 + { value: 'cors', label: 'CORS', hint: 'Cross-origin resource sharing' }, 335 + ], 336 + 'Monitoring': [ 337 + { value: 'logging', label: 'Logging', hint: 'Application logging' }, 338 + { value: 'metrics', label: 'Metrics', hint: 'Performance metrics' }, 339 + ], 340 + 'Development': [ 341 + { value: 'swagger', label: 'Swagger', hint: 'API documentation' }, 342 + { value: 'debug', label: 'Debug', hint: 'Debugging tools' }, 343 + ], 344 + }, 345 + }); 346 + 195 347 if (isCancel(features)) { 196 348 console.log('Operation cancelled'); 197 349 process.exit(0); 350 + return null; 198 351 } 199 352 200 353 return { 201 354 port: Number(port), 202 355 host, 203 356 mode: mode as 'development' | 'production', 204 - features: [features], 357 + features, 358 + database: { 359 + type: database as 'postgres' | 'mysql' | 'mongodb', 360 + url: dbUrl, 361 + }, 205 362 }; 206 363 } 207 364 ``` 208 365 209 - ### Interactive CLI Tool 366 + ### Interactive CLI Tool with Tasks 210 367 211 368 ```ts twoslash 212 - import { text, select, confirm as confirmPrompt, isCancel } from '@clack/prompts'; 369 + import { text, select, confirm, tasks, spinner, isCancel } from '@clack/prompts'; 213 370 214 371 async function cliTool() { 215 372 while (true) { 216 373 const action = await select({ 217 374 message: 'What would you like to do?', 218 375 options: [ 219 - { value: 'create', label: 'Create new item' }, 220 - { value: 'list', label: 'List items' }, 221 - { value: 'delete', label: 'Delete item' }, 222 - { value: 'exit', label: 'Exit' }, 376 + { value: 'create', label: 'Create new item', hint: 'Create a new resource' }, 377 + { value: 'list', label: 'List items', hint: 'View all resources' }, 378 + { value: 'delete', label: 'Delete item', hint: 'Remove a resource' }, 379 + { value: 'update', label: 'Update item', hint: 'Modify a resource' }, 380 + { value: 'exit', label: 'Exit', hint: 'Close the application' }, 223 381 ], 224 382 }); 225 383 ··· 234 392 case 'create': { 235 393 const name = await text({ 236 394 message: 'Enter item name:', 395 + validate: (value) => { 396 + if (!value) return 'Name is required'; 397 + return undefined; 398 + }, 237 399 }); 238 400 239 401 if (isCancel(name)) { 240 402 console.log('Operation cancelled'); 241 403 break; 242 404 } 405 + 406 + const spin = spinner(); 407 + spin.start('Creating item...'); 243 408 244 - console.log(`Created item: ${name}`); 409 + await tasks([ 410 + { 411 + title: 'Validating input', 412 + task: async () => { 413 + // Validation logic 414 + return 'Input validated'; 415 + }, 416 + }, 417 + { 418 + title: 'Creating resource', 419 + task: async () => { 420 + // Creation logic 421 + return 'Resource created'; 422 + }, 423 + }, 424 + { 425 + title: 'Setting up permissions', 426 + task: async () => { 427 + // Permission setup 428 + return 'Permissions configured'; 429 + }, 430 + }, 431 + ]); 432 + 433 + spin.stop('Item created successfully'); 245 434 break; 246 435 } 247 436 case 'list': { 437 + const spin = spinner(); 438 + spin.start('Fetching items...'); 439 + 440 + // Simulate API call 441 + await new Promise(resolve => setTimeout(resolve, 1000)); 442 + 443 + spin.stop('Items fetched successfully'); 248 444 console.log('Listing items...'); 249 445 break; 250 446 } 251 447 case 'delete': { 252 448 const item = await text({ 253 449 message: 'Enter item to delete:', 450 + validate: (value) => { 451 + if (!value) return 'Item name is required'; 452 + return undefined; 453 + }, 254 454 }); 255 455 256 456 if (isCancel(item)) { ··· 258 458 break; 259 459 } 260 460 261 - const shouldDelete = await confirmPrompt({ 262 - message: `Delete ${item}?`, 461 + const shouldDelete = await confirm({ 462 + message: `Are you sure you want to delete ${item}?`, 263 463 }); 264 464 465 + if (isCancel(shouldDelete)) { 466 + console.log('Operation cancelled'); 467 + break; 468 + } 469 + 265 470 if (shouldDelete) { 266 - console.log(`Deleted ${item}`); 471 + const spin = spinner(); 472 + spin.start('Deleting item...'); 473 + 474 + await tasks([ 475 + { 476 + title: 'Checking dependencies', 477 + task: async () => { 478 + // Check dependencies 479 + return 'Dependencies checked'; 480 + }, 481 + }, 482 + { 483 + title: 'Removing item', 484 + task: async () => { 485 + // Delete logic 486 + return 'Item removed'; 487 + }, 488 + }, 489 + { 490 + title: 'Cleaning up', 491 + task: async () => { 492 + // Cleanup logic 493 + return 'Cleanup completed'; 494 + }, 495 + }, 496 + ]); 497 + 498 + spin.stop('Item deleted successfully'); 267 499 } 268 500 break; 269 501 } 502 + case 'update': { 503 + const item = await text({ 504 + message: 'Enter item to update:', 505 + validate: (value) => { 506 + if (!value) return 'Item name is required'; 507 + return undefined; 508 + }, 509 + }); 510 + 511 + if (isCancel(item)) { 512 + console.log('Operation cancelled'); 513 + break; 514 + } 515 + 516 + const spin = spinner(); 517 + spin.start('Updating item...'); 518 + 519 + await tasks([ 520 + { 521 + title: 'Fetching current state', 522 + task: async () => { 523 + // Fetch current state 524 + return 'Current state fetched'; 525 + }, 526 + }, 527 + { 528 + title: 'Applying updates', 529 + task: async () => { 530 + // Update logic 531 + return 'Updates applied'; 532 + }, 533 + }, 534 + { 535 + title: 'Verifying changes', 536 + task: async () => { 537 + // Verification logic 538 + return 'Changes verified'; 539 + }, 540 + }, 541 + ]); 542 + 543 + spin.stop('Item updated successfully'); 544 + break; 545 + } 270 546 } 271 547 } 272 548 } 273 549 ``` 274 550 275 - ### Form Validation 551 + ### Form Validation with Complex Types 276 552 277 553 ```ts twoslash 278 - // @errors: 2731 2322 2345 279 - import { text, select, isCancel } from '@clack/prompts'; 554 + import { text, select, groupMultiselect, isCancel } from '@clack/prompts'; 280 555 281 556 interface UserData { 282 557 name: string; 283 558 email: string; 284 559 age: number; 285 560 role: string; 561 + skills: string[]; 562 + preferences: { 563 + theme: 'light' | 'dark' | 'system'; 564 + notifications: boolean; 565 + language: string; 566 + }; 286 567 } 287 568 288 569 async function collectUserData(): Promise<UserData | null> { ··· 335 616 const role = await select({ 336 617 message: 'Select role:', 337 618 options: [ 338 - { value: 'admin', label: 'Administrator' }, 339 - { value: 'user', label: 'User' }, 340 - { value: 'guest', label: 'Guest' }, 619 + { value: 'admin', label: 'Administrator', hint: 'Full system access' }, 620 + { value: 'user', label: 'User', hint: 'Standard user access' }, 621 + { value: 'guest', label: 'Guest', hint: 'Limited access' }, 341 622 ], 342 623 }); 343 624 ··· 347 628 return null; 348 629 } 349 630 631 + const skills = await groupMultiselect({ 632 + message: 'Select skills:', 633 + options: { 634 + 'Frontend': [ 635 + { value: 'react', label: 'React', hint: 'UI library' }, 636 + { value: 'vue', label: 'Vue', hint: 'Progressive framework' }, 637 + { value: 'angular', label: 'Angular', hint: 'Platform' }, 638 + ], 639 + 'Backend': [ 640 + { value: 'node', label: 'Node.js', hint: 'Runtime' }, 641 + { value: 'python', label: 'Python', hint: 'Language' }, 642 + { value: 'java', label: 'Java', hint: 'Language' }, 643 + ], 644 + 'Database': [ 645 + { value: 'sql', label: 'SQL', hint: 'Query language' }, 646 + { value: 'mongodb', label: 'MongoDB', hint: 'NoSQL database' }, 647 + { value: 'redis', label: 'Redis', hint: 'Cache' }, 648 + ], 649 + }, 650 + }); 651 + 652 + if (isCancel(skills)) { 653 + console.log('Operation cancelled'); 654 + process.exit(0); 655 + return null; 656 + } 657 + 658 + const theme = await select({ 659 + message: 'Select theme:', 660 + options: [ 661 + { value: 'light', label: 'Light', hint: 'Light mode' }, 662 + { value: 'dark', label: 'Dark', hint: 'Dark mode' }, 663 + { value: 'system', label: 'System', hint: 'Follow system preference' }, 664 + ], 665 + }); 666 + 667 + if (isCancel(theme)) { 668 + console.log('Operation cancelled'); 669 + process.exit(0); 670 + return null; 671 + } 672 + 673 + const notifications = await select({ 674 + message: 'Enable notifications?', 675 + options: [ 676 + { value: true, label: 'Yes', hint: 'Receive notifications' }, 677 + { value: false, label: 'No', hint: 'No notifications' }, 678 + ], 679 + }); 680 + 681 + if (isCancel(notifications)) { 682 + console.log('Operation cancelled'); 683 + process.exit(0); 684 + return null; 685 + } 686 + 687 + const language = await select({ 688 + message: 'Select language:', 689 + options: [ 690 + { value: 'en', label: 'English', hint: 'English' }, 691 + { value: 'es', label: 'Spanish', hint: 'Español' }, 692 + { value: 'fr', label: 'French', hint: 'Français' }, 693 + { value: 'de', label: 'German', hint: 'Deutsch' }, 694 + ], 695 + }); 696 + 697 + if (isCancel(language)) { 698 + console.log('Operation cancelled'); 699 + process.exit(0); 700 + return null; 701 + } 702 + 350 703 return { 351 704 name, 352 705 email, 353 706 age: parseInt(ageInput), 354 707 role, 708 + skills, 709 + preferences: { 710 + theme: theme as 'light' | 'dark' | 'system', 711 + notifications: notifications as boolean, 712 + language, 713 + }, 355 714 }; 356 715 } 357 - ``` 716 + ``` 717 + 718 + For more examples and best practices, check out our [GitHub repository](https://github.com/bombshell-dev/clack/tree/main/examples/basic).
+132 -23
src/content/docs/clack/packages/core.mdx
··· 12 12 - **Type-safe**: Built with TypeScript for better developer experience 13 13 - **Flexible rendering**: Customizable rendering system for prompts 14 14 - **Cancel handling**: Built-in support for handling user cancellation 15 + - **Event system**: Comprehensive event handling for user interactions 16 + - **Input validation**: Built-in support for input validation 17 + - **Abort signal support**: Integration with AbortController for cancellation 18 + 19 + ## Installation 20 + 21 + To start using the core package, first install it: 22 + 23 + <Tabs> 24 + <TabItem label="npm" icon="seti:npm"> 25 + ```bash 26 + npm install @clack/core 27 + ``` 28 + </TabItem> 29 + <TabItem label="pnpm" icon="pnpm"> 30 + ```bash 31 + pnpm add @clack/core 32 + ``` 33 + </TabItem> 34 + <TabItem label="Yarn" icon="seti:yarn"> 35 + ```bash 36 + yarn add @clack/core 37 + ``` 38 + </TabItem> 39 + </Tabs> 40 + 41 + Then import the components you need: 42 + 43 + ```ts twoslash 44 + import { 45 + TextPrompt, 46 + SelectPrompt, 47 + ConfirmPrompt, 48 + PasswordPrompt, 49 + MultiSelectPrompt, 50 + GroupMultiSelectPrompt, 51 + SelectKeyPrompt 52 + } from '@clack/core'; 53 + ``` 15 54 16 55 ## Package Structure 17 56 ··· 30 69 - Input handling 31 70 - State management 32 71 - Event system 72 + - Validation support 73 + - Abort signal integration 74 + 75 + #### Key Methods 76 + 77 + ```ts twoslash 78 + 79 + interface Prompt { 80 + // Event handling 81 + on<T extends string>(event: T, cb: (value: any) => void): void; 82 + once<T extends string>(event: T, cb: (value: any) => void): void; 83 + emit<T extends string>(event: T, ...data: any[]): void; 84 + 85 + // Core functionality 86 + prompt(): Promise<string | symbol>; 87 + close(): void; 88 + } 89 + ``` 90 + 91 + #### Available Events 92 + 93 + The `Prompt` interface provides several events that can be handled: 94 + 95 + ```ts twoslash 96 + // @errors: 2304 7006 2353 2345 2459 97 + import { Prompt } from '@clack/core'; 98 + 99 + // Example usage 100 + const p = new Prompt({ 101 + render: () => 'Enter your name:' 102 + }); 103 + 104 + // Handle value changes 105 + p.on('value', (value?: string) => { 106 + console.log('Value changed:', value); 107 + }); 108 + 109 + // Handle submission 110 + p.on('submit', (value?: string) => { 111 + console.log('Submitted:', value); 112 + }); 113 + 114 + // Handle cancellation 115 + p.on('cancel', () => { 116 + console.log('Operation cancelled'); 117 + }); 118 + ``` 33 119 34 120 ### Available Prompts 35 121 36 122 1. **TextPrompt**: For text input 123 + - Supports validation 124 + - Placeholder text 125 + - Initial value 126 + 37 127 2. **SelectPrompt**: For selection from options 128 + - Multiple selection support 129 + - Custom rendering 130 + - Option filtering 131 + 38 132 3. **ConfirmPrompt**: For yes/no confirmations 133 + - Yes/No shortcuts 134 + - Custom messages 39 135 40 - ## Getting Started 136 + 4. **AutocompletePrompt**: For searchable selection 137 + - Type-ahead filtering 138 + - Custom filtering logic 139 + - Multiple selection support 140 + 141 + 5. **PasswordPrompt**: For secure input 142 + - Character masking 143 + - Validation support 144 + 145 + 6. **MultiSelectPrompt**: For multiple selections 146 + - Checkbox interface 147 + - Selection limits 148 + - Custom rendering 149 + 150 + 7. **GroupMultiSelectPrompt**: For grouped selections 151 + - Hierarchical options 152 + - Group selection 153 + - Custom rendering 154 + 155 + 8. **SelectKeyPrompt**: For key-based selection 156 + - Custom key bindings 157 + - Multiple selection support 41 158 42 - To start using the core package, first install it: 159 + ## Creating Custom Prompts 43 160 44 - <Tabs> 45 - <TabItem label="npm" icon="seti:npm"> 46 - ```bash 47 - npm install @clack/core 48 - ``` 49 - </TabItem> 50 - <TabItem label="pnpm" icon="pnpm"> 51 - ```bash 52 - pnpm add @clack/core 53 - ``` 54 - </TabItem> 55 - <TabItem label="Yarn" icon="seti:yarn"> 56 - ```bash 57 - yarn add @clack/core 58 - ``` 59 - </TabItem> 60 - </Tabs> 161 + You can create custom prompts by extending the base `Prompt` class: 61 162 62 - Then import the components you need: 163 + ```ts twoslash 164 + import { Prompt } from '@clack/core'; 63 165 64 - ```ts 65 - import { TextPrompt, SelectPrompt, ConfirmPrompt } from '@clack/core'; 166 + // Example of extending the base Prompt class 167 + class CustomPrompt extends Prompt { 168 + constructor(options: { message: string }) { 169 + super({ 170 + ...options, 171 + render: () => `${options.message}\n${this.value || ''}` 172 + }); 173 + } 174 + } 66 175 ``` 67 176 68 - For detailed examples and usage patterns, check out our [examples guide](/docs/guides/examples). 177 + For detailed examples and usage patterns, check out our [examples guide](/docs/guides/examples).
+281 -54
src/content/docs/clack/packages/prompts.mdx
··· 2 2 title: Prompts 3 3 description: Learn about the prompts package and its capabilities 4 4 --- 5 - 6 5 import { Aside } from "@astrojs/starlight/components" 6 + import { Tabs, TabItem } from '@astrojs/starlight/components'; 7 7 8 8 The `@clack/prompts` package provides a collection of pre-built, high-level prompts that make it easy to create interactive command-line interfaces. It builds on top of the core package to provide a more developer-friendly experience. 9 9 ··· 14 14 - **Type-safe**: Full TypeScript support 15 15 - **Customizable**: Easy to extend and modify 16 16 17 + ## Installation 18 + 19 + 20 + <Tabs> 21 + <TabItem label="npm" icon="seti:npm"> 22 + ```bash 23 + npm install @clack/prompts 24 + ``` 25 + </TabItem> 26 + <TabItem label="pnpm" icon="pnpm"> 27 + ```bash 28 + pnpm add @clack/prompts 29 + ``` 30 + </TabItem> 31 + <TabItem label="Yarn" icon="seti:yarn"> 32 + ```bash 33 + yarn add @clack/prompts 34 + ``` 35 + </TabItem> 36 + </Tabs> 37 + 38 + ## Usage 39 + 40 + The prompts package is designed to be intuitive and easy to use. Each prompt function returns a Promise that resolves to the user's input. 41 + 42 + For more detailed examples and advanced usage patterns, check out our [examples guide](/docs/guides/examples) and [best practices](/docs/guides/best-practices). 43 + 17 44 ## Available Prompts 18 45 19 46 ### Text Input 47 + 20 48 ```ts twoslash 21 49 // @errors: 2339 22 50 import { text } from '@clack/prompts'; ··· 37 65 <font color="#06989A">└</font></pre> 38 66 39 67 ### Password Input 68 + 40 69 ```ts twoslash 41 70 import { password } from '@clack/prompts'; 42 71 ··· 68 97 const framework = await select({ 69 98 message: 'Pick a framework', 70 99 options: [ 71 - { value: 'next', label: 'Next.js' }, 72 - { value: 'astro', label: 'Astro' }, 73 - { value: 'svelte', label: 'SvelteKit' }, 100 + { value: 'next', label: 'Next.js', hint: 'React framework' }, 101 + { value: 'astro', label: 'Astro', hint: 'Content-focused' }, 102 + { value: 'svelte', label: 'SvelteKit', hint: 'Compile-time framework' }, 74 103 ], 104 + maxItems: 5, // Maximum number of items to display at once 75 105 }); 76 106 ``` 77 107 78 108 <pre class="cli-preview"><font color="#555753">│</font> 79 109 <font color="#06989A">◆</font> Pick a framework 80 - <font color="#06989A">│</font> <font color="#4E9A06">●</font> Next.js 81 - <font color="#06989A">│</font> ○ Astro 82 - <font color="#06989A">│</font> ○ SvelteKit 110 + <font color="#06989A">│</font> <font color="#4E9A06">●</font> Next.js (React framework) 111 + <font color="#06989A">│</font> ○ Astro (Content-focused) 112 + <font color="#06989A">│</font> ○ SvelteKit (Compile-time framework) 83 113 <font color="#06989A">└</font></pre> 84 114 85 115 #### Complex value ··· 90 120 const framework = await select({ 91 121 message: 'Pick a framework', 92 122 options: [ 93 - { value: { framework: 'Next', language: 'React' }, label: 'Next.js' }, 94 - { value: { framework: null, language: 'Astro' }, label: 'Astro' }, 95 - { value: { framework: 'Sveltekit', language: 'Svelte' }, label: 'SvelteKit' }, 123 + { value: { framework: 'Next', language: 'React' }, label: 'Next.js', hint: 'React framework' }, 124 + { value: { framework: null, language: 'Astro' }, label: 'Astro', hint: 'Content-focused' }, 125 + { value: { framework: 'Sveltekit', language: 'Svelte' }, label: 'SvelteKit', hint: 'Compile-time framework' }, 96 126 ], 97 127 }); 98 128 ``` 99 129 100 130 <pre class="cli-preview"><font color="#555753">│</font> 101 - <font color="#06989A">◆</font> Pick a framework 102 - <font color="#06989A">│</font> <font color="#4E9A06">●</font> Next.js 103 - <font color="#06989A">│</font> ○ Astro 104 - <font color="#06989A">│</font> ○ SvelteKit 105 - <font color="#06989A">└</font></pre> 131 + <font color="#555753">│</font> Pick a framework 132 + <font color="#555753">│</font> <font color="#4E9A06">●</font> Next.js (React framework) 133 + <font color="#555753">│</font> ○ Astro (Content-focused) 134 + <font color="#555753">│</font> ○ SvelteKit (Compile-time framework) 135 + <font color="#555753">└</font></pre> 106 136 107 137 #### Multiple values 108 138 ··· 112 142 const framework = await multiselect({ 113 143 message: 'Pick a framework', 114 144 options: [ 115 - { value: { framework: 'Next', language: 'React' }, label: 'Next.js' }, 116 - { value: { framework: null, language: 'Astro' }, label: 'Astro' }, 117 - { value: { framework: 'Sveltekit', language: 'Svelte' }, label: 'SvelteKit' }, 145 + { value: { framework: 'Next', language: 'React' }, label: 'Next.js', hint: 'React framework' }, 146 + { value: { framework: null, language: 'Astro' }, label: 'Astro', hint: 'Content-focused' }, 147 + { value: { framework: 'Sveltekit', language: 'Svelte' }, label: 'SvelteKit', hint: 'Compile-time framework' }, 118 148 ], 149 + maxItems: 5, // Maximum number of items to display at once 119 150 }); 120 151 ``` 121 152 122 153 <pre class="cli-preview"><font color="#555753">│</font> 123 154 <font color="#06989A">◆</font> Pick a framework 124 - <font color="#06989A">│</font> <font color="#4E9A06">◼</font> Next.js 125 - <font color="#06989A">│</font> <font color="#06989A">◻</font> Astro 126 - <font color="#06989A">│</font> ◻ SvelteKit 155 + <font color="#06989A">│</font> <font color="#4E9A06">◼</font> Next.js (React framework) 156 + <font color="#06989A">│</font> <font color="#06989A">◻</font> Astro (Content-focused) 157 + <font color="#06989A">│</font> ◻ SvelteKit (Compile-time framework) 158 + <font color="#06989A">└</font></pre> 159 + 160 + ### Autocomplete 161 + 162 + 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. 163 + 164 + ```ts twoslash 165 + import { autocomplete } from '@clack/prompts'; 166 + 167 + const framework = await autocomplete({ 168 + message: 'Search for a framework', 169 + options: [ 170 + { value: 'next', label: 'Next.js', hint: 'React framework' }, 171 + { value: 'astro', label: 'Astro', hint: 'Content-focused' }, 172 + { value: 'svelte', label: 'SvelteKit', hint: 'Compile-time framework' }, 173 + { value: 'remix', label: 'Remix', hint: 'Full stack framework' }, 174 + { value: 'nuxt', label: 'Nuxt', hint: 'Vue framework' }, 175 + ], 176 + placeholder: 'Type to search...', 177 + maxItems: 5, // Maximum number of items to display at once 178 + }); 179 + ``` 180 + 181 + <pre class="cli-preview"><font color="#555753">│</font> 182 + <font color="#06989A">◆</font> Search for a framework 183 + <font color="#06989A">│</font> Search: <span style="background-color:#FFFFFF"><font color="#181818">n</font></span> 184 + <font color="#06989A">│</font> (2 matches) 185 + <font color="#06989A">│</font> ● Next.js (React framework) 186 + <font color="#06989A">│</font> ○ Nuxt (Vue framework) 187 + <font color="#06989A">└</font></pre> 188 + 189 + ### Autocomplete Multiselect 190 + 191 + The `autocompleteMultiselect` combines the search functionality of autocomplete with the ability to select multiple options. 192 + 193 + ```ts twoslash 194 + import { autocompleteMultiselect } from '@clack/prompts'; 195 + 196 + const frameworks = await autocompleteMultiselect({ 197 + message: 'Select frameworks', 198 + options: [ 199 + { value: 'next', label: 'Next.js', hint: 'React framework' }, 200 + { value: 'astro', label: 'Astro', hint: 'Content-focused' }, 201 + { value: 'svelte', label: 'SvelteKit', hint: 'Compile-time framework' }, 202 + { value: 'remix', label: 'Remix', hint: 'Full stack framework' }, 203 + { value: 'nuxt', label: 'Nuxt', hint: 'Vue framework' }, 204 + ], 205 + placeholder: 'Type to search...', 206 + maxItems: 5, // Maximum number of items to display at once 207 + }); 208 + ``` 209 + 210 + <pre class="cli-preview"><font color="#555753">│</font> 211 + <font color="#06989A">◆</font> Select frameworks 212 + <font color="#06989A">│</font> Search: <span style="background-color:#FFFFFF"><font color="#181818">n</font></span> 213 + <font color="#06989A">│</font> (2 matches) 214 + <font color="#06989A">│</font> ◼ Next.js (React framework) 215 + <font color="#06989A">│</font> ◻ Nuxt (Vue framework) 127 216 <font color="#06989A">└</font></pre> 128 217 129 218 ### Confirmation ··· 146 235 ### Group Multiselect 147 236 148 237 ```ts twoslash 238 + // @errors: 2353 149 239 import { groupMultiselect } from '@clack/prompts'; 150 240 151 241 const projectOptions = await groupMultiselect({ 152 242 message: 'Define your project', 153 243 options: { 154 244 'Testing': [ 155 - { value: 'Jest' }, 156 - { value: 'Playwright' }, 157 - { value: 'Vitest' }, 245 + { value: 'Jest', hint: 'JavaScript testing framework' }, 246 + { value: 'Playwright', hint: 'End-to-end testing' }, 247 + { value: 'Vitest', hint: 'Vite-native testing' }, 158 248 ], 159 249 'Language': [{ 160 250 label: "Javascript", 161 251 value: 'js', 252 + hint: 'Dynamic typing' 162 253 }, { 163 254 label: 'TypeScript', 164 255 value: 'ts', 256 + hint: 'Static typing' 165 257 }, { 166 258 label: "CoffeeScript", 167 259 value: 'coffee', 260 + hint: 'JavaScript with Ruby-like syntax' 168 261 }], 169 262 'Code quality': [ 170 - { value: 'Prettier' }, 171 - { value: 'ESLint' }, 172 - { value: 'Biome.js' }, 263 + { value: 'Prettier', hint: 'Code formatter' }, 264 + { value: 'ESLint', hint: 'Linter' }, 265 + { value: 'Biome.js', hint: 'Formatter and linter' }, 173 266 ], 174 267 }, 268 + groupSpacing: 1, // Add one new line between each group 269 + selectableGroups: false, // Disable selection of top-level groups 175 270 }); 176 271 ``` 177 272 178 273 <pre class="cli-preview"><font color="#555753">│</font> 179 274 <font color="#06989A">◆</font> Define your project 180 275 <font color="#06989A">│</font> <font color="#4E9A06">◼</font> Testing 181 - <font color="#06989A">│</font> │ <font color="#4E9A06">◼</font> Jest 182 - <font color="#06989A">│</font> │ <font color="#4E9A06">◼</font> Playwright 183 - <font color="#06989A">│</font> └ <font color="#4E9A06">◼</font> Vitest 276 + <font color="#06989A">│</font> │ <font color="#4E9A06">◼</font> Jest (JavaScript testing framework) 277 + <font color="#06989A">│</font> │ <font color="#4E9A06">◼</font> Playwright (End-to-end testing) 278 + <font color="#06989A">│</font> └ <font color="#4E9A06">◼</font> Vitest (Vite-native testing) 279 + <font color="#06989A">│</font> 184 280 <font color="#06989A">│</font> <font color="#06989A">◻</font> Language 185 - <font color="#06989A">│</font> │ <font color="#4E9A06">◼</font> Javascript 186 - <font color="#06989A">│</font> │ <font color="#06989A">◻</font> TypeScript 187 - <font color="#06989A">│</font> └ <font color="#06989A">◻</font> CoffeeScript 281 + <font color="#06989A">│</font> │ <font color="#4E9A06">◼</font> Javascript (Dynamic typing) 282 + <font color="#06989A">│</font> │ <font color="#06989A">◻</font> TypeScript (Static typing) 283 + <font color="#06989A">│</font> └ <font color="#06989A">◻</font> CoffeeScript (JavaScript with Ruby-like syntax) 284 + <font color="#06989A">│</font> 188 285 <font color="#06989A">│</font> ◻ Code quality 189 - <font color="#06989A">│</font> │ ◻ Prettier 190 - <font color="#06989A">│</font> │ ◻ ESLint 191 - <font color="#06989A">│</font> └ <font color="#4E9A06">◼</font> Biome.js 286 + <font color="#06989A">│</font> │ ◻ Prettier (Code formatter) 287 + <font color="#06989A">│</font> │ ◻ ESLint (Linter) 288 + <font color="#06989A">│</font> └ <font color="#4E9A06">◼</font> Biome.js (Formatter and linter) 192 289 <font color="#06989A">└</font></pre> 290 + 291 + The `groupMultiselect` prompt supports two additional options: 292 + - `groupSpacing`: An integer that specifies how many new lines to add between each group. This helps improve readability when you have many groups. 293 + - `selectableGroups`: A boolean that determines whether top-level groups can be selected. When set to `false`, only individual items within groups can be selected. 193 294 194 295 ### Group 195 296 ··· 322 423 &nbsp;</pre> 323 424 324 425 ### Spinner 426 + 427 + The `spinner` function provides a loading indicator for long-running operations. 428 + 325 429 ```ts twoslash 326 430 import { spinner } from '@clack/prompts'; 327 431 ··· 332 436 // Do more things 333 437 spin.stop('Done'); 334 438 ``` 439 + 335 440 <pre class="cli-preview"><font color="#555753">│</font> 336 441 <font color="#AD7FA8">◒</font> Loading{`...`}</pre> 337 442 443 + You can customize the spinner's messages either per instance or globally: 444 + 445 + ```ts twoslash 446 + import { spinner, updateSettings } from '@clack/prompts'; 447 + 448 + // Global customization 449 + updateSettings({ 450 + messages: { 451 + cancel: "Operation cancelled", 452 + error: "An error occurred", 453 + }, 454 + }); 455 + 456 + // Per-instance customization 457 + const spin = spinner({ 458 + cancelMessage: "Process cancelled", 459 + errorMessage: "Process failed", 460 + }); 461 + 462 + spin.start('Loading'); 463 + // Do something 464 + spin.stop('Done'); 465 + ``` 466 + 338 467 The second parameter of `spinner.stop` (`code`) allow you to indicate the ending status of the spinner: 339 468 - `0` (or no value) indicate a success and the symbol for a finished task will be used 340 469 - `1` indicate a cancellation and the red square symbol will be used 341 470 - Any other code indicate an error and the yellow triangle symbol will be used 342 471 472 + You can also use custom frames for the spinner: 473 + 474 + ```ts twoslash 475 + import { spinner } from '@clack/prompts'; 476 + 477 + const spin = spinner({ 478 + frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] 479 + }); 480 + spin.start('Loading'); 481 + // Do something 482 + spin.stop('Done'); 483 + ``` 484 + 485 + ### Progress 486 + 487 + The `progress` prompt displays a progress bar for long-running operations. 488 + 489 + ```ts twoslash 490 + // @errors: 2339 491 + import { progress } from '@clack/prompts'; 492 + 493 + const prog = progress(); 494 + prog.start('Processing files'); 495 + // Update progress 496 + prog.message('Halfway there'); 497 + // Update again 498 + prog.message('Complete'); 499 + prog.stop('All files processed'); 500 + ``` 501 + 502 + <pre class="cli-preview"><font color="#555753">│</font> 503 + <font color="#06989A">◆</font> Processing files 504 + <font color="#555753">│</font> [████████████████████████████████████] 100% 505 + <font color="#555753">│</font> Complete 506 + <font color="#4E9A06">◆</font> All files processed</pre> 507 + 343 508 ### Note 344 509 345 510 The `note` function renders a box around a message to draw a user's attention. ··· 361 526 <font color="#555753">│</font> <font color="#555753">│</font> 362 527 <font color="#555753">├───────────────────────────────────────╯</font></pre> 363 528 364 - The second parameter (the title) is optional 529 + The second parameter (the title) is optional. You can also provide a format function to customize how each line is displayed: 530 + 365 531 ```ts twoslash 532 + // @errors: 2345 7006 366 533 import { note } from '@clack/prompts'; 367 534 368 535 note( 369 - 'All files have been created.', 536 + 'Line 1\nLine 2\nLine 3', 537 + 'Formatted steps', 538 + { 539 + format: (line: string) => `→ ${line}` 540 + } 370 541 ); 371 542 ``` 372 543 373 544 <pre class="cli-preview"><font color="#555753">│</font> 545 + <font color="#4E9A06">◇</font> Formatted steps 374 546 <font color="#4E9A06">◇</font> <font color="#555753">─────────────────────────────╮</font> 375 547 <font color="#555753">│</font> <font color="#555753">│</font> 376 - <font color="#555753">│</font> All files have been created. <font color="#555753">│</font> 548 + <font color="#555753">│</font> → Line 1 <font color="#555753">│</font> 549 + <font color="#555753">│</font> → Line 2 <font color="#555753">│</font> 550 + <font color="#555753">│</font> → Line 3 <font color="#555753">│</font> 377 551 <font color="#555753">│</font> <font color="#555753">│</font> 378 552 <font color="#555753">├────────────────────────────────╯</font></pre> 379 553 554 + ### Task Log 555 + 556 + The `taskLog` prompt provides a way to display log output that is cleared on success. This is useful for showing progress or status updates that should be removed once the task is complete. 557 + 558 + ```ts twoslash 559 + // @errors: 2345 560 + import { taskLog } from '@clack/prompts'; 561 + 562 + const log = taskLog({ 563 + title: 'Installing dependencies' 564 + }); 565 + log.message('Fetching package information...'); 566 + // Do some work 567 + log.message('Installing packages...'); 568 + // Do more work 569 + log.success('Installation complete'); 570 + ``` 571 + 572 + <pre class="cli-preview"><font color="#555753">│</font> 573 + <font color="#06989A">◆</font> Installing dependencies 574 + <font color="#555753">│</font> Fetching package information... 575 + <font color="#555753">│</font> Installing packages... 576 + <font color="#4E9A06">◆</font> Installation complete</pre> 577 + 380 578 ### Logs 381 579 382 580 The `log` utilities allow you to add semantic contextual information during an interaction. ··· 417 615 <font color="#555753">│</font> 418 616 <font color="#4E9A06">◇</font> Check files</pre> 419 617 618 + ### Internationalization 619 + 620 + The prompts package supports internationalization through the `updateSettings` function. You can customize the messages used by various prompts to match your preferred language. 621 + 622 + ```ts twoslash 623 + // @errors: 2353 624 + import { updateSettings, select, cancel } from '@clack/prompts'; 625 + 626 + // Update global messages 627 + updateSettings({ 628 + messages: { 629 + cancel: "Operación cancelada", 630 + error: "Se ha producido un error" 631 + } 632 + }); 633 + 634 + // Use the select prompt with translated content 635 + const framework = await select({ 636 + message: 'Selecciona un framework', 637 + options: [ 638 + { value: 'next', label: 'Next.js', hint: 'Framework de React' }, 639 + { value: 'astro', label: 'Astro', hint: 'Enfocado en contenido' }, 640 + { value: 'svelte', label: 'SvelteKit', hint: 'Framework de compilación' }, 641 + ] 642 + }); 643 + 644 + // If the user cancels, they'll see the translated message 645 + if (!framework) { 646 + cancel(); 647 + } 648 + ``` 649 + 650 + <pre class="cli-preview"><font color="#555753">│</font> 651 + <font color="#06989A">◆</font> Selecciona un framework 652 + <font color="#06989A">│</font> <font color="#4E9A06">●</font> Next.js (Framework de React) 653 + <font color="#06989A">│</font> ○ Astro (Enfocado en contenido) 654 + <font color="#06989A">│</font> ○ SvelteKit (Framework de compilación) 655 + <font color="#06989A">└</font> 656 + <font color="#555753">└</font> <font color="#CC0000">Operación cancelada</font> 657 + &nbsp;</pre> 658 + 420 659 ### Stream 421 660 422 661 The `stream` utilities allow you, like the `log` utilities, to add semantic contextual information during an interaction, ··· 454 693 <pre class="cli-preview"><font color="#555753">│</font> 455 694 <font color="#555753">│</font> 456 695 <font color="#555753">│</font> ⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣶⣶⣿⣿⣿⣿⣿⣿⣶⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 457 - <font color="#555753">│</font> ⠀⠀⠀⠀⠀⣠⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 696 + <font color="#555753">│</font> ⠀⠀⠀⠀⠀⣠⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 458 697 <font color="#555753">│</font> ⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 459 698 <font color="#555753">│</font> ⠀⠀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 460 - <font color="#555753">│</font> ⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀ 699 + <font color="#555753">│</font> ⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀ 461 700 <font color="#555753">│</font> ⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⣀⠀⠀⠀⠀⣿⣿⣿⡿⠀⠀⠀⠀⣀⠀ 462 701 <font color="#555753">│</font> ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⢠⣿⣿⣶⣤⣄⣻⣿⣿⣇⣠⣴⣶⣿⣿⡀ 463 702 <font color="#555753">│</font> ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⢾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡧ ··· 465 704 <font color="#555753">│</font> ⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⠟⢻⣿⣿⣿⣦⠀⠀⠀ 466 705 <font color="#555753">│</font> ⠀⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⠐⠿⣿⣿⣿⠏⠀⠀⢻⣿⣿⣿⠷⠀⠀ 467 706 <font color="#555753">│</font> ⠀⠀⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠏⠀⠀⠀⠀⠹⠋⠁⠀⠀⠀ 468 - <font color="#555753">│</font> ⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 707 + <font color="#555753">│</font> ⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 469 708 <font color="#555753">│</font> ⠀⠀⠀⠀⠀⠙⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 470 709 <font color="#555753">│</font> ⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠻⠿⢿⣿⣿⣿⣿⡿⠿⠟⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 471 710 <font color="#555753">│</font> ··· 476 715 <font color="#4E9A06">◇</font> Job1{`...`} <font color="#4E9A06">done</font> 477 716 <font color="#555753">│</font> Job2{`...`} <font color="#4E9A06">done</font> 478 717 <font color="#555753">│</font> Job3{`...`} <font color="#4E9A06">done</font></pre> 479 - 480 - ## Installation 481 - 482 - ```bash 483 - npm install @clack/prompts 484 - ``` 485 - 486 - ## Usage 487 - 488 - The prompts package is designed to be intuitive and easy to use. Each prompt function returns a Promise that resolves to the user's input. 489 - 490 - For more detailed examples and advanced usage patterns, check out our [examples guide](/docs/guides/examples) and [best practices](/docs/guides/best-practices).