diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml new file mode 100644 index 00000000..9bab9d02 --- /dev/null +++ b/.github/workflows/preview.yml @@ -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/*' diff --git a/packages/core/src/prompts/prompt.ts b/packages/core/src/prompts/prompt.ts index f2c85771..cc4fcbea 100644 --- a/packages/core/src/prompts/prompt.ts +++ b/packages/core/src/prompts/prompt.ts @@ -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 { render(this: Omit): string | undefined; placeholder?: string; @@ -17,11 +18,13 @@ export interface PromptOptions { 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, 'render' | 'input' | 'output'>; @@ -36,7 +39,7 @@ export default class Prompt { public value: any; constructor(options: PromptOptions, 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); @@ -44,6 +47,7 @@ export default class Prompt { this.render = this.render.bind(this); this._render = render.bind(this); this._track = trackValue; + this.abortController = signal; this.input = input; this.output = output; @@ -111,6 +115,27 @@ export default class Prompt { public prompt() { return new Promise((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) { diff --git a/packages/core/test/prompts/prompt.test.ts b/packages/core/test/prompts/prompt.test.ts index 0f976c34..44318c88 100644 --- a/packages/core/test/prompts/prompt.test.ts +++ b/packages/core/test/prompts/prompt.test.ts @@ -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'); + }); });