diff --git a/.changeset/friendly-bears-walk.md b/.changeset/friendly-bears-walk.md new file mode 100644 index 0000000000000..f0a8bae28aa77 --- /dev/null +++ b/.changeset/friendly-bears-walk.md @@ -0,0 +1,14 @@ +--- +"@graphql-mesh/cli": minor +"@graphql-mesh/utils": minor +--- + +Now CLI reports critical errors with stack traces even if DEBUG isn't enabled, and error messages are no longer trimmed. + +```diff +Schema couldn't be generated because of the following errors: +- - Foo bar is n... ++ - Foo bar is not valid ++ at /somepath/somejsfile.js:123:2 ++ at /someotherpath/someotherjs.file:232:4 +``` diff --git a/.changeset/young-clouds-burn.md b/.changeset/young-clouds-burn.md new file mode 100644 index 0000000000000..b687175d8cc07 --- /dev/null +++ b/.changeset/young-clouds-burn.md @@ -0,0 +1,6 @@ +--- +"@graphql-mesh/cli": minor +"@graphql-mesh/config": minor +--- + +New flag for `mesh build`. You can set `mesh build --throwOnInvalidConfig=true` to make the CLI throw an exception if the configuration file is invalid per Mesh's configuration schema diff --git a/packages/cli/src/config.ts b/packages/cli/src/config.ts index 527442a1c0f10..8b5f21a642aff 100644 --- a/packages/cli/src/config.ts +++ b/packages/cli/src/config.ts @@ -4,11 +4,13 @@ import { defaultImportFn, loadYaml, DefaultLogger } from '@graphql-mesh/utils'; import Ajv from 'ajv'; import { cosmiconfig, defaultLoaders } from 'cosmiconfig'; import { path, process } from '@graphql-mesh/cross-helpers'; +import { AggregateError } from '@graphql-tools/utils'; export function validateConfig( config: any, filepath: string, - initialLoggerPrefix: string + initialLoggerPrefix: string, + throwOnInvalidConfig = false ): asserts config is YamlConfig.Config { const ajv = new Ajv({ strict: false, @@ -16,6 +18,17 @@ export function validateConfig( jsonSchema.$schema = undefined; const isValid = ajv.validate(jsonSchema, config); if (!isValid) { + if (throwOnInvalidConfig) { + const aggregateError = new AggregateError( + ajv.errors.map(e => { + const error = new Error(e.message); + error.stack += `\n at ${filepath}:0:0`; + return error; + }), + 'Configuration file is not valid' + ); + throw aggregateError; + } const logger = new DefaultLogger(initialLoggerPrefix).child('config'); logger.warn('Configuration file is not valid!'); logger.warn("This is just a warning! It doesn't have any effects on runtime."); diff --git a/packages/cli/src/handleFatalError.ts b/packages/cli/src/handleFatalError.ts index c7c33831c6b16..d6a95ae676029 100644 --- a/packages/cli/src/handleFatalError.ts +++ b/packages/cli/src/handleFatalError.ts @@ -2,7 +2,7 @@ import { Logger } from '@graphql-mesh/types'; import { process } from '@graphql-mesh/cross-helpers'; export function handleFatalError(e: Error, logger: Logger): any { - logger.error(e.stack || e.message); + logger.error(e); if (process.env.JEST == null) { process.exit(1); } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 6b83c18ce58be..2e70e7974309d 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -309,7 +309,7 @@ export async function graphqlMesh( } } ) - .command<{ fileType: 'json' | 'ts' | 'js' }>( + .command<{ fileType: 'json' | 'ts' | 'js'; throwOnInvalidConfig: boolean }>( cliParams.buildArtifactsCommand, 'Builds artifacts', builder => { @@ -318,6 +318,10 @@ export async function graphqlMesh( choices: ['json', 'ts', 'js'], default: 'ts', }); + builder.option('throwOnInvalidConfig', { + type: 'boolean', + default: false, + }); }, async args => { try { @@ -373,6 +377,7 @@ export async function graphqlMesh( additionalPackagePrefixes: cliParams.additionalPackagePrefixes, generateCode: true, initialLoggerPrefix: cliParams.initialLoggerPrefix, + throwOnInvalidConfig: args.throwOnInvalidConfig, }); logger = meshConfig.logger; diff --git a/packages/config/src/process.ts b/packages/config/src/process.ts index 6c89e02a2c495..b1bf5fd96be60 100644 --- a/packages/config/src/process.ts +++ b/packages/config/src/process.ts @@ -54,6 +54,7 @@ export type ConfigProcessOptions = { additionalPackagePrefixes?: string[]; generateCode?: boolean; initialLoggerPrefix?: string; + throwOnInvalidConfig?: boolean; }; type EnvelopPlugins = Parameters[0]['plugins']; diff --git a/packages/utils/src/logger.ts b/packages/utils/src/logger.ts index 693b62bc4da5d..531faaff495e3 100644 --- a/packages/utils/src/logger.ts +++ b/packages/utils/src/logger.ts @@ -26,13 +26,13 @@ export const titleBold: MessageTransformer = msg => ANSI_CODES.bold + msg + ANSI export class DefaultLogger implements Logger { constructor(public name?: string) {} - private getLoggerMessage(...args: any[]) { + private getLoggerMessage({ args = [], trim = this.isDebug }: { args: any[]; trim?: boolean }) { return args .flat(Infinity) .map(arg => { if (typeof arg === 'string') { - if (arg.length > 100 && !this.isDebug) { - return arg.slice(0, 100) + '...'; + if (trim && arg.length > 100) { + return arg.slice(0, 100) + '...' + ''; } return arg; } else if (typeof arg === 'object' && arg?.stack != null) { @@ -43,14 +43,17 @@ export class DefaultLogger implements Logger { .join(` `); } - private handleLazyMessage(...lazyArgs: LazyLoggerMessage[]) { + private handleLazyMessage({ lazyArgs, trim }: { lazyArgs: LazyLoggerMessage[]; trim?: boolean }) { const flattenedArgs = lazyArgs.flat(Infinity).flatMap(arg => { if (typeof arg === 'function') { return arg(); } return arg; }); - return this.getLoggerMessage(flattenedArgs); + return this.getLoggerMessage({ + args: flattenedArgs, + trim, + }); } private get isDebug() { @@ -66,12 +69,16 @@ export class DefaultLogger implements Logger { } log(...args: any[]) { - const message = this.getLoggerMessage(...args); + const message = this.getLoggerMessage({ + args, + }); return console.log(`${this.prefix} ${message}`); } warn(...args: any[]) { - const message = this.getLoggerMessage(...args); + const message = this.getLoggerMessage({ + args, + }); const fullMessage = `⚠️ ${this.prefix} ${warnColor(message)}`; if (console.warn) { console.warn(fullMessage); @@ -81,7 +88,9 @@ export class DefaultLogger implements Logger { } info(...args: any[]) { - const message = this.getLoggerMessage(...args); + const message = this.getLoggerMessage({ + args, + }); const fullMessage = `💡 ${this.prefix} ${infoColor(message)}`; if (console.info) { console.info(fullMessage); @@ -91,14 +100,19 @@ export class DefaultLogger implements Logger { } error(...args: any[]) { - const message = this.getLoggerMessage(...args); + const message = this.getLoggerMessage({ + args, + trim: false, + }); const fullMessage = `💥 ${this.prefix} ${errorColor(message)}`; console.log(fullMessage); } debug(...lazyArgs: LazyLoggerMessage[]) { if (this.isDebug) { - const message = this.handleLazyMessage(lazyArgs); + const message = this.handleLazyMessage({ + lazyArgs, + }); const fullMessage = `🐛 ${this.prefix} ${debugColor(message)}`; if (console.debug) { console.debug(fullMessage);