Skip to content
Closed
24 changes: 24 additions & 0 deletions .github/workflows/preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Preview
on:
pull_request:
pull_request_review:
types: [submitted]

jobs:

publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"

- name: Install dependencies
run: pnpm install

- run: pnpx pkg-pr-new publish './packages/*'
29 changes: 27 additions & 2 deletions packages/core/src/prompts/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ALIASES, CANCEL_SYMBOL, KEYS, diffLines, hasAliasKey, setRawMode } from

import type { ClackEvents, ClackState, InferSetType } from '../types';

// trigger build
export interface PromptOptions<Self extends Prompt> {
render(this: Omit<Self, 'prompt'>): string | undefined;
placeholder?: string;
Expand All @@ -17,11 +18,13 @@ export interface PromptOptions<Self extends Prompt> {
input?: Readable;
output?: Writable;
debug?: boolean;
signal?: AbortSignal;
}

// aaah
export default class Prompt {
protected input: Readable;
protected output: Writable;
private abortController?: AbortSignal;

private rl!: ReadLine;
private opts: Omit<PromptOptions<Prompt>, 'render' | 'input' | 'output'>;
Expand All @@ -36,14 +39,15 @@ export default class Prompt {
public value: any;

constructor(options: PromptOptions<Prompt>, trackValue = true) {
const { input = stdin, output = stdout, render, ...opts } = options;
const { input = stdin, output = stdout, render, signal, ...opts } = options;

this.opts = opts;
this.onKeypress = this.onKeypress.bind(this);
this.close = this.close.bind(this);
this.render = this.render.bind(this);
this._render = render.bind(this);
this._track = trackValue;
this.abortController = signal;

this.input = input;
this.output = output;
Expand Down Expand Up @@ -111,6 +115,27 @@ export default class Prompt {

public prompt() {
return new Promise<string | symbol>((resolve, reject) => {
if (this.abortController) {
if (this.abortController.aborted) {
this.state = 'cancel';

this.input.unpipe();
this.input.removeListener('keypress', this.onKeypress);
this.output.write('\n');
setRawMode(this.input, false);
// this.rl.close(); // The readline interface is not set up yet
this.emit(`${this.state}`, this.value);
this.unsubscribe();

return resolve(CANCEL_SYMBOL);
}

this.abortController.addEventListener('abort', () => {
this.state = 'cancel';
this.close();
}, { once: true });
}

const sink = new WriteStream(0);
sink._write = (chunk, encoding, done) => {
if (this._track) {
Expand Down
29 changes: 29 additions & 0 deletions packages/core/test/prompts/prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,33 @@ describe('Prompt', () => {
expect(eventSpy).toBeCalledWith(key);
}
});

test('aborts on abort signal', () => {
const abortController = new AbortController();

const instance = new Prompt({
input,
output,
render: () => 'foo',
signal: abortController.signal,
});

instance.prompt();

expect(instance.state).to.equal('active');

abortController.abort();

expect(instance.state).to.equal('cancel');
});

test('returns immediately if signal is already aborted', () => {
const abortController = new AbortController();
abortController.abort();

const instance = new Prompt({ input, output, render: () => 'foo', signal: abortController.signal });
instance.prompt();

expect(instance.state).to.equal('cancel');
});
});
Loading