UNPKG

58.6 kBJavaScriptView Raw
1import { isDetailedError, DetailedError, normalizeInstanceOrArray, normalizeConfig, normalizeOutputParam } from '@graphql-codegen/plugin-helpers';
2import { codegen } from '@graphql-codegen/core';
3import chalk from 'chalk';
4import indentString from 'indent-string';
5import logSymbols from 'log-symbols';
6import ansiEscapes from 'ansi-escapes';
7import wrapAnsi from 'wrap-ansi';
8import { stripIndent } from 'common-tags';
9import { dummyLogger } from 'ts-log';
10import { GraphQLError, parse } from 'graphql';
11import { resolve, join, delimiter, sep, dirname, isAbsolute, relative } from 'path';
12import { printSchemaWithDirectives, isValidPath } from '@graphql-tools/utils';
13import { cosmiconfig, defaultLoaders } from 'cosmiconfig';
14import { env } from 'string-env-interpolation';
15import yargs from 'yargs';
16import { loadConfig } from 'graphql-config';
17import { ApolloEngineLoader } from '@graphql-tools/apollo-engine-loader';
18import { CodeFileLoader } from '@graphql-tools/code-file-loader';
19import { GitLoader } from '@graphql-tools/git-loader';
20import { GithubLoader } from '@graphql-tools/github-loader';
21import { PrismaLoader } from '@graphql-tools/prisma-loader';
22import { loadSchema as loadSchema$1, loadDocuments as loadDocuments$1 } from '@graphql-tools/load';
23import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
24import { JsonFileLoader } from '@graphql-tools/json-file-loader';
25import { UrlLoader } from '@graphql-tools/url-loader';
26import { exec } from 'child_process';
27import isGlob from 'is-glob';
28import debounce from 'debounce';
29import { readFileSync, writeFileSync, statSync, unlink } from 'fs';
30import { sync } from 'mkdirp';
31import { createHash } from 'crypto';
32import inquirer from 'inquirer';
33import detectIndent from 'detect-indent';
34
35let logger;
36function getLogger() {
37 return logger || dummyLogger;
38}
39useWinstonLogger();
40function useWinstonLogger() {
41 if (logger && logger.levels) {
42 return;
43 }
44 logger = console;
45}
46
47let queue = [];
48function debugLog(message, ...meta) {
49 if (!process.env.GQL_CODEGEN_NODEBUG && process.env.DEBUG !== undefined) {
50 queue.push({
51 message,
52 meta,
53 });
54 }
55}
56function printLogs() {
57 if (!process.env.GQL_CODEGEN_NODEBUG && process.env.DEBUG !== undefined) {
58 queue.forEach(log => {
59 getLogger().info(log.message, ...log.meta);
60 });
61 resetLogs();
62 }
63}
64function resetLogs() {
65 queue = [];
66}
67
68const UpdateRenderer = require('listr-update-renderer');
69class Renderer {
70 constructor(tasks, options) {
71 this.updateRenderer = new UpdateRenderer(tasks, options);
72 }
73 render() {
74 return this.updateRenderer.render();
75 }
76 end(err) {
77 this.updateRenderer.end(err);
78 if (typeof err === 'undefined') {
79 logUpdate.clear();
80 return;
81 }
82 // persist the output
83 logUpdate.done();
84 // show errors
85 if (err) {
86 const errorCount = err.errors ? err.errors.length : 0;
87 if (errorCount > 0) {
88 const count = indentString(chalk.red.bold(`Found ${errorCount} error${errorCount > 1 ? 's' : ''}`), 1);
89 const details = err.errors
90 .map(error => {
91 debugLog(`[CLI] Exited with an error`, error);
92 return { msg: isDetailedError(error) ? error.details : null, rawError: error };
93 })
94 .map(({ msg, rawError }, i) => {
95 const source = err.errors[i].source;
96 msg = msg ? chalk.gray(indentString(stripIndent(`${msg}`), 4)) : null;
97 const stack = rawError.stack ? chalk.gray(indentString(stripIndent(rawError.stack), 4)) : null;
98 if (source) {
99 const sourceOfError = typeof source === 'string' ? source : source.name;
100 const title = indentString(`${logSymbols.error} ${sourceOfError}`, 2);
101 return [title, msg, stack, stack].filter(Boolean).join('\n');
102 }
103 return [msg, stack].filter(Boolean).join('\n');
104 })
105 .join('\n\n');
106 logUpdate.emit(['', count, details, ''].join('\n\n'));
107 }
108 else {
109 const details = err.details ? err.details : '';
110 logUpdate.emit(`${chalk.red.bold(`${indentString(err.message, 2)}`)}\n${details}\n${chalk.grey(err.stack)}`);
111 }
112 }
113 logUpdate.done();
114 printLogs();
115 }
116}
117class LogUpdate {
118 constructor() {
119 this.stream = process.stdout;
120 // state
121 this.previousLineCount = 0;
122 this.previousOutput = '';
123 this.previousWidth = this.getWidth();
124 }
125 emit(...args) {
126 let output = args.join(' ') + '\n';
127 const width = this.getWidth();
128 if (output === this.previousOutput && this.previousWidth === width) {
129 return;
130 }
131 this.previousOutput = output;
132 this.previousWidth = width;
133 output = wrapAnsi(output, width, {
134 trim: false,
135 hard: true,
136 wordWrap: false,
137 });
138 this.stream.write(ansiEscapes.eraseLines(this.previousLineCount) + output);
139 this.previousLineCount = output.split('\n').length;
140 }
141 clear() {
142 this.stream.write(ansiEscapes.eraseLines(this.previousLineCount));
143 this.previousOutput = '';
144 this.previousWidth = this.getWidth();
145 this.previousLineCount = 0;
146 }
147 done() {
148 this.previousOutput = '';
149 this.previousWidth = this.getWidth();
150 this.previousLineCount = 0;
151 }
152 getWidth() {
153 const { columns } = this.stream;
154 if (!columns) {
155 return 80;
156 }
157 return columns;
158 }
159}
160const logUpdate = new LogUpdate();
161
162async function getPluginByName(name, pluginLoader) {
163 const possibleNames = [
164 `@graphql-codegen/${name}`,
165 `@graphql-codegen/${name}-template`,
166 `@graphql-codegen/${name}-plugin`,
167 `graphql-codegen-${name}`,
168 `graphql-codegen-${name}-template`,
169 `graphql-codegen-${name}-plugin`,
170 `codegen-${name}`,
171 `codegen-${name}-template`,
172 name,
173 ];
174 const possibleModules = possibleNames.concat(resolve(process.cwd(), name));
175 for (const moduleName of possibleModules) {
176 try {
177 return await pluginLoader(moduleName);
178 }
179 catch (err) {
180 if (err.message.indexOf(`Cannot find module '${moduleName}'`) === -1) {
181 throw new DetailedError(`Unable to load template plugin matching ${name}`, `
182 Unable to load template plugin matching '${name}'.
183 Reason:
184 ${err.message}
185 `);
186 }
187 }
188 }
189 const possibleNamesMsg = possibleNames
190 .map(name => `
191 - ${name}
192 `.trimRight())
193 .join('');
194 throw new DetailedError(`Unable to find template plugin matching ${name}`, `
195 Unable to find template plugin matching '${name}'
196 Install one of the following packages:
197
198 ${possibleNamesMsg}
199 `);
200}
201
202async function getPresetByName(name, loader) {
203 const possibleNames = [`@graphql-codegen/${name}`, `@graphql-codegen/${name}-preset`, name];
204 const possibleModules = possibleNames.concat(resolve(process.cwd(), name));
205 for (const moduleName of possibleModules) {
206 try {
207 const loaded = await loader(moduleName);
208 if (loaded && loaded.preset) {
209 return loaded.preset;
210 }
211 else if (loaded && loaded.default) {
212 return loaded.default;
213 }
214 return loaded;
215 }
216 catch (err) {
217 if (err.message.indexOf(`Cannot find module '${moduleName}'`) === -1) {
218 throw new DetailedError(`Unable to load preset matching ${name}`, `
219 Unable to load preset matching '${name}'.
220 Reason:
221 ${err.message}
222 `);
223 }
224 }
225 }
226 const possibleNamesMsg = possibleNames
227 .map(name => `
228 - ${name}
229 `.trimRight())
230 .join('');
231 throw new DetailedError(`Unable to find preset matching ${name}`, `
232 Unable to find preset matching '${name}'
233 Install one of the following packages:
234
235 ${possibleNamesMsg}
236 `);
237}
238
239const CodegenExtension = (api) => {
240 // Schema
241 api.loaders.schema.register(new CodeFileLoader());
242 api.loaders.schema.register(new GitLoader());
243 api.loaders.schema.register(new GithubLoader());
244 api.loaders.schema.register(new ApolloEngineLoader());
245 api.loaders.schema.register(new PrismaLoader());
246 // Documents
247 api.loaders.documents.register(new CodeFileLoader());
248 api.loaders.documents.register(new GitLoader());
249 api.loaders.documents.register(new GithubLoader());
250 return {
251 name: 'codegen',
252 };
253};
254async function findAndLoadGraphQLConfig(filepath) {
255 const config = await loadConfig({
256 filepath,
257 rootDir: process.cwd(),
258 extensions: [CodegenExtension],
259 throwOnEmpty: false,
260 throwOnMissing: false,
261 });
262 if (isGraphQLConfig(config)) {
263 return config;
264 }
265}
266// Kamil: user might load a config that is not GraphQL Config
267// so we need to check if it's a regular config or not
268function isGraphQLConfig(config) {
269 if (!config) {
270 return false;
271 }
272 try {
273 return config.getDefault().hasExtension('codegen');
274 }
275 catch (e) { }
276 try {
277 for (const projectName in config.projects) {
278 if (config.projects.hasOwnProperty(projectName)) {
279 const project = config.projects[projectName];
280 if (project.hasExtension('codegen')) {
281 return true;
282 }
283 }
284 }
285 }
286 catch (e) { }
287 return false;
288}
289
290const loadSchema = async (schemaPointers, config) => {
291 try {
292 const loaders = [
293 new CodeFileLoader(),
294 new GitLoader(),
295 new GithubLoader(),
296 new GraphQLFileLoader(),
297 new JsonFileLoader(),
298 new UrlLoader(),
299 new ApolloEngineLoader(),
300 new PrismaLoader(),
301 ];
302 const schema = await loadSchema$1(schemaPointers, {
303 assumeValidSDL: true,
304 loaders,
305 sort: true,
306 convertExtensions: true,
307 ...config,
308 });
309 return schema;
310 }
311 catch (e) {
312 throw new DetailedError('Failed to load schema', `
313 Failed to load schema from ${Object.keys(schemaPointers).join(',')}:
314
315 ${e.message || e}
316 ${e.stack || ''}
317
318 GraphQL Code Generator supports:
319 - ES Modules and CommonJS exports (export as default or named export "schema")
320 - Introspection JSON File
321 - URL of GraphQL endpoint
322 - Multiple files with type definitions (glob expression)
323 - String in config file
324
325 Try to use one of above options and run codegen again.
326
327 `);
328 }
329};
330const loadDocuments = async (documentPointers, config) => {
331 const loaders = [new CodeFileLoader(), new GitLoader(), new GithubLoader(), new GraphQLFileLoader()];
332 const loadedFromToolkit = await loadDocuments$1(documentPointers, {
333 ignore: Object.keys(config.generates).map(p => join(process.cwd(), p)),
334 loaders,
335 sort: true,
336 skipGraphQLImport: true,
337 ...config,
338 });
339 return loadedFromToolkit;
340};
341
342function generateSearchPlaces(moduleName) {
343 const extensions = ['json', 'yaml', 'yml', 'js', 'config.js'];
344 // gives codegen.json...
345 const regular = extensions.map(ext => `${moduleName}.${ext}`);
346 // gives .codegenrc.json... but no .codegenrc.config.js
347 const dot = extensions.filter(ext => ext !== 'config.js').map(ext => `.${moduleName}rc.${ext}`);
348 return regular.concat(dot);
349}
350function customLoader(ext) {
351 function loader(filepath, content) {
352 if (typeof process !== 'undefined' && 'env' in process) {
353 content = env(content);
354 }
355 if (ext === 'json') {
356 return defaultLoaders['.json'](filepath, content);
357 }
358 if (ext === 'yaml') {
359 return defaultLoaders['.yaml'](filepath, content);
360 }
361 if (ext === 'js') {
362 return defaultLoaders['.js'](filepath, content);
363 }
364 }
365 return loader;
366}
367async function loadContext(configFilePath) {
368 const moduleName = 'codegen';
369 const cosmi = cosmiconfig(moduleName, {
370 searchPlaces: generateSearchPlaces(moduleName),
371 loaders: {
372 '.json': customLoader('json'),
373 '.yaml': customLoader('yaml'),
374 '.yml': customLoader('yaml'),
375 '.js': customLoader('js'),
376 noExt: customLoader('yaml'),
377 },
378 });
379 const graphqlConfig = await findAndLoadGraphQLConfig(configFilePath);
380 if (graphqlConfig) {
381 return new CodegenContext({
382 graphqlConfig,
383 });
384 }
385 const result = await (configFilePath ? cosmi.load(configFilePath) : cosmi.search(process.cwd()));
386 if (!result) {
387 if (configFilePath) {
388 throw new DetailedError(`Config ${configFilePath} does not exist`, `
389 Config ${configFilePath} does not exist.
390
391 $ graphql-codegen --config ${configFilePath}
392
393 Please make sure the --config points to a correct file.
394 `);
395 }
396 throw new DetailedError(`Unable to find Codegen config file!`, `
397 Please make sure that you have a configuration file under the current directory!
398 `);
399 }
400 if (result.isEmpty) {
401 throw new DetailedError(`Found Codegen config file but it was empty!`, `
402 Please make sure that you have a valid configuration file under the current directory!
403 `);
404 }
405 return new CodegenContext({
406 filepath: result.filepath,
407 config: result.config,
408 });
409}
410function getCustomConfigPath(cliFlags) {
411 const configFile = cliFlags.config;
412 return configFile ? resolve(process.cwd(), configFile) : null;
413}
414function buildOptions() {
415 return {
416 c: {
417 alias: 'config',
418 type: 'string',
419 describe: 'Path to GraphQL codegen YAML config file, defaults to "codegen.yml" on the current directory',
420 },
421 w: {
422 alias: 'watch',
423 describe: 'Watch for changes and execute generation automatically. You can also specify a glob expreession for custom watch list.',
424 coerce: (watch) => {
425 if (watch === 'false') {
426 return false;
427 }
428 if (typeof watch === 'string' || Array.isArray(watch)) {
429 return watch;
430 }
431 return true;
432 },
433 },
434 r: {
435 alias: 'require',
436 describe: 'Loads specific require.extensions before running the codegen and reading the configuration',
437 type: 'array',
438 default: [],
439 },
440 o: {
441 alias: 'overwrite',
442 describe: 'Overwrites existing files',
443 type: 'boolean',
444 },
445 s: {
446 alias: 'silent',
447 describe: 'Suppresses printing errors',
448 type: 'boolean',
449 },
450 p: {
451 alias: 'project',
452 describe: 'Name of a project in GraphQL Config',
453 type: 'string',
454 },
455 };
456}
457function parseArgv(argv = process.argv) {
458 return yargs.options(buildOptions()).parse(argv);
459}
460async function createContext(cliFlags = parseArgv(process.argv)) {
461 if (cliFlags.require && cliFlags.require.length > 0) {
462 await Promise.all(cliFlags.require.map(mod => import(mod)));
463 }
464 const customConfigPath = getCustomConfigPath(cliFlags);
465 const context = await loadContext(customConfigPath);
466 updateContextWithCliFlags(context, cliFlags);
467 return context;
468}
469function updateContextWithCliFlags(context, cliFlags) {
470 const config = {
471 configFilePath: context.filepath,
472 };
473 if (cliFlags.watch) {
474 config.watch = cliFlags.watch;
475 }
476 if (cliFlags.overwrite === true) {
477 config.overwrite = cliFlags.overwrite;
478 }
479 if (cliFlags.silent === true) {
480 config.silent = cliFlags.silent;
481 }
482 if (cliFlags.project) {
483 context.useProject(cliFlags.project);
484 }
485 context.updateConfig(config);
486}
487class CodegenContext {
488 constructor({ config, graphqlConfig, filepath, }) {
489 this._config = config;
490 this._graphqlConfig = graphqlConfig;
491 this.filepath = this._graphqlConfig ? this._graphqlConfig.filepath : filepath;
492 this.cwd = this._graphqlConfig ? this._graphqlConfig.dirpath : process.cwd();
493 }
494 useProject(name) {
495 this._project = name;
496 }
497 getConfig() {
498 if (!this.config) {
499 if (this._graphqlConfig) {
500 const project = this._graphqlConfig.getProject(this._project);
501 this.config = {
502 ...project.extension('codegen'),
503 schema: project.schema,
504 documents: project.documents,
505 };
506 }
507 else {
508 this.config = this._config;
509 }
510 }
511 return this.config;
512 }
513 updateConfig(config) {
514 this.config = {
515 ...this.getConfig(),
516 ...config,
517 };
518 }
519 async loadSchema(pointer) {
520 if (this._graphqlConfig) {
521 // TODO: SchemaWithLoader won't work here
522 return this._graphqlConfig.getProject(this._project).loadSchema(pointer);
523 }
524 return loadSchema(pointer, this.getConfig());
525 }
526 async loadDocuments(pointer) {
527 if (this._graphqlConfig) {
528 // TODO: pointer won't work here
529 const documents = await this._graphqlConfig.getProject(this._project).loadDocuments(pointer);
530 return documents;
531 }
532 return loadDocuments(pointer, this.getConfig());
533 }
534}
535function ensureContext(input) {
536 return input instanceof CodegenContext ? input : new CodegenContext({ config: input });
537}
538
539const defaultLoader = (mod) => import(mod);
540async function executeCodegen(input) {
541 function wrapTask(task, source) {
542 return async () => {
543 try {
544 await Promise.resolve().then(() => task());
545 }
546 catch (error) {
547 if (source && !(error instanceof GraphQLError)) {
548 error.source = source;
549 }
550 throw error;
551 }
552 };
553 }
554 const context = ensureContext(input);
555 const config = context.getConfig();
556 const result = [];
557 const commonListrOptions = {
558 exitOnError: true,
559 };
560 const Listr = await import('listr').then(m => ('default' in m ? m.default : m));
561 let listr;
562 if (process.env.VERBOSE) {
563 listr = new Listr({
564 ...commonListrOptions,
565 renderer: 'verbose',
566 nonTTYRenderer: 'verbose',
567 });
568 }
569 else if (process.env.NODE_ENV === 'test') {
570 listr = new Listr({
571 ...commonListrOptions,
572 renderer: 'silent',
573 nonTTYRenderer: 'silent',
574 });
575 }
576 else {
577 listr = new Listr({
578 ...commonListrOptions,
579 renderer: config.silent ? 'silent' : Renderer,
580 nonTTYRenderer: config.silent ? 'silent' : 'default',
581 collapse: true,
582 clearOutput: false,
583 });
584 }
585 let rootConfig = {};
586 let rootSchemas;
587 let rootDocuments;
588 const generates = {};
589 async function normalize() {
590 /* Load Require extensions */
591 const requireExtensions = normalizeInstanceOrArray(config.require);
592 for (const mod of requireExtensions) {
593 await import(mod);
594 }
595 /* Root plugin config */
596 rootConfig = config.config || {};
597 /* Normalize root "schema" field */
598 rootSchemas = normalizeInstanceOrArray(config.schema);
599 /* Normalize root "documents" field */
600 rootDocuments = normalizeInstanceOrArray(config.documents);
601 /* Normalize "generators" field */
602 const generateKeys = Object.keys(config.generates || {});
603 if (generateKeys.length === 0) {
604 throw new DetailedError('Invalid Codegen Configuration!', `
605 Please make sure that your codegen config file contains the "generates" field, with a specification for the plugins you need.
606
607 It should looks like that:
608
609 schema:
610 - my-schema.graphql
611 generates:
612 my-file.ts:
613 - plugin1
614 - plugin2
615 - plugin3
616 `);
617 }
618 for (const filename of generateKeys) {
619 generates[filename] = normalizeOutputParam(config.generates[filename]);
620 if (generates[filename].plugins.length === 0) {
621 throw new DetailedError('Invalid Codegen Configuration!', `
622 Please make sure that your codegen config file has defined plugins list for output "${filename}".
623
624 It should looks like that:
625
626 schema:
627 - my-schema.graphql
628 generates:
629 my-file.ts:
630 - plugin1
631 - plugin2
632 - plugin3
633 `);
634 }
635 }
636 if (rootSchemas.length === 0 &&
637 Object.keys(generates).some(filename => !generates[filename].schema || generates[filename].schema.length === 0)) {
638 throw new DetailedError('Invalid Codegen Configuration!', `
639 Please make sure that your codegen config file contains either the "schema" field
640 or every generated file has its own "schema" field.
641
642 It should looks like that:
643 schema:
644 - my-schema.graphql
645
646 or:
647 generates:
648 path/to/output:
649 schema: my-schema.graphql
650 `);
651 }
652 }
653 listr.add({
654 title: 'Parse configuration',
655 task: () => normalize(),
656 });
657 listr.add({
658 title: 'Generate outputs',
659 task: () => {
660 return new Listr(Object.keys(generates).map(filename => {
661 const outputConfig = generates[filename];
662 const hasPreset = !!outputConfig.preset;
663 return {
664 title: hasPreset
665 ? `Generate to ${filename} (using EXPERIMENTAL preset "${outputConfig.preset}")`
666 : `Generate ${filename}`,
667 task: () => {
668 let outputSchemaAst;
669 let outputSchema;
670 const outputFileTemplateConfig = outputConfig.config || {};
671 const outputDocuments = [];
672 const outputSpecificSchemas = normalizeInstanceOrArray(outputConfig.schema);
673 const outputSpecificDocuments = normalizeInstanceOrArray(outputConfig.documents);
674 return new Listr([
675 {
676 title: 'Load GraphQL schemas',
677 task: wrapTask(async () => {
678 debugLog(`[CLI] Loading Schemas`);
679 const schemaPointerMap = {};
680 const allSchemaUnnormalizedPointers = [...rootSchemas, ...outputSpecificSchemas];
681 for (const unnormalizedPtr of allSchemaUnnormalizedPointers) {
682 if (typeof unnormalizedPtr === 'string') {
683 schemaPointerMap[unnormalizedPtr] = {};
684 }
685 else if (typeof unnormalizedPtr === 'object') {
686 Object.assign(schemaPointerMap, unnormalizedPtr);
687 }
688 }
689 outputSchemaAst = await context.loadSchema(schemaPointerMap);
690 outputSchema = parse(printSchemaWithDirectives(outputSchemaAst));
691 }, filename),
692 },
693 {
694 title: 'Load GraphQL documents',
695 task: wrapTask(async () => {
696 debugLog(`[CLI] Loading Documents`);
697 const allDocuments = [...rootDocuments, ...outputSpecificDocuments];
698 const documents = await context.loadDocuments(allDocuments);
699 if (documents.length > 0) {
700 outputDocuments.push(...documents);
701 }
702 }, filename),
703 },
704 {
705 title: 'Generate',
706 task: wrapTask(async () => {
707 debugLog(`[CLI] Generating output`);
708 const normalizedPluginsArray = normalizeConfig(outputConfig.plugins);
709 const pluginLoader = config.pluginLoader || defaultLoader;
710 const pluginPackages = await Promise.all(normalizedPluginsArray.map(plugin => getPluginByName(Object.keys(plugin)[0], pluginLoader)));
711 const pluginMap = {};
712 const preset = hasPreset
713 ? typeof outputConfig.preset === 'string'
714 ? await getPresetByName(outputConfig.preset, defaultLoader)
715 : outputConfig.preset
716 : null;
717 pluginPackages.forEach((pluginPackage, i) => {
718 const plugin = normalizedPluginsArray[i];
719 const name = Object.keys(plugin)[0];
720 pluginMap[name] = pluginPackage;
721 });
722 const mergedConfig = {
723 ...rootConfig,
724 ...(typeof outputFileTemplateConfig === 'string'
725 ? { value: outputFileTemplateConfig }
726 : outputFileTemplateConfig),
727 };
728 let outputs = [];
729 if (hasPreset) {
730 outputs = await preset.buildGeneratesSection({
731 baseOutputDir: filename,
732 presetConfig: outputConfig.presetConfig || {},
733 plugins: normalizedPluginsArray,
734 schema: outputSchema,
735 schemaAst: outputSchemaAst,
736 documents: outputDocuments,
737 config: mergedConfig,
738 pluginMap,
739 });
740 }
741 else {
742 outputs = [
743 {
744 filename,
745 plugins: normalizedPluginsArray,
746 schema: outputSchema,
747 schemaAst: outputSchemaAst,
748 documents: outputDocuments,
749 config: mergedConfig,
750 pluginMap,
751 },
752 ];
753 }
754 const process = async (outputArgs) => {
755 const output = await codegen(outputArgs);
756 result.push({
757 filename: outputArgs.filename,
758 content: output,
759 hooks: outputConfig.hooks || {},
760 });
761 };
762 await Promise.all(outputs.map(process));
763 }, filename),
764 },
765 ], {
766 // it stops when one of tasks failed
767 exitOnError: true,
768 });
769 },
770 };
771 }), {
772 // it doesn't stop when one of tasks failed, to finish at least some of outputs
773 exitOnError: false,
774 // run 4 at once
775 concurrent: 4,
776 });
777 },
778 });
779 await listr.run();
780 return result;
781}
782
783const DEFAULT_HOOKS = {
784 afterStart: [],
785 beforeDone: [],
786 onWatchTriggered: [],
787 onError: [],
788 afterOneFileWrite: [],
789 afterAllFileWrite: [],
790 beforeOneFileWrite: [],
791 beforeAllFileWrite: [],
792};
793function normalizeHooks(_hooks) {
794 const keys = Object.keys({
795 ...DEFAULT_HOOKS,
796 ...(_hooks || {}),
797 });
798 return keys.reduce((prev, hookName) => {
799 if (typeof _hooks[hookName] === 'string') {
800 return {
801 ...prev,
802 [hookName]: [_hooks[hookName]],
803 };
804 }
805 else if (Array.isArray(_hooks[hookName])) {
806 return {
807 ...prev,
808 [hookName]: _hooks[hookName],
809 };
810 }
811 else {
812 return prev;
813 }
814 }, {});
815}
816function execShellCommand(cmd) {
817 return new Promise((resolve, reject) => {
818 exec(cmd, {
819 env: {
820 ...process.env,
821 PATH: `${process.env.PATH}${delimiter}${process.cwd()}${sep}node_modules${sep}.bin`,
822 },
823 }, (error, stdout, stderr) => {
824 if (error) {
825 reject(error);
826 }
827 else {
828 resolve(stdout || stderr);
829 }
830 });
831 });
832}
833async function executeHooks(hookName, scripts = [], args = []) {
834 debugLog(`Running lifecycle hook "${hookName}" scripts...`);
835 for (const script of scripts) {
836 if (typeof script === 'string') {
837 debugLog(`Running lifecycle hook "${hookName}" script: ${script} with args: ${args.join(' ')}...`);
838 await execShellCommand(`${script} ${args.join(' ')}`);
839 }
840 else {
841 debugLog(`Running lifecycle hook "${hookName}" script: ${script.name} with args: ${args.join(' ')}...`);
842 await script(...args);
843 }
844 }
845}
846const lifecycleHooks = (_hooks = {}) => {
847 const hooks = normalizeHooks(_hooks);
848 return {
849 afterStart: async () => executeHooks('afterStart', hooks.afterStart),
850 onWatchTriggered: async (event, path) => executeHooks('onWatchTriggered', hooks.onWatchTriggered, [event, path]),
851 onError: async (error) => executeHooks('onError', hooks.onError, [`"${error}"`]),
852 afterOneFileWrite: async (path) => executeHooks('afterOneFileWrite', hooks.afterOneFileWrite, [path]),
853 afterAllFileWrite: async (paths) => executeHooks('afterAllFileWrite', hooks.afterAllFileWrite, paths),
854 beforeOneFileWrite: async (path) => executeHooks('beforeOneFileWrite', hooks.beforeOneFileWrite, [path]),
855 beforeAllFileWrite: async (paths) => executeHooks('beforeAllFileWrite', hooks.beforeAllFileWrite, paths),
856 beforeDone: async () => executeHooks('beforeDone', hooks.beforeDone),
857 };
858};
859
860function log(msg) {
861 // double spaces to inline the message with Listr
862 getLogger().info(` ${msg}`);
863}
864function emitWatching() {
865 log(`${logSymbols.info} Watching for changes...`);
866}
867const createWatcher = (initalContext, onNext) => {
868 debugLog(`[Watcher] Starting watcher...`);
869 let config = initalContext.getConfig();
870 const files = [initalContext.filepath].filter(a => a);
871 const documents = normalizeInstanceOrArray(config.documents);
872 const schemas = normalizeInstanceOrArray(config.schema);
873 // Add schemas and documents from "generates"
874 Object.keys(config.generates)
875 .map(filename => normalizeOutputParam(config.generates[filename]))
876 .forEach(conf => {
877 schemas.push(...normalizeInstanceOrArray(conf.schema));
878 documents.push(...normalizeInstanceOrArray(conf.documents));
879 });
880 if (documents) {
881 documents.forEach(doc => {
882 if (typeof doc === 'string') {
883 files.push(doc);
884 }
885 else {
886 files.push(...Object.keys(doc));
887 }
888 });
889 }
890 schemas.forEach((schema) => {
891 if (isGlob(schema) || isValidPath(schema)) {
892 files.push(schema);
893 }
894 });
895 if (typeof config.watch !== 'boolean') {
896 files.push(...normalizeInstanceOrArray(config.watch));
897 }
898 let watcher;
899 const runWatcher = async () => {
900 const chokidar = await import('chokidar');
901 let isShutdown = false;
902 const debouncedExec = debounce(() => {
903 if (!isShutdown) {
904 executeCodegen(initalContext)
905 .then(onNext, () => Promise.resolve())
906 .then(() => emitWatching());
907 }
908 }, 100);
909 emitWatching();
910 const ignored = [];
911 Object.keys(config.generates)
912 .map(filename => ({ filename, config: normalizeOutputParam(config.generates[filename]) }))
913 .forEach(entry => {
914 if (entry.config.preset) {
915 const extension = entry.config.presetConfig && entry.config.presetConfig.extension;
916 if (extension) {
917 ignored.push(join(entry.filename, '**', '*' + extension));
918 }
919 }
920 else {
921 ignored.push(entry.filename);
922 }
923 });
924 watcher = chokidar.watch(files, {
925 persistent: true,
926 ignoreInitial: true,
927 followSymlinks: true,
928 cwd: process.cwd(),
929 disableGlobbing: false,
930 usePolling: true,
931 interval: 100,
932 binaryInterval: 300,
933 depth: 99,
934 awaitWriteFinish: true,
935 ignorePermissionErrors: false,
936 atomic: true,
937 ignored,
938 });
939 debugLog(`[Watcher] Started`);
940 const shutdown = () => {
941 isShutdown = true;
942 debugLog(`[Watcher] Shutting down`);
943 log(`Shutting down watch...`);
944 watcher.close();
945 lifecycleHooks(config.hooks).beforeDone();
946 };
947 // it doesn't matter what has changed, need to run whole process anyway
948 watcher.on('all', async (eventName, path) => {
949 lifecycleHooks(config.hooks).onWatchTriggered(eventName, path);
950 debugLog(`[Watcher] triggered due to a file ${eventName} event: ${path}`);
951 const fullPath = join(process.cwd(), path);
952 delete require.cache[fullPath];
953 if (eventName === 'change' && config.configFilePath && fullPath === config.configFilePath) {
954 log(`${logSymbols.info} Config file has changed, reloading...`);
955 const context = await loadContext(config.configFilePath);
956 const newParsedConfig = context.getConfig();
957 newParsedConfig.watch = config.watch;
958 newParsedConfig.silent = config.silent;
959 newParsedConfig.overwrite = config.overwrite;
960 newParsedConfig.configFilePath = config.configFilePath;
961 config = newParsedConfig;
962 }
963 debouncedExec();
964 });
965 process.once('SIGINT', shutdown);
966 process.once('SIGTERM', shutdown);
967 };
968 // the promise never resolves to keep process running
969 return new Promise((resolve, reject) => {
970 executeCodegen(initalContext)
971 .then(onNext, () => Promise.resolve())
972 .then(runWatcher)
973 .catch(err => {
974 watcher.close();
975 reject(err);
976 });
977 });
978};
979
980function writeSync(filepath, content) {
981 return writeFileSync(filepath, content);
982}
983function readSync(filepath) {
984 return readFileSync(filepath, 'utf-8');
985}
986function fileExists(filePath) {
987 try {
988 return statSync(filePath).isFile();
989 }
990 catch (err) {
991 return false;
992 }
993}
994function unlinkFile(filePath, cb) {
995 unlink(filePath, cb);
996}
997
998const hash = (content) => createHash('sha1').update(content).digest('base64');
999async function generate(input, saveToFile = true) {
1000 const context = ensureContext(input);
1001 const config = context.getConfig();
1002 await lifecycleHooks(config.hooks).afterStart();
1003 let previouslyGeneratedFilenames = [];
1004 function removeStaleFiles(config, generationResult) {
1005 const filenames = generationResult.map(o => o.filename);
1006 // find stale files from previous build which are not present in current build
1007 const staleFilenames = previouslyGeneratedFilenames.filter(f => !filenames.includes(f));
1008 staleFilenames.forEach(filename => {
1009 if (shouldOverwrite(config, filename)) {
1010 unlinkFile(filename, err => {
1011 const prettyFilename = filename.replace(`${input.cwd || process.cwd()}/`, '');
1012 if (err) {
1013 debugLog(`Cannot remove stale file: ${prettyFilename}\n${err}`);
1014 }
1015 else {
1016 debugLog(`Removed stale file: ${prettyFilename}`);
1017 }
1018 });
1019 }
1020 });
1021 previouslyGeneratedFilenames = filenames;
1022 }
1023 const recentOutputHash = new Map();
1024 async function writeOutput(generationResult) {
1025 if (!saveToFile) {
1026 return generationResult;
1027 }
1028 if (config.watch) {
1029 removeStaleFiles(config, generationResult);
1030 }
1031 await lifecycleHooks(config.hooks).beforeAllFileWrite(generationResult.map(r => r.filename));
1032 await Promise.all(generationResult.map(async (result) => {
1033 const exists = fileExists(result.filename);
1034 if (!shouldOverwrite(config, result.filename) && exists) {
1035 return;
1036 }
1037 const content = result.content || '';
1038 const currentHash = hash(content);
1039 let previousHash = recentOutputHash.get(result.filename);
1040 if (!previousHash && exists) {
1041 previousHash = hash(readSync(result.filename));
1042 }
1043 if (previousHash && currentHash === previousHash) {
1044 debugLog(`Skipping file (${result.filename}) writing due to indentical hash...`);
1045 return;
1046 }
1047 if (content.length === 0) {
1048 return;
1049 }
1050 recentOutputHash.set(result.filename, currentHash);
1051 const basedir = dirname(result.filename);
1052 await lifecycleHooks(result.hooks).beforeOneFileWrite(result.filename);
1053 await lifecycleHooks(config.hooks).beforeOneFileWrite(result.filename);
1054 sync(basedir);
1055 const absolutePath = isAbsolute(result.filename)
1056 ? result.filename
1057 : join(input.cwd || process.cwd(), result.filename);
1058 writeSync(absolutePath, result.content);
1059 await lifecycleHooks(result.hooks).afterOneFileWrite(result.filename);
1060 await lifecycleHooks(config.hooks).afterOneFileWrite(result.filename);
1061 }));
1062 await lifecycleHooks(config.hooks).afterAllFileWrite(generationResult.map(r => r.filename));
1063 return generationResult;
1064 }
1065 // watch mode
1066 if (config.watch) {
1067 return createWatcher(context, writeOutput);
1068 }
1069 const outputFiles = await executeCodegen(context);
1070 await writeOutput(outputFiles);
1071 lifecycleHooks(config.hooks).beforeDone();
1072 return outputFiles;
1073}
1074function shouldOverwrite(config, outputPath) {
1075 const globalValue = config.overwrite === undefined ? true : !!config.overwrite;
1076 const outputConfig = config.generates[outputPath];
1077 if (!outputConfig) {
1078 debugLog(`Couldn't find a config of ${outputPath}`);
1079 return globalValue;
1080 }
1081 if (isConfiguredOutput(outputConfig) && typeof outputConfig.overwrite === 'boolean') {
1082 return outputConfig.overwrite;
1083 }
1084 return globalValue;
1085}
1086function isConfiguredOutput(output) {
1087 return typeof output.plugins !== 'undefined';
1088}
1089
1090// Parses config and writes it to a file
1091async function writeConfig(answers, config) {
1092 const YAML = await import('json-to-pretty-yaml').then(m => ('default' in m ? m.default : m));
1093 const ext = answers.config.toLocaleLowerCase().endsWith('.json') ? 'json' : 'yml';
1094 const content = ext === 'json' ? JSON.stringify(config) : YAML.stringify(config);
1095 const fullPath = resolve(process.cwd(), answers.config);
1096 const relativePath = relative(process.cwd(), answers.config);
1097 writeFileSync(fullPath, content, {
1098 encoding: 'utf-8',
1099 });
1100 return {
1101 relativePath,
1102 fullPath,
1103 };
1104}
1105// Updates package.json (script and plugins as dependencies)
1106async function writePackage(answers, configLocation) {
1107 // script
1108 const pkgPath = resolve(process.cwd(), 'package.json');
1109 const pkgContent = readFileSync(pkgPath, {
1110 encoding: 'utf-8',
1111 });
1112 const pkg = JSON.parse(pkgContent);
1113 const { indent } = detectIndent(pkgContent);
1114 if (!pkg.scripts) {
1115 pkg.scripts = {};
1116 }
1117 pkg.scripts[answers.script] = `graphql-codegen --config ${configLocation}`;
1118 // plugin
1119 if (!pkg.devDependencies) {
1120 pkg.devDependencies = {};
1121 }
1122 // read codegen's version
1123 let version;
1124 const dynamicImport = (m) => import(m).then(m => ('default' in m ? m.default : m));
1125 try {
1126 // Works in tests
1127 const packageJson = await dynamicImport('../../package.json');
1128 version = packageJson.version;
1129 }
1130 catch (e) {
1131 // Works in production (package dist is flat, everything is in the same folder)
1132 const packageJson = await dynamicImport('./package.json');
1133 version = packageJson.version;
1134 }
1135 answers.plugins.forEach(plugin => {
1136 pkg.devDependencies[plugin.package] = version;
1137 });
1138 if (answers.introspection) {
1139 pkg.devDependencies['@graphql-codegen/introspection'] = version;
1140 }
1141 // If cli haven't installed yet
1142 pkg.devDependencies['@graphql-codegen/cli'] = version;
1143 writeFileSync(pkgPath, JSON.stringify(pkg, null, indent));
1144}
1145function bold(str) {
1146 return chalk.bold(str);
1147}
1148function grey(str) {
1149 return chalk.grey(str);
1150}
1151function italic(str) {
1152 return chalk.italic(str);
1153}
1154
1155var Tags;
1156(function (Tags) {
1157 Tags["browser"] = "Browser";
1158 Tags["node"] = "Node";
1159 Tags["typescript"] = "TypeScript";
1160 Tags["flow"] = "Flow";
1161 Tags["angular"] = "Angular";
1162 Tags["stencil"] = "Stencil";
1163 Tags["react"] = "React";
1164 Tags["vue"] = "Vue";
1165})(Tags || (Tags = {}));
1166
1167const plugins = [
1168 {
1169 name: `TypeScript ${italic('(required by other typescript plugins)')}`,
1170 package: '@graphql-codegen/typescript',
1171 value: 'typescript',
1172 pathInRepo: 'typescript/typescript',
1173 available: hasTag(Tags.typescript),
1174 shouldBeSelected: tags => oneOf(tags, Tags.angular, Tags.stencil) || allOf(tags, Tags.typescript, Tags.react) || noneOf(tags, Tags.flow),
1175 defaultExtension: '.ts',
1176 },
1177 {
1178 name: `TypeScript Operations ${italic('(operations and fragments)')}`,
1179 package: '@graphql-codegen/typescript-operations',
1180 value: 'typescript-operations',
1181 pathInRepo: 'typescript/operations',
1182 available: tags => allOf(tags, Tags.browser, Tags.typescript),
1183 shouldBeSelected: tags => oneOf(tags, Tags.angular, Tags.stencil) || allOf(tags, Tags.typescript, Tags.react),
1184 defaultExtension: '.ts',
1185 },
1186 {
1187 name: `TypeScript Resolvers ${italic('(strongly typed resolve functions)')}`,
1188 package: '@graphql-codegen/typescript-resolvers',
1189 value: 'typescript-resolvers',
1190 pathInRepo: 'typescript/resolvers',
1191 available: tags => allOf(tags, Tags.node, Tags.typescript),
1192 shouldBeSelected: tags => noneOf(tags, Tags.flow),
1193 defaultExtension: '.ts',
1194 },
1195 {
1196 name: `Flow ${italic('(required by other flow plugins)')}`,
1197 package: '@graphql-codegen/flow',
1198 value: 'flow',
1199 pathInRepo: 'flow/flow',
1200 available: hasTag(Tags.flow),
1201 shouldBeSelected: tags => noneOf(tags, Tags.typescript),
1202 defaultExtension: '.js',
1203 },
1204 {
1205 name: `Flow Operations ${italic('(operations and fragments)')}`,
1206 package: '@graphql-codegen/flow-operations',
1207 value: 'flow-operations',
1208 pathInRepo: 'flow/operations',
1209 available: tags => allOf(tags, Tags.browser, Tags.flow),
1210 shouldBeSelected: tags => noneOf(tags, Tags.typescript),
1211 defaultExtension: '.js',
1212 },
1213 {
1214 name: `Flow Resolvers ${italic('(strongly typed resolve functions)')}`,
1215 package: '@graphql-codegen/flow-resolvers',
1216 value: 'flow-resolvers',
1217 pathInRepo: 'flow/resolvers',
1218 available: tags => allOf(tags, Tags.node, Tags.flow),
1219 shouldBeSelected: tags => noneOf(tags, Tags.typescript),
1220 defaultExtension: '.js',
1221 },
1222 {
1223 name: `TypeScript Apollo Angular ${italic('(typed GQL services)')}`,
1224 package: '@graphql-codegen/typescript-apollo-angular',
1225 value: 'typescript-apollo-angular',
1226 pathInRepo: 'typescript/apollo-angular',
1227 available: hasTag(Tags.angular),
1228 shouldBeSelected: () => true,
1229 defaultExtension: '.js',
1230 },
1231 {
1232 name: `TypeScript Vue Apollo ${italic('(typed composition functions)')}`,
1233 package: '@graphql-codegen/typescript-vue-apollo',
1234 value: 'typescript-vue-apollo',
1235 pathInRepo: 'typescript/vue-apollo',
1236 available: tags => allOf(tags, Tags.vue, Tags.typescript),
1237 shouldBeSelected: () => true,
1238 defaultExtension: '.ts',
1239 },
1240 {
1241 name: `TypeScript React Apollo ${italic('(typed components and HOCs)')}`,
1242 package: '@graphql-codegen/typescript-react-apollo',
1243 value: 'typescript-react-apollo',
1244 pathInRepo: 'typescript/react-apollo',
1245 available: tags => allOf(tags, Tags.react, Tags.typescript),
1246 shouldBeSelected: () => true,
1247 defaultExtension: '.tsx',
1248 },
1249 {
1250 name: `TypeScript Stencil Apollo ${italic('(typed components)')}`,
1251 package: '@graphql-codegen/typescript-stencil-apollo',
1252 value: 'typescript-stencil-apollo',
1253 pathInRepo: 'typescript/stencil-apollo',
1254 available: hasTag(Tags.stencil),
1255 shouldBeSelected: () => true,
1256 defaultExtension: '.tsx',
1257 },
1258 {
1259 name: `TypeScript MongoDB ${italic('(typed MongoDB objects)')}`,
1260 package: '@graphql-codegen/typescript-mongodb',
1261 value: 'typescript-mongodb',
1262 pathInRepo: 'typescript/mongodb',
1263 available: tags => allOf(tags, Tags.node, Tags.typescript),
1264 shouldBeSelected: () => false,
1265 defaultExtension: '.ts',
1266 },
1267 {
1268 name: `TypeScript GraphQL files modules ${italic('(declarations for .graphql files)')}`,
1269 package: '@graphql-codegen/typescript-graphql-files-modules',
1270 value: 'typescript-graphql-files-modules',
1271 pathInRepo: 'typescript/graphql-files-modules',
1272 available: tags => allOf(tags, Tags.browser, Tags.typescript),
1273 shouldBeSelected: () => false,
1274 defaultExtension: '.ts',
1275 },
1276 {
1277 name: `TypeScript GraphQL document nodes ${italic('(embedded GraphQL document)')}`,
1278 package: '@graphql-codegen/typescript-document-nodes',
1279 value: 'typescript-document-nodes',
1280 pathInRepo: 'typescript/document-nodes',
1281 available: tags => allOf(tags, Tags.typescript),
1282 shouldBeSelected: () => false,
1283 defaultExtension: '.ts',
1284 },
1285 {
1286 name: `Introspection Fragment Matcher ${italic('(for Apollo Client)')}`,
1287 package: '@graphql-codegen/fragment-matcher',
1288 value: 'fragment-matcher',
1289 pathInRepo: 'other/fragment-matcher',
1290 available: hasTag(Tags.browser),
1291 shouldBeSelected: () => false,
1292 defaultExtension: '.ts',
1293 },
1294];
1295function hasTag(tag) {
1296 return (tags) => tags.includes(tag);
1297}
1298function oneOf(list, ...items) {
1299 return list.some(i => items.includes(i));
1300}
1301function noneOf(list, ...items) {
1302 return !list.some(i => items.includes(i));
1303}
1304function allOf(list, ...items) {
1305 return items.every(i => list.includes(i));
1306}
1307
1308function getQuestions(possibleTargets) {
1309 return [
1310 {
1311 type: 'checkbox',
1312 name: 'targets',
1313 message: `What type of application are you building?`,
1314 choices: getApplicationTypeChoices(possibleTargets),
1315 validate: ((targets) => targets.length > 0),
1316 },
1317 {
1318 type: 'input',
1319 name: 'schema',
1320 message: 'Where is your schema?:',
1321 suffix: grey(' (path or url)'),
1322 default: 'http://localhost:4000',
1323 validate: (str) => str.length > 0,
1324 },
1325 {
1326 type: 'input',
1327 name: 'documents',
1328 message: 'Where are your operations and fragments?:',
1329 when: answers => {
1330 // flatten targets
1331 // I can't find an API in Inquirer that would do that
1332 answers.targets = normalizeTargets(answers.targets);
1333 return answers.targets.includes(Tags.browser);
1334 },
1335 default: 'src/**/*.graphql',
1336 validate: (str) => str.length > 0,
1337 },
1338 {
1339 type: 'checkbox',
1340 name: 'plugins',
1341 message: 'Pick plugins:',
1342 choices: getPluginChoices,
1343 validate: ((plugins) => plugins.length > 0),
1344 },
1345 {
1346 type: 'input',
1347 name: 'output',
1348 message: 'Where to write the output:',
1349 default: getOutputDefaultValue,
1350 validate: (str) => str.length > 0,
1351 },
1352 {
1353 type: 'confirm',
1354 name: 'introspection',
1355 message: 'Do you want to generate an introspection file?',
1356 },
1357 {
1358 type: 'input',
1359 name: 'config',
1360 message: 'How to name the config file?',
1361 default: 'codegen.yml',
1362 validate: (str) => {
1363 const isNotEmpty = str.length > 0;
1364 const hasCorrectExtension = ['json', 'yml', 'yaml'].some(ext => str.toLocaleLowerCase().endsWith(`.${ext}`));
1365 return isNotEmpty && hasCorrectExtension;
1366 },
1367 },
1368 {
1369 type: 'input',
1370 name: 'script',
1371 message: 'What script in package.json should run the codegen?',
1372 validate: (str) => str.length > 0,
1373 },
1374 ];
1375}
1376function getApplicationTypeChoices(possibleTargets) {
1377 function withFlowOrTypescript(tags) {
1378 if (possibleTargets.TypeScript) {
1379 tags.push(Tags.typescript);
1380 }
1381 else if (possibleTargets.Flow) {
1382 tags.push(Tags.flow);
1383 }
1384 else {
1385 tags.push(Tags.flow, Tags.typescript);
1386 }
1387 return tags;
1388 }
1389 return [
1390 {
1391 name: 'Backend - API or server',
1392 key: 'backend',
1393 value: withFlowOrTypescript([Tags.node]),
1394 checked: possibleTargets.Node,
1395 },
1396 {
1397 name: 'Application built with Angular',
1398 key: 'angular',
1399 value: [Tags.angular, Tags.browser, Tags.typescript],
1400 checked: possibleTargets.Angular,
1401 },
1402 {
1403 name: 'Application built with React',
1404 key: 'react',
1405 value: withFlowOrTypescript([Tags.react, Tags.browser]),
1406 checked: possibleTargets.React,
1407 },
1408 {
1409 name: 'Application built with Stencil',
1410 key: 'stencil',
1411 value: [Tags.stencil, Tags.browser, Tags.typescript],
1412 checked: possibleTargets.Stencil,
1413 },
1414 {
1415 name: 'Application built with other framework or vanilla JS',
1416 key: 'client',
1417 value: [Tags.browser, Tags.typescript, Tags.flow],
1418 checked: possibleTargets.Browser && !possibleTargets.Angular && !possibleTargets.React && !possibleTargets.Stencil,
1419 },
1420 ];
1421}
1422function getPluginChoices(answers) {
1423 return plugins
1424 .filter(p => p.available(answers.targets))
1425 .map(p => {
1426 return {
1427 name: p.name,
1428 value: p,
1429 checked: p.shouldBeSelected(answers.targets),
1430 };
1431 });
1432}
1433function normalizeTargets(targets) {
1434 return [].concat(...targets);
1435}
1436function getOutputDefaultValue(answers) {
1437 if (answers.plugins.some(plugin => plugin.defaultExtension === '.tsx')) {
1438 return 'src/generated/graphql.tsx';
1439 }
1440 else if (answers.plugins.some(plugin => plugin.defaultExtension === '.ts')) {
1441 return 'src/generated/graphql.ts';
1442 }
1443 else {
1444 return 'src/generated/graphql.js';
1445 }
1446}
1447
1448async function guessTargets() {
1449 const pkg = JSON.parse(readFileSync(resolve(process.cwd(), 'package.json'), {
1450 encoding: 'utf-8',
1451 }));
1452 const dependencies = Object.keys({
1453 ...pkg.dependencies,
1454 ...pkg.devDependencies,
1455 });
1456 return {
1457 [Tags.angular]: isAngular(dependencies),
1458 [Tags.react]: isReact(dependencies),
1459 [Tags.stencil]: isStencil(dependencies),
1460 [Tags.vue]: isVue(dependencies),
1461 [Tags.browser]: false,
1462 [Tags.node]: false,
1463 [Tags.typescript]: isTypescript(dependencies),
1464 [Tags.flow]: isFlow(dependencies),
1465 };
1466}
1467function isAngular(dependencies) {
1468 return dependencies.includes('@angular/core');
1469}
1470function isReact(dependencies) {
1471 return dependencies.includes('react');
1472}
1473function isStencil(dependencies) {
1474 return dependencies.includes('@stencil/core');
1475}
1476function isVue(dependencies) {
1477 return dependencies.includes('vue') || dependencies.includes('nuxt');
1478}
1479function isTypescript(dependencies) {
1480 return dependencies.includes('typescript');
1481}
1482function isFlow(dependencies) {
1483 return dependencies.includes('flow');
1484}
1485
1486function log$1(...msgs) {
1487 // eslint-disable-next-line no-console
1488 console.log(...msgs);
1489}
1490async function init() {
1491 log$1(`
1492 Welcome to ${bold('GraphQL Code Generator')}!
1493 Answer few questions and we will setup everything for you.
1494 `);
1495 const possibleTargets = await guessTargets();
1496 const answers = await inquirer.prompt(getQuestions(possibleTargets));
1497 // define config
1498 const config = {
1499 overwrite: true,
1500 schema: answers.schema,
1501 documents: answers.targets.includes(Tags.browser) ? answers.documents : null,
1502 generates: {
1503 [answers.output]: {
1504 plugins: answers.plugins.map(p => p.value),
1505 },
1506 },
1507 };
1508 // introspection
1509 if (answers.introspection) {
1510 addIntrospection(config);
1511 }
1512 // config file
1513 const { relativePath } = await writeConfig(answers, config);
1514 // write package.json
1515 await writePackage(answers, relativePath);
1516 // Emit status to the terminal
1517 log$1(`
1518 Config file generated at ${bold(relativePath)}
1519
1520 ${bold('$ npm install')}
1521
1522 To install the plugins.
1523
1524 ${bold(`$ npm run ${answers.script}`)}
1525
1526 To run GraphQL Code Generator.
1527 `);
1528}
1529// adds an introspection to `generates`
1530function addIntrospection(config) {
1531 config.generates['./graphql.schema.json'] = {
1532 plugins: ['introspection'],
1533 };
1534}
1535
1536const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
1537const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
1538
1539function cliError(err, exitOnError = true) {
1540 let msg;
1541 if (err instanceof Error) {
1542 msg = err.message || err.toString();
1543 }
1544 else if (typeof err === 'string') {
1545 msg = err;
1546 }
1547 else {
1548 msg = JSON.stringify(err);
1549 }
1550 // eslint-disable-next-line no-console
1551 console.error(msg);
1552 if (exitOnError && isNode) {
1553 process.exit(1);
1554 }
1555 else if (exitOnError && isBrowser) {
1556 throw err;
1557 }
1558}
1559
1560function runCli(cmd) {
1561 ensureGraphQlPackage();
1562 switch (cmd) {
1563 case 'init':
1564 return init();
1565 default: {
1566 return createContext().then(context => {
1567 return generate(context).catch(async (error) => {
1568 await lifecycleHooks(context.getConfig().hooks).onError(error.toString());
1569 throw error;
1570 });
1571 });
1572 }
1573 }
1574}
1575function ensureGraphQlPackage() {
1576 try {
1577 require('graphql');
1578 }
1579 catch (e) {
1580 throw new DetailedError(`Unable to load "graphql" package. Please make sure to install "graphql" as a dependency!`, `
1581 To install "graphql", run:
1582 yarn add graphql
1583 Or, with NPM:
1584 npm install --save graphql
1585`);
1586 }
1587}
1588
1589export { CodegenContext, CodegenExtension, buildOptions, cliError, createContext, ensureContext, ensureGraphQlPackage, executeCodegen, findAndLoadGraphQLConfig, generate, init, loadContext, parseArgv, runCli, updateContextWithCliFlags };
1590//# sourceMappingURL=index.esm.js.map