From f13d3554ed838047b62285281d0f87fd3ab20035 Mon Sep 17 00:00:00 2001 From: jlmessenger Date: Thu, 3 Feb 2022 09:04:16 -0500 Subject: [PATCH] #5064 Display detailed errors from CLI (#6921) * Display detailed errors from CLI * feat(codegen): better Listr error handling at `executeCodegen()` level * fix(codegen): remove useless comment * fix(codegen): use AggregateError to keep references of original errors * Create famous-bikes-punch.md Co-authored-by: Charly POLY Co-authored-by: Charly POLY <1252066+charlypoly@users.noreply.github.com> --- .changeset/famous-bikes-punch.md | 5 ++ packages/graphql-codegen-cli/src/codegen.ts | 21 +++++- .../src/utils/cli-error.ts | 7 ++ .../graphql-codegen-cli/tests/codegen.spec.ts | 70 ++++++++++--------- 4 files changed, 70 insertions(+), 33 deletions(-) create mode 100644 .changeset/famous-bikes-punch.md diff --git a/.changeset/famous-bikes-punch.md b/.changeset/famous-bikes-punch.md new file mode 100644 index 00000000000..551c1ce8a9f --- /dev/null +++ b/.changeset/famous-bikes-punch.md @@ -0,0 +1,5 @@ +--- +"@graphql-codegen/cli": patch +--- + +#5064 Display detailed errors from CLI diff --git a/packages/graphql-codegen-cli/src/codegen.ts b/packages/graphql-codegen-cli/src/codegen.ts index 4d4577eeaeb..987d03c86f4 100644 --- a/packages/graphql-codegen-cli/src/codegen.ts +++ b/packages/graphql-codegen-cli/src/codegen.ts @@ -6,9 +6,12 @@ import { normalizeInstanceOrArray, normalizeConfig, getCachedDocumentNodeFromSchema, + isDetailedError, } from '@graphql-codegen/plugin-helpers'; import { codegen } from '@graphql-codegen/core'; +import { AggregateError } from '@graphql-tools/utils'; + import { Renderer, ErrorRenderer } from './utils/listr-renderer'; import { GraphQLError, GraphQLSchema, DocumentNode } from 'graphql'; import { getPluginByName } from './plugins'; @@ -20,6 +23,7 @@ import path from 'path'; // eslint-disable-next-line import { createRequire } from 'module'; import Listr from 'listr'; +import { isListrError } from './utils/cli-error'; const makeDefaultLoader = (from: string) => { if (fs.statSync(from).isDirectory()) { @@ -384,7 +388,22 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom }, }); - await listr.run(); + try { + await listr.run(); + } catch (err) { + if (isListrError(err)) { + const allErrs = err.errors.map(subErr => + isDetailedError(subErr) + ? `${subErr.message} for "${subErr.source}"${subErr.details}` + : subErr.message || subErr.toString() + ); + const newErr = new AggregateError(err.errors, `${err.message} ${allErrs.join('\n\n')}`); + // Best-effort to all stack traces for debugging + newErr.stack = `${newErr.stack}\n\n${err.errors.map(subErr => subErr.stack).join('\n\n')}`; + throw newErr; + } + throw err; + } return result; } diff --git a/packages/graphql-codegen-cli/src/utils/cli-error.ts b/packages/graphql-codegen-cli/src/utils/cli-error.ts index 51d99a78e07..46acf8a9b99 100644 --- a/packages/graphql-codegen-cli/src/utils/cli-error.ts +++ b/packages/graphql-codegen-cli/src/utils/cli-error.ts @@ -1,5 +1,12 @@ +import { DetailedError } from '@graphql-codegen/plugin-helpers'; import { isBrowser, isNode } from './is-browser'; +type CompositeError = Error | DetailedError; +type ListrError = Error & { errors: CompositeError[] }; +export function isListrError(err: Error & { name?: unknown; errors?: unknown }): err is ListrError { + return err.name === 'ListrError' && Array.isArray(err.errors) && err.errors.length > 0; +} + export function cliError(err: any, exitOnError = true) { let msg: string; diff --git a/packages/graphql-codegen-cli/tests/codegen.spec.ts b/packages/graphql-codegen-cli/tests/codegen.spec.ts index a3316213ccc..c6d4327528e 100644 --- a/packages/graphql-codegen-cli/tests/codegen.spec.ts +++ b/packages/graphql-codegen-cli/tests/codegen.spec.ts @@ -261,7 +261,7 @@ describe('Codegen Executor', () => { throw SHOULD_NOT_THROW_STRING; } catch (e) { expect(e).not.toEqual(SHOULD_NOT_THROW_STRING); - expect(e.errors[0].message).toContain('Not all operations have an unique name: q'); + expect(e.message).toContain('Not all operations have an unique name: q'); } }); @@ -547,8 +547,7 @@ describe('Codegen Executor', () => { throw new Error(SHOULD_NOT_THROW_STRING); } catch (e) { expect(e.message).not.toBe(SHOULD_NOT_THROW_STRING); - expect(e.errors[0].message).toContain('Invalid Custom Plugin'); - expect(e.errors[0].details).toContain('does not export a valid JS object with'); + expect(e.message).toContain('Invalid Custom Plugin'); } }); @@ -565,8 +564,7 @@ describe('Codegen Executor', () => { throw new Error(SHOULD_NOT_THROW_STRING); } catch (e) { expect(e.message).not.toBe(SHOULD_NOT_THROW_STRING); - expect(e.errors[0].message).toContain('validation failed'); - expect(e.errors[0].details).toContain('Invalid!'); + expect(e.message).toContain('validation failed'); } }); @@ -757,9 +755,8 @@ describe('Codegen Executor', () => { }); throw new Error(SHOULD_NOT_THROW_STRING); - } catch (listrError) { - const e = listrError.errors[0]; - expect(e.message).toContain('Failed to load schema'); + } catch (error) { + expect(error.message).toContain('Failed to load schema'); } }); @@ -781,9 +778,8 @@ describe('Codegen Executor', () => { }); throw new Error(SHOULD_NOT_THROW_STRING); - } catch (listrError) { - const e = listrError.errors[0]; - expect(e.details).toContain('Failed to load custom loader'); + } catch (error) { + expect(error.message).toContain('Failed to load custom loader'); } }); @@ -805,10 +801,9 @@ describe('Codegen Executor', () => { }); throw new Error(SHOULD_NOT_THROW_STRING); - } catch (listrError) { - const e = listrError.errors[0]; - expect(e.message).toContain('Failed to load schema'); - expect(e.details).toContain('Failed to load custom loader'); + } catch (error) { + expect(error.message).toContain('Failed to load schema'); + expect(error.message).toContain('Failed to load custom loader'); } }); }); @@ -873,9 +868,8 @@ describe('Codegen Executor', () => { }); throw new Error(SHOULD_NOT_THROW_STRING); - } catch (listrError) { - const e = listrError.errors[0]; - expect(e.message).toContain('Unable to find any GraphQL type definitions for the following pointers'); + } catch (error) { + expect(error.message).toContain('Unable to find any GraphQL type definitions for the following pointers'); } }); @@ -898,9 +892,8 @@ describe('Codegen Executor', () => { }); throw new Error(SHOULD_NOT_THROW_STRING); - } catch (listrError) { - const e = listrError.errors[0]; - expect(e.message).toContain('Failed to load custom loader'); + } catch (error) { + expect(error.message).toContain('Failed to load custom loader'); } }); @@ -923,9 +916,8 @@ describe('Codegen Executor', () => { }); throw new Error(SHOULD_NOT_THROW_STRING); - } catch (listrError) { - const e = listrError.errors[0]; - expect(e.message).toContain('Failed to load custom loader'); + } catch (error) { + expect(error.message).toContain('Failed to load custom loader'); } }); }); @@ -943,13 +935,7 @@ describe('Codegen Executor', () => { }, }); } catch (error) { - const isExpectedError = error.errors && error.errors.some(e => e.message.includes('Failed to load schema')); - - if (!isExpectedError) { - // eslint-disable-next-line no-console - console.error(error); - throw error; - } + expect(error.message).toContain('Failed to load schema from http://www.dummyschema.com/graphql'); } expect((global as any).CUSTOM_FETCH_FN_CALLED).toBeTruthy(); }); @@ -988,7 +974,7 @@ describe('Codegen Executor', () => { }); expect(output.length).toBe(1); } catch (e) { - expect(e.errors[0].message).not.toBe('Query root type must be provided.'); + expect(e.message).not.toBe('Query root type must be provided.'); } }); @@ -1061,4 +1047,24 @@ describe('Codegen Executor', () => { } `); }); + + it('Handles weird errors due to invalid schema', async () => { + const schema = /* GraphQL */ ` + type Query { + brrrt:1 + } + `; + try { + await executeCodegen({ + schema: [schema], + generates: { + 'out1.graphql': { + plugins: ['schema-ast'], + }, + }, + }); + } catch (error) { + expect(error.message).toContain('Failed to load schema for "out1.graphql"'); + } + }); });