Skip to content

Commit 7e6c3a2

Browse files
authored
fix(inquirer): Allow Partial<*> types as prefilled answers (#1889)
Fixes #1888
1 parent 3458ab6 commit 7e6c3a2

File tree

3 files changed

+125
-1
lines changed

3 files changed

+125
-1
lines changed

packages/inquirer/inquirer.test.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,120 @@ describe('inquirer.prompt(...)', () => {
846846
expectTypeOf(answers.prefilled).toEqualTypeOf<{ nested: string }>();
847847
expectTypeOf(answers.prefilled).not.toBeAny();
848848
});
849+
850+
describe('Partial<T> prefilled answers (issue #1888)', () => {
851+
it('should accept Partial-like objects and strip undefined from types', async () => {
852+
// Use a partial object without explicit Partial<T> type annotation
853+
const partial = {
854+
displayName: 'Custom Widget',
855+
technology: 'vue',
856+
};
857+
858+
const result = await inquirer.prompt(
859+
[
860+
{
861+
name: 'displayName',
862+
type: 'stub',
863+
message: 'Widget Display Name',
864+
answer: 'My Widget',
865+
},
866+
{
867+
name: 'technology',
868+
type: 'stub',
869+
message: 'Technology',
870+
answer: 'react',
871+
},
872+
{
873+
name: 'iconUrl',
874+
type: 'stub',
875+
message: 'Icon URL',
876+
answer: 'https://example.com/icon.png',
877+
},
878+
],
879+
partial,
880+
);
881+
882+
// Runtime behavior: prefilled values are used
883+
expect(result.displayName).toEqual('Custom Widget');
884+
expect(result.technology).toEqual('vue');
885+
expect(result.iconUrl).toEqual('https://example.com/icon.png');
886+
887+
// Type checks: result properties are string, not string | undefined
888+
expectTypeOf(result.displayName).toEqualTypeOf<string>();
889+
expectTypeOf(result.technology).toEqualTypeOf<string>();
890+
expectTypeOf(result).not.toBeAny();
891+
});
892+
893+
it('should handle prefilled answers with some fields undefined', async () => {
894+
const partial = {
895+
field1: 'value1',
896+
field2: undefined as string | undefined,
897+
};
898+
899+
const result = await inquirer.prompt(
900+
[
901+
{
902+
name: 'field1',
903+
type: 'stub',
904+
message: 'Field 1',
905+
answer: 'default1',
906+
},
907+
{
908+
name: 'field2',
909+
type: 'stub',
910+
message: 'Field 2',
911+
answer: 'default2',
912+
},
913+
],
914+
partial,
915+
);
916+
917+
// field1 should be prefilled, field2 should be prompted
918+
expect(result.field1).toEqual('value1');
919+
expect(result.field2).toEqual('default2');
920+
expectTypeOf(result.field1).toEqualTypeOf<string>();
921+
expectTypeOf(result.field2).toEqualTypeOf<string>();
922+
expectTypeOf(result).not.toBeAny();
923+
});
924+
925+
it('should handle multiple prefilled fields of same type', async () => {
926+
const partial = {
927+
name: 'Custom Name',
928+
description: 'Custom Description',
929+
};
930+
931+
const result = await inquirer.prompt(
932+
[
933+
{
934+
name: 'name',
935+
type: 'stub',
936+
message: 'Name',
937+
answer: 'Default Name',
938+
},
939+
{
940+
name: 'description',
941+
type: 'stub',
942+
message: 'Description',
943+
answer: 'Default Description',
944+
},
945+
{
946+
name: 'email',
947+
type: 'stub',
948+
message: 'Email',
949+
answer: 'default@example.com',
950+
},
951+
],
952+
partial,
953+
);
954+
955+
expect(result.name).toEqual('Custom Name');
956+
expect(result.description).toEqual('Custom Description');
957+
expect(result.email).toEqual('default@example.com');
958+
expectTypeOf(result.name).toEqualTypeOf<string>();
959+
expectTypeOf(result.description).toEqualTypeOf<string>();
960+
expectTypeOf(result).not.toBeAny();
961+
});
962+
});
849963
});
850964

851965
describe('#registerPrompt()', () => {

packages/inquirer/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ export function createPromptModule<
121121
questions: QuestionsDictionary<A, PrefilledAnswers, Prompts>,
122122
answers?: PrefilledAnswers,
123123
): PromptReturnType<DictionaryAnswers<A, PrefilledAnswers>>;
124+
function promptModule<A extends Answers>(
125+
questions: PromptSession<A>,
126+
answers?: Partial<A>,
127+
): PromptReturnType<A>;
124128
function promptModule<A extends Answers>(
125129
questions: PromptSession<A>,
126130
answers?: Partial<A>,

packages/inquirer/src/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,13 @@ type WidenAnswerLiterals<T> = T extends string
6969
: T extends Array<infer U>
7070
? Array<WidenAnswerLiterals<U>>
7171
: T extends Record<string, unknown>
72-
? { [K in keyof Mutable<T>]: WidenAnswerLiterals<Mutable<T>[K]> }
72+
? {
73+
[K in keyof Mutable<T>]: Mutable<T>[K] extends infer V
74+
? V extends undefined
75+
? never
76+
: WidenAnswerLiterals<V>
77+
: never;
78+
}
7379
: T;
7480

7581
type MergeAnswerObjects<Base, Override> = Prettify<Omit<Base, keyof Override> & Override>;

0 commit comments

Comments
 (0)