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

wip: logger

Nate Moore (Feb 27, 2023, 5:02 AM -0600) d2aebb53 50593866

+451 -1
+19
examples/dev/index.ts
··· 1 + import { logger } from '@clack/logger'; 2 + import { setTimeout as sleep } from 'node:timers/promises'; 3 + 4 + async function main() { 5 + const c = logger(); 6 + 7 + c.log("Hello world welcome to this shit"); 8 + await sleep(1000); 9 + c.log("Hello world"); 10 + console.log("AHHHHHH!"); 11 + await sleep(1000); 12 + c.log("Hello world"); 13 + console.log("AHHHHHH!"); 14 + await sleep(1000); 15 + c.log("Hello world"); 16 + console.log({ test: 0 }); 17 + } 18 + 19 + main().catch(console.error);
+16
examples/dev/package.json
··· 1 + { 2 + "name": "@example/dev", 3 + "private": true, 4 + "version": "0.0.0", 5 + "type": "module", 6 + "dependencies": { 7 + "@clack/logger": "workspace:*", 8 + "picocolors": "^1.0.0" 9 + }, 10 + "scripts": { 11 + "start": "jiti ./index.ts" 12 + }, 13 + "devDependencies": { 14 + "jiti": "^1.17.0" 15 + } 16 + }
+3
examples/dev/tsconfig.json
··· 1 + { 2 + "extends": "../../tsconfig.json" 3 + }
+1 -1
package.json
··· 8 8 "build:core": "pnpm --filter @clack/core run build", 9 9 "build:prompts": "pnpm --filter @clack/prompts run build", 10 10 "start": "pnpm --filter @example/basic run start", 11 - "dev": "pnpm --filter @example/changesets run start", 11 + "dev": "pnpm --filter @example/dev run start", 12 12 "format": "pnpm run format:code", 13 13 "format:code": "prettier -w . --cache", 14 14 "format:imports": "organize-imports-cli ./packages/*/tsconfig.json",
+168
packages/logger/CHANGELOG.md
··· 1 + # @clack/core 2 + 3 + ## 0.3.0 4 + 5 + ### Minor Changes 6 + 7 + - 8a4a12f: Add `GroupMultiSelect` prompt 8 + 9 + ### Patch Changes 10 + 11 + - 8a4a12f: add `groupMultiselect` prompt 12 + 13 + ## 0.2.1 14 + 15 + ### Patch Changes 16 + 17 + - ec812b6: fix `readline` hang on Windows 18 + 19 + ## 0.2.0 20 + 21 + ### Minor Changes 22 + 23 + - d74dd05: Adds a `selectKey` prompt type 24 + - 54c1bc3: **Breaking Change** `multiselect` has renamed `initialValue` to `initialValues` 25 + 26 + ## 0.1.9 27 + 28 + ### Patch Changes 29 + 30 + - 1251132: Multiselect: return `Value[]` instead of `Option[]`. 31 + - 8994382: Add a password prompt to `@clack/prompts` 32 + 33 + ## 0.1.8 34 + 35 + ### Patch Changes 36 + 37 + - d96071c: Don't mutate `initialValue` in `multiselect`, fix parameter type for `validate()`. 38 + 39 + Credits to @banjo for the bug report and initial PR! 40 + 41 + ## 0.1.7 42 + 43 + ### Patch Changes 44 + 45 + - 6d9e675: Add support for neovim cursor motion (`hjkl`) 46 + 47 + Thanks [@esau-morais](https://github.com/esau-morais) for the assist! 48 + 49 + ## 0.1.6 50 + 51 + ### Patch Changes 52 + 53 + - 7fb5375: Adds a new `defaultValue` option to the text prompt, removes automatic usage of the placeholder value. 54 + 55 + ## 0.1.5 56 + 57 + ### Patch Changes 58 + 59 + - de1314e: Support `required` option for multi-select 60 + 61 + ## 0.1.4 62 + 63 + ### Patch Changes 64 + 65 + - ca77da1: Fix multiselect initial value logic 66 + - 8aed606: Fix `MaxListenersExceededWarning` by detaching `stdin` listeners on close 67 + 68 + ## 0.1.3 69 + 70 + ### Patch Changes 71 + 72 + - a99c458: Support `initialValue` option for text prompt 73 + 74 + ## 0.1.2 75 + 76 + ### Patch Changes 77 + 78 + - Allow isCancel to type guard any unknown value 79 + - 7dcad8f: Allow placeholder to be passed to TextPrompt 80 + - 2242f13: Fix multiselect returning undefined 81 + - b1341d6: Improved placeholder handling 82 + 83 + ## 0.1.1 84 + 85 + ### Patch Changes 86 + 87 + - 4be7dbf: Ensure raw mode is unset on submit 88 + - b480679: Preserve value if validation fails 89 + 90 + ## 0.1.0 91 + 92 + ### Minor Changes 93 + 94 + - 7015ec9: Create new prompt: multi-select 95 + 96 + ## 0.0.12 97 + 98 + ### Patch Changes 99 + 100 + - 9d371c3: Fix rendering bug when using y/n to confirm 101 + 102 + ## 0.0.11 103 + 104 + ### Patch Changes 105 + 106 + - 441d5b7: fix select return undefined 107 + - d20ef2a: Update keywords, URLs 108 + - fe13c2f: fix cursor missing after submit 109 + 110 + ## 0.0.10 111 + 112 + ### Patch Changes 113 + 114 + - a0cb382: Add `main` entrypoint 115 + 116 + ## 0.0.9 117 + 118 + ### Patch Changes 119 + 120 + - Fix node@16 issue (cannot read "createInterface" of undefined) 121 + 122 + ## 0.0.8 123 + 124 + ### Patch Changes 125 + 126 + - a4b5e13: Bug fixes, exposes `block` utility 127 + 128 + ## 0.0.7 129 + 130 + ### Patch Changes 131 + 132 + - Fix cursor bug 133 + 134 + ## 0.0.6 135 + 136 + ### Patch Changes 137 + 138 + - Fix error with character check 139 + 140 + ## 0.0.5 141 + 142 + ### Patch Changes 143 + 144 + - 491f9e0: update readme 145 + 146 + ## 0.0.4 147 + 148 + ### Patch Changes 149 + 150 + - 7372d5c: Fix bug with line deletion 151 + 152 + ## 0.0.3 153 + 154 + ### Patch Changes 155 + 156 + - 5605d28: Do not bundle dependencies (take II) 157 + 158 + ## 0.0.2 159 + 160 + ### Patch Changes 161 + 162 + - 2ee67cb: don't bundle deps 163 + 164 + ## 0.0.1 165 + 166 + ### Patch Changes 167 + 168 + - 306598e: Initial publish, still WIP
+22
packages/logger/README.md
··· 1 + # `@clack/core` 2 + 3 + Clack contains low-level primitives for implementing your own command-line applications. 4 + 5 + Currently, `TextPrompt`, `SelectPrompt`, and `ConfirmPrompt` are exposed as well as the base `Prompt` class. 6 + 7 + Each `Prompt` accepts a `render` function. 8 + 9 + ```js 10 + import { TextPrompt, isCancel } from '@clack/core'; 11 + 12 + const p = new TextPrompt({ 13 + render() { 14 + return `What's your name?\n${this.valueWithCursor}`; 15 + }, 16 + }); 17 + 18 + const name = await p.prompt(); 19 + if (isCancel(name)) { 20 + process.exit(0); 21 + } 22 + ```
+7
packages/logger/build.config.ts
··· 1 + import { defineBuildConfig } from 'unbuild'; 2 + 3 + // @see https://github.com/unjs/unbuild 4 + export default defineBuildConfig({ 5 + preset: '../../build.preset', 6 + entries: ['src/index'], 7 + });
+61
packages/logger/package.json
··· 1 + { 2 + "name": "@clack/logger", 3 + "version": "0.0.0", 4 + "type": "module", 5 + "main": "./dist/index.cjs", 6 + "module": "./dist/index.mjs", 7 + "exports": { 8 + ".": { 9 + "types": "./dist/index.d.ts", 10 + "import": "./dist/index.mjs", 11 + "require": "./dist/index.cjs" 12 + }, 13 + "./package.json": "./package.json" 14 + }, 15 + "types": "./dist/index.d.ts", 16 + "repository": { 17 + "type": "git", 18 + "url": "https://github.com/natemoo-re/clack", 19 + "directory": "packages/logger" 20 + }, 21 + "bugs": { 22 + "url": "https://github.com/natemoo-re/clack/issues" 23 + }, 24 + "homepage": "https://github.com/natemoo-re/clack/tree/main/packages/logger#readme", 25 + "files": [ 26 + "dist", 27 + "CHANGELOG.md" 28 + ], 29 + "keywords": [ 30 + "ask", 31 + "clack", 32 + "cli", 33 + "command-line", 34 + "command", 35 + "input", 36 + "interact", 37 + "interface", 38 + "menu", 39 + "prompt", 40 + "prompts", 41 + "stdin", 42 + "ui" 43 + ], 44 + "author": { 45 + "name": "Nate Moore", 46 + "email": "nate@natemoo.re", 47 + "url": "https://twitter.com/n_moore" 48 + }, 49 + "license": "MIT", 50 + "packageManager": "pnpm@7.6.0", 51 + "scripts": { 52 + "build": "unbuild" 53 + }, 54 + "dependencies": { 55 + "picocolors": "^1.0.0", 56 + "sisteransi": "^1.0.5" 57 + }, 58 + "devDependencies": { 59 + "unbuild": "^1.1.1" 60 + } 61 + }
+129
packages/logger/src/index.ts
··· 1 + import { emitKeypressEvents, Interface as ReadlineInterface } from 'node:readline'; 2 + import { stdin, stdout } from 'node:process'; 3 + import { createInterface } from 'node:readline'; 4 + import { WriteStream } from 'node:tty'; 5 + import { Readable, Writable } from 'node:stream'; 6 + import { formatWithOptions } from 'node:util'; 7 + import { cursor, erase } from 'sisteransi'; 8 + 9 + interface Message { 10 + content: string; 11 + count: number; 12 + } 13 + class Logger { 14 + globalMessages: Message[] = []; 15 + messages: Message[] = []; 16 + rl: ReadlineInterface; 17 + output: WriteStream; 18 + state = 'initial'; 19 + 20 + constructor({ input = stdin, output = stdout } = {}) { 21 + this.output = output; 22 + const sink = new WriteStream(0); 23 + sink._write = (chunk, encoding, done) => { 24 + done(); 25 + }; 26 + input.pipe(sink); 27 + setRawMode(input, true); 28 + this.rl = createInterface({ 29 + input, 30 + output: sink, 31 + tabSize: 2, 32 + prompt: '', 33 + escapeCodeTimeout: 50, 34 + }); 35 + output.write(cursor.hide); 36 + this.log = this.log.bind(this); 37 + this.#hookGlobalConsole(); 38 + 39 + input.on('keypress', (char: string) => { 40 + if (char === '\x03') { 41 + this.close(); 42 + process.exit(0); 43 + } 44 + }) 45 + } 46 + 47 + #originalConsole: Console | undefined; 48 + #hookGlobalConsole() { 49 + this.#originalConsole = globalThis.console; 50 + globalThis.console = new Proxy(console, { 51 + get: (target, property, receiver) => { 52 + return (...args: any) => { 53 + const content = formatWithOptions({ colors: true, }, ...args); 54 + this.globalMessages.push({ content, count: 1 }); 55 + this.render() 56 + 57 + }; 58 + } 59 + }) 60 + } 61 + 62 + log(...args: any) { 63 + const content = formatWithOptions({ colors: true }, ...args); 64 + if (this.messages.at(-1)?.content === content) { 65 + this.messages.at(-1)!.count++; 66 + } else { 67 + this.messages.push({ content, count: 1 }); 68 + } 69 + this.render(); 70 + } 71 + close() { 72 + this.state = 'final'; 73 + this.render(); 74 + this.rl.close(); 75 + if (this.#originalConsole) { 76 + globalThis.console = this.#originalConsole; 77 + } 78 + this.output.write(cursor.show); 79 + } 80 + 81 + render() { 82 + if (this.state !== 'initial') { 83 + this.output.write(cursor.move(-999, this.output.rows * -1)); 84 + this.output.write(erase.down()); 85 + } 86 + this.output.write('Welcome to the dev server\n'); 87 + let lines = []; 88 + 89 + for (const message of this.messages) { 90 + if (message.count === 1) { 91 + lines.push(message.content) 92 + } else { 93 + lines.push(`${message.content} (x${message.count})`) 94 + } 95 + } 96 + for (const message of this.globalMessages) { 97 + if (message.count === 1) { 98 + lines.push(message.content) 99 + } else { 100 + lines.push(`${message.content} (x${message.count})`) 101 + } 102 + } 103 + let frame = lines.join('\n'); 104 + if (this.state !== 'final') { 105 + frame += Array.from({ length: this.output.rows - lines.length - 1 }, () => '').join('\n'); 106 + } 107 + this.output.write(frame); 108 + 109 + if (this.state !== 'final') { 110 + this.output.write(`Ctrl + C to quit\n`); 111 + } 112 + if (this.state === 'initial') { 113 + this.state = 'active'; 114 + } 115 + if (this.state === 'final') { 116 + this.output.write('\n'); 117 + } 118 + } 119 + } 120 + 121 + export function logger() { 122 + const log = new Logger(); 123 + 124 + return log; 125 + } 126 + 127 + function setRawMode(input: Readable, value: boolean) { 128 + if ((input as typeof stdin).isTTY) (input as typeof stdin).setRawMode(value); 129 + }
+3
packages/logger/tsconfig.json
··· 1 + { 2 + "extends": "../../tsconfig.json" 3 + }
+22
pnpm-lock.yaml
··· 46 46 devDependencies: 47 47 jiti: 1.17.0 48 48 49 + examples/dev: 50 + specifiers: 51 + '@clack/logger': workspace:* 52 + jiti: ^1.17.0 53 + picocolors: ^1.0.0 54 + dependencies: 55 + '@clack/logger': link:../../packages/logger 56 + picocolors: 1.0.0 57 + devDependencies: 58 + jiti: 1.17.0 59 + 49 60 packages/core: 61 + specifiers: 62 + picocolors: ^1.0.0 63 + sisteransi: ^1.0.5 64 + unbuild: ^1.1.1 65 + dependencies: 66 + picocolors: 1.0.0 67 + sisteransi: 1.0.5 68 + devDependencies: 69 + unbuild: 1.1.1 70 + 71 + packages/logger: 50 72 specifiers: 51 73 picocolors: ^1.0.0 52 74 sisteransi: ^1.0.5