Skip to content

Commit

Permalink
Feat: gracefully error on missing message (#1575)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Simon Boudrias <[email protected]>
  • Loading branch information
mshima and SBoudrias authored Nov 14, 2024
1 parent 07679d1 commit 28c2eb2
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 0 deletions.
12 changes: 12 additions & 0 deletions packages/core/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,18 @@ it('fail on aborted signals', async () => {
});

describe('Error handling', () => {
it('gracefully error on missing content', async () => {
// @ts-expect-error Testing an invalid behavior.
const prompt = createPrompt(function TestPrompt() {});
const { answer } = await render(prompt, {});
await expect(answer).rejects.toMatchInlineSnapshot(
`
[Error: Prompt functions must return a string.
at ${import.meta.filename}]
`,
);
});

it('surface errors in render functions', async () => {
const Prompt = () => {
throw new Error('Error in render function');
Expand Down
29 changes: 29 additions & 0 deletions packages/core/src/lib/create-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,28 @@ type ViewFunction<Value, Config> = (
done: (value: Value) => void,
) => string | [string, string | undefined];

function getCallSites() {
const _prepareStackTrace = Error.prepareStackTrace;
try {
let result: NodeJS.CallSite[] = [];
Error.prepareStackTrace = (_, callSites) => {
const callSitesWithoutCurrent = callSites.slice(1);
result = callSitesWithoutCurrent;
return callSitesWithoutCurrent;
};

// eslint-disable-next-line @typescript-eslint/no-unused-expressions, unicorn/error-message
new Error().stack;
return result;
} finally {
Error.prepareStackTrace = _prepareStackTrace;
}
}

export function createPrompt<Value, Config>(view: ViewFunction<Value, Config>) {
const callSites = getCallSites();
const callerFilename = callSites[1]?.getFileName?.();

const prompt: Prompt<Value, Config> = (config, context = {}) => {
// Default `input` to stdin
const { input = process.stdin, signal } = context;
Expand Down Expand Up @@ -75,6 +96,14 @@ export function createPrompt<Value, Config>(view: ViewFunction<Value, Config>) {
setImmediate(() => resolve(value));
});

// Typescript won't allow this, but not all users rely on typescript.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (nextView === undefined) {
throw new Error(
`Prompt functions must return a string.\n at ${callerFilename}`,
);
}

const [content, bottomContent] =
typeof nextView === 'string' ? [nextView] : nextView;
screen.render(content, bottomContent);
Expand Down

0 comments on commit 28c2eb2

Please sign in to comment.