UNPKG

@shopify/cli-kit

Version:

A set of utilities, interfaces, and models that are common across all the platform features

159 lines 5.42 kB
import { isTruthy } from '../../../public/node/context/utilities.js'; import { Stdout } from '../ui.js'; import { render as inkRender } from 'ink'; import { EventEmitter } from 'events'; class Stderr extends EventEmitter { constructor() { super(...arguments); this.frames = []; this.write = (frame) => { this.frames.push(frame); this._lastFrame = frame; }; this.lastFrame = () => this._lastFrame; } } export class Stdin extends EventEmitter { constructor(options = {}) { super(); this.data = null; this.write = (data) => { this.data = data; this.emit('readable'); }; this.read = () => { const data = this.data; this.data = null; return data; }; this.isTTY = options.isTTY ?? true; } setEncoding() { } setRawMode() { } ref() { } unref() { } } export const render = (tree, options = {}) => { const stdout = new Stdout({ columns: 100 }); const stderr = new Stderr(); const stdin = new Stdin(); const instance = inkRender(tree, { stdout: options.stdout ?? stdout, stderr: options.stderr ?? stderr, stdin: options.stdin ?? stdin, debug: true, exitOnCtrlC: false, patchConsole: false, }); return { rerender: instance.rerender, unmount: instance.unmount, cleanup: instance.cleanup, waitUntilExit: () => trackPromise(instance.waitUntilExit()), stdout, stderr, stdin, frames: stdout.frames, lastFrame: stdout.lastFrame, }; }; /** * Wait for the component to be ready to accept input. */ export function waitForInputsToBeReady() { return new Promise((resolve) => setTimeout(resolve, 100)); } /** * Wait for the last frame to change to anything. */ export function waitForChange(func, getChangingValue) { return new Promise((resolve) => { const initialValue = getChangingValue(); func(); const interval = setInterval(() => { if (getChangingValue() !== initialValue) { clearInterval(interval); resolve(); } }, 10); }); } export function waitFor(func, condition) { return new Promise((resolve) => { func(); const interval = setInterval(() => { if (condition()) { clearInterval(interval); resolve(); } }, 10); }); } /** * Wait for the last frame to contain specific text. */ export function waitForContent(renderInstance, content, func = () => { }) { return waitFor(() => func(), () => renderInstance.lastFrame().includes(content)); } /** * Send input and wait for the last frame to change. * * Useful when you want to send some input and wait for anything to change in the interface. * If you need to wait for a specific change instead, you can use sendInputAndWaitForContent. */ export async function sendInputAndWaitForChange(renderInstance, ...inputs) { await waitForChange(() => inputs.forEach((input) => renderInstance.stdin.write(input)), renderInstance.lastFrame); // wait for another tick so we give time to react to update caches await new Promise((resolve) => setTimeout(resolve, 0)); } /** Send input and wait a number of milliseconds. * * Useful if you know some what will happen after input will take a certain amount of time * and it will not cause any visible change so you can't use sendInputAndWaitForChange. * This function can also be used if you want to test that nothing changes after some input has been sent. */ export async function sendInputAndWait(renderInstance, waitTime, ...inputs) { inputs.forEach((input) => renderInstance.stdin.write(input)); await new Promise((resolve) => setTimeout(resolve, waitTime)); } /** * Send input and wait for the last frame to contain specific text. * * Useful when you want to send some input and wait for a specific change to happen. * If you need to wait for any change instead, you can use sendInputAndWaitForChange. */ export async function sendInputAndWaitForContent(renderInstance, content, ...inputs) { await waitForContent(renderInstance, content, () => inputs.forEach((input) => renderInstance.stdin.write(input))); } /** Function that is useful when you want to check the last frame of a component that unmounted. * * The reason this function exists is that in CI Ink will clear the last frame on unmount. */ export function getLastFrameAfterUnmount(renderInstance) { return isTruthy(process.env.CI) ? renderInstance.frames[renderInstance.frames.length - 2] : renderInstance.lastFrame(); } function trackPromise(promise) { let isPending = true; let isRejected = false; let isFulfilled = false; const trackedPromise = promise.then(function (result) { isFulfilled = true; isPending = false; return result; }, function (error) { isRejected = true; isPending = false; throw error; }); trackedPromise.isFulfilled = function () { return isFulfilled; }; trackedPromise.isPending = function () { return isPending; }; trackedPromise.isRejected = function () { return isRejected; }; return trackedPromise; } //# sourceMappingURL=ui.js.map