Skip to content

Commit 17b40db

Browse files
committed
fix: keep api key prompt compact
1 parent 3fbd872 commit 17b40db

4 files changed

Lines changed: 42 additions & 5 deletions

File tree

bun.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"test:watch": "bun test --watch"
4141
},
4242
"dependencies": {
43+
"@clack/core": "^0.3.5",
4344
"@clack/prompts": "^0.7.0",
4445
"bun-plugin-dts": "^0.4.0",
4546
"es-toolkit": "^1.46.1",

src/utils/prompt.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
* case explicitly.
1111
*/
1212

13+
import { PasswordPrompt } from '@clack/core';
14+
1315
import { isInteractive } from './env.js';
1416
import { CLIError } from '../errors/base.js';
1517
import { ExitCode } from '../errors/codes';
@@ -59,13 +61,35 @@ export async function promptConfirm(options: {
5961
return val as boolean;
6062
}
6163

62-
export async function promptPassword(options: {
63-
message: string;
64-
}): Promise<string | undefined> {
64+
const PASSWORD_MASK_PREVIEW_LENGTH = 12;
65+
const PROMPT_THEME = '\x1b[38;2;248;103;58m'; // #F8673A
66+
const RESET_COLOR = '\x1b[0m';
67+
68+
function themed(value: string): string {
69+
return process.env.NO_COLOR ? value : `${PROMPT_THEME}${value}${RESET_COLOR}`;
70+
}
71+
72+
export function passwordMaskPreview(length: number): string {
73+
const mask = '•'.repeat(Math.min(length, PASSWORD_MASK_PREVIEW_LENGTH));
74+
return length > PASSWORD_MASK_PREVIEW_LENGTH ? `${mask}… (${length} chars)` : mask;
75+
}
76+
77+
export async function promptPassword(options: { message: string }): Promise<string | undefined> {
6578
if (!isInteractive()) return undefined;
6679

67-
const inquirer = await import('@clack/prompts');
68-
const val = await inquirer.password({ message: options.message, mask: '•' });
80+
const val = await new PasswordPrompt({
81+
mask: '•',
82+
render() {
83+
const length = typeof this.value === 'string' ? this.value.length : 0;
84+
const preview = passwordMaskPreview(length);
85+
if (this.state === 'submit') {
86+
return `${themed('│')}\n${themed('◇')} ${options.message}\n${themed('│')} ${preview}`;
87+
}
88+
if (this.state === 'cancel') return `${themed('│')}\n■ ${options.message}\n│ Cancelled`;
89+
return `${themed('│')}\n${themed('◆')} ${options.message}\n`
90+
+ `${themed('│')} ${preview}${themed('_')}\n${themed('└')}`;
91+
},
92+
}).prompt();
6993
if (typeof val === 'symbol') return undefined;
7094
return val;
7195
}

test/utils/prompt.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { describe, expect, it } from 'bun:test';
2+
3+
import { passwordMaskPreview } from '../../src/utils/prompt';
4+
5+
describe('passwordMaskPreview', () => {
6+
it('caps long input while keeping visible typing progress', () => {
7+
expect(passwordMaskPreview(5)).toBe('•••••');
8+
expect(passwordMaskPreview(160)).toBe('••••••••••••… (160 chars)');
9+
expect(passwordMaskPreview(160)).not.toContain('\n');
10+
});
11+
});

0 commit comments

Comments
 (0)