Skip to content

Commit 6009a96

Browse files
committed
feat(@clack/prompts): builder onCancel api
1 parent 20c19d9 commit 6009a96

File tree

2 files changed

+59
-32
lines changed

2 files changed

+59
-32
lines changed

examples/builder.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,14 @@ import * as p from '@clack/prompts';
5656
})
5757
)
5858
.run();
59+
60+
await p
61+
.builder()
62+
.add('cancel', () => p.text({ message: 'Try cancel prompt (Ctrl + C):' }))
63+
.add('afterCancel', () => p.text({ message: 'This will not appear!' }))
64+
.onCancel(({ results }) => {
65+
p.cancel('Builder canceled');
66+
process.exit(0);
67+
})
68+
.run();
5969
})();

packages/prompts/src/index.ts

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -714,36 +714,38 @@ function ansiRegex() {
714714
return new RegExp(pattern, 'g');
715715
}
716716

717-
export type PromptGroupAwaitedReturn<T> = {
717+
type Prettify<T> = {
718+
[P in keyof T]: T[P];
719+
} & {};
720+
721+
export type PromptGroupAwaitedReturn<T> = Prettify<{
718722
[P in keyof T]: Exclude<Awaited<T[P]>, symbol>;
723+
}>;
724+
725+
export type PromptWithOptions<TResults, TReturn> = (opts: {
726+
results: PromptGroupAwaitedReturn<TResults>;
727+
}) => TReturn;
728+
729+
export type PromptGroup<T> = {
730+
[P in keyof T]: PromptWithOptions<Partial<Omit<T, P>>, void | Promise<T[P] | void>>;
719731
};
720732

721733
export interface PromptGroupOptions<T> {
722734
/**
723735
* Control how the group can be canceled
724736
* if one of the prompts is canceled.
725737
*/
726-
onCancel?: (opts: { results: Prettify<Partial<PromptGroupAwaitedReturn<T>>> }) => void;
738+
onCancel?: PromptWithOptions<Partial<T>, void>;
727739
}
728740

729-
type Prettify<T> = {
730-
[P in keyof T]: T[P];
731-
} & {};
732-
733-
export type PromptGroup<T> = {
734-
[P in keyof T]: (opts: {
735-
results: Prettify<Partial<PromptGroupAwaitedReturn<Omit<T, P>>>>;
736-
}) => void | Promise<T[P] | void>;
737-
};
738-
739741
/**
740742
* Define a group of prompts to be displayed
741743
* and return a results of objects within the group
742744
*/
743745
export const group = async <T>(
744746
prompts: PromptGroup<T>,
745747
opts?: PromptGroupOptions<T>
746-
): Promise<Prettify<PromptGroupAwaitedReturn<T>>> => {
748+
): Promise<PromptGroupAwaitedReturn<T>> => {
747749
const results = {} as any;
748750
const promptNames = Object.keys(prompts);
749751

@@ -768,32 +770,47 @@ export const group = async <T>(
768770
return results;
769771
};
770772

773+
type NextPromptBuilder<
774+
TResults extends Record<string, unknown>,
775+
TKey extends string,
776+
TResult
777+
> = PromptBuilder<
778+
{
779+
[Key in keyof TResults]: Key extends TKey ? TResult : TResults[Key];
780+
} & {
781+
[Key in TKey]: TResult;
782+
}
783+
>;
784+
771785
class PromptBuilder<TResults extends Record<string, unknown> = {}> {
772-
private results = {} as TResults;
786+
private results: TResults = {} as TResults;
787+
private prompts: Record<string, PromptWithOptions<TResults, unknown>> = {};
788+
private cancelCallback: PromptWithOptions<Partial<TResults>, void> | undefined;
773789

774-
add<TKey extends string, TResult extends Promise<unknown>>(
790+
public add<TKey extends string, TResult>(
775791
key: TKey extends keyof TResults ? never : TKey,
776-
prompt: (data: { results: Prettify<PromptGroupAwaitedReturn<TResults>> }) => TResult
777-
): PromptBuilder<
778-
{
779-
[Key in keyof TResults]: Key extends TKey ? TResult : TResults[Key];
780-
} & {
781-
[Key in TKey]: TResult;
782-
}
783-
> {
784-
// @ts-ignore
785-
this.results[key] = prompt;
786-
// @ts-ignore
792+
prompt: PromptWithOptions<TResults, TResult>
793+
): NextPromptBuilder<TResults, TKey, TResult> {
794+
this.prompts[key] = prompt;
795+
return this as NextPromptBuilder<TResults, TKey, TResult>;
796+
}
797+
798+
public onCancel(cb: PromptWithOptions<Partial<TResults>, void>): PromptBuilder<TResults> {
799+
this.cancelCallback = cb;
787800
return this;
788801
}
789802

790-
async run(): Promise<Prettify<PromptGroupAwaitedReturn<TResults>>> {
791-
for (const [key, prompt] of Object.entries(this.results)) {
792-
// @ts-ignore
793-
this.results[key] = await prompt({ results: this.results });
803+
public async run(): Promise<PromptGroupAwaitedReturn<TResults>> {
804+
for (const [key, prompt] of Object.entries(this.prompts)) {
805+
const result = await prompt({ results: this.results as any });
806+
if (isCancel(result)) {
807+
this.cancelCallback?.({ results: this.results as any });
808+
continue;
809+
}
810+
//@ts-ignore
811+
this.results[key] = result;
794812
}
795-
// @ts-ignore
796-
return this.results;
813+
return this.results as PromptGroupAwaitedReturn<TResults>;
797814
}
798815
}
799816

0 commit comments

Comments
 (0)