UNPKG

59 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._pluginContext = {};
490 this._config = config;
491 this._graphqlConfig = graphqlConfig;
492 this.filepath = this._graphqlConfig ? this._graphqlConfig.filepath : filepath;
493 this.cwd = this._graphqlConfig ? this._graphqlConfig.dirpath : process.cwd();
494 }
495 useProject(name) {
496 this._project = name;
497 }
498 getConfig() {
499 if (!this.config) {
500 if (this._graphqlConfig) {
501 const project = this._graphqlConfig.getProject(this._project);
502 this.config = {
503 ...project.extension('codegen'),
504 schema: project.schema,
505 documents: project.documents,
506 pluginContext: this._pluginContext,
507 };
508 }
509 else {
510 this.config = { ...this._config, pluginContext: this._pluginContext };
511 }
512 }
513 return this.config;
514 }
515 updateConfig(config) {
516 this.config = {
517 ...this.getConfig(),
518 ...config,
519 };
520 }
521 getPluginContext() {
522 return this._pluginContext;
523 }
524 async loadSchema(pointer) {
525 if (this._graphqlConfig) {
526 // TODO: SchemaWithLoader won't work here
527 return this._graphqlConfig.getProject(this._project).loadSchema(pointer);
528 }
529 return loadSchema(pointer, this.getConfig());
530 }
531 async loadDocuments(pointer) {
532 if (this._graphqlConfig) {
533 // TODO: pointer won't work here
534 const documents = await this._graphqlConfig.getProject(this._project).loadDocuments(pointer);
535 return documents;
536 }
537 return loadDocuments(pointer, this.getConfig());
538 }
539}
540function ensureContext(input) {
541 return input instanceof CodegenContext ? input : new CodegenContext({ config: input });
542}
543
544const defaultLoader = (mod) => import(mod);
545async function executeCodegen(input) {
546 function wrapTask(task, source) {
547 return async () => {
548 try {
549 await Promise.resolve().then(() => task());
550 }
551 catch (error) {
552 if (source && !(error instanceof GraphQLError)) {
553 error.source = source;
554 }
555 throw error;
556 }
557 };
558 }
559 const context = ensureContext(input);
560 const config = context.getConfig();
561 const pluginContext = context.getPluginContext();
562 const result = [];
563 const commonListrOptions = {
564 exitOnError: true,
565 };
566 const Listr = await import('listr').then(m => ('default' in m ? m.default : m));
567 let listr;
568 if (process.env.VERBOSE) {
569 listr = new Listr({
570 ...commonListrOptions,
571 renderer: 'verbose',
572 nonTTYRenderer: 'verbose',
573 });
574 }
575 else if (process.env.NODE_ENV === 'test') {
576 listr = new Listr({
577 ...commonListrOptions,
578 renderer: 'silent',
579 nonTTYRenderer: 'silent',
580 });
581 }
582 else {
583 listr = new Listr({
584 ...commonListrOptions,
585 renderer: config.silent ? 'silent' : Renderer,
586 nonTTYRenderer: config.silent ? 'silent' : 'default',
587 collapse: true,
588 clearOutput: false,
589 });
590 }
591 let rootConfig = {};
592 let rootSchemas;
593 let rootDocuments;
594 const generates = {};
595 async function normalize() {
596 /* Load Require extensions */
597 const requireExtensions = normalizeInstanceOrArray(config.require);
598 for (const mod of requireExtensions) {
599 await import(mod);
600 }
601 /* Root plugin config */
602 rootConfig = config.config || {};
603 /* Normalize root "schema" field */
604 rootSchemas = normalizeInstanceOrArray(config.schema);
605 /* Normalize root "documents" field */
606 rootDocuments = normalizeInstanceOrArray(config.documents);
607 /* Normalize "generators" field */
608 const generateKeys = Object.keys(config.generates || {});
609 if (generateKeys.length === 0) {
610 throw new DetailedError('Invalid Codegen Configuration!', `
611 Please make sure that your codegen config file contains the "generates" field, with a specification for the plugins you need.
612
613 It should looks like that:
614
615 schema:
616 - my-schema.graphql
617 generates:
618 my-file.ts:
619 - plugin1
620 - plugin2
621 - plugin3
622 `);
623 }
624 for (const filename of generateKeys) {
625 generates[filename] = normalizeOutputParam(config.generates[filename]);
626 if (generates[filename].plugins.length === 0) {
627 throw new DetailedError('Invalid Codegen Configuration!', `
628 Please make sure that your codegen config file has defined plugins list for output "${filename}".
629
630 It should looks like that:
631
632 schema:
633 - my-schema.graphql
634 generates:
635 my-file.ts:
636 - plugin1
637 - plugin2
638 - plugin3
639 `);
640 }
641 }
642 if (rootSchemas.length === 0 &&
643 Object.keys(generates).some(filename => !generates[filename].schema || generates[filename].schema.length === 0)) {
644 throw new DetailedError('Invalid Codegen Configuration!', `
645 Please make sure that your codegen config file contains either the "schema" field
646 or every generated file has its own "schema" field.
647
648 It should looks like that:
649 schema:
650 - my-schema.graphql
651
652 or:
653 generates:
654 path/to/output:
655 schema: my-schema.graphql
656 `);
657 }
658 }
659 listr.add({
660 title: 'Parse configuration',
661 task: () => normalize(),
662 });
663 listr.add({
664 title: 'Generate outputs',
665 task: () => {
666 return new Listr(Object.keys(generates).map(filename => {
667 const outputConfig = generates[filename];
668 const hasPreset = !!outputConfig.preset;
669 return {
670 title: hasPreset
671 ? `Generate to ${filename} (using EXPERIMENTAL preset "${outputConfig.preset}")`
672 : `Generate ${filename}`,
673 task: () => {
674 let outputSchemaAst;
675 let outputSchema;
676 const outputFileTemplateConfig = outputConfig.config || {};
677 const outputDocuments = [];
678 const outputSpecificSchemas = normalizeInstanceOrArray(outputConfig.schema);
679 const outputSpecificDocuments = normalizeInstanceOrArray(outputConfig.documents);
680 return new Listr([
681 {
682 title: 'Load GraphQL schemas',
683 task: wrapTask(async () => {
684 debugLog(`[CLI] Loading Schemas`);
685 const schemaPointerMap = {};
686 const allSchemaUnnormalizedPointers = [...rootSchemas, ...outputSpecificSchemas];
687 for (const unnormalizedPtr of allSchemaUnnormalizedPointers) {
688 if (typeof unnormalizedPtr === 'string') {
689 schemaPointerMap[unnormalizedPtr] = {};
690 }
691 else if (typeof unnormalizedPtr === 'object') {
692 Object.assign(schemaPointerMap, unnormalizedPtr);
693 }
694 }
695 outputSchemaAst = await context.loadSchema(schemaPointerMap);
696 outputSchema = parse(printSchemaWithDirectives(outputSchemaAst));
697 }, filename),
698 },
699 {
700 title: 'Load GraphQL documents',
701 task: wrapTask(async () => {
702 debugLog(`[CLI] Loading Documents`);
703 const allDocuments = [...rootDocuments, ...outputSpecificDocuments];
704 const documents = await context.loadDocuments(allDocuments);
705 if (documents.length > 0) {
706 outputDocuments.push(...documents);
707 }
708 }, filename),
709 },
710 {
711 title: 'Generate',
712 task: wrapTask(async () => {
713 debugLog(`[CLI] Generating output`);
714 const normalizedPluginsArray = normalizeConfig(outputConfig.plugins);
715 const pluginLoader = config.pluginLoader || defaultLoader;
716 const pluginPackages = await Promise.all(normalizedPluginsArray.map(plugin => getPluginByName(Object.keys(plugin)[0], pluginLoader)));
717 const pluginMap = {};
718 const preset = hasPreset
719 ? typeof outputConfig.preset === 'string'
720 ? await getPresetByName(outputConfig.preset, defaultLoader)
721 : outputConfig.preset
722 : null;
723 pluginPackages.forEach((pluginPackage, i) => {
724 const plugin = normalizedPluginsArray[i];
725 const name = Object.keys(plugin)[0];
726 pluginMap[name] = pluginPackage;
727 });
728 const mergedConfig = {
729 ...rootConfig,
730 ...(typeof outputFileTemplateConfig === 'string'
731 ? { value: outputFileTemplateConfig }
732 : outputFileTemplateConfig),
733 };
734 let outputs = [];
735 if (hasPreset) {
736 outputs = await preset.buildGeneratesSection({
737 baseOutputDir: filename,
738 presetConfig: outputConfig.presetConfig || {},
739 plugins: normalizedPluginsArray,
740 schema: outputSchema,
741 schemaAst: outputSchemaAst,
742 documents: outputDocuments,
743 config: mergedConfig,
744 pluginMap,
745 pluginContext,
746 });
747 }
748 else {
749 outputs = [
750 {
751 filename,
752 plugins: normalizedPluginsArray,
753 schema: outputSchema,
754 schemaAst: outputSchemaAst,
755 documents: outputDocuments,
756 config: mergedConfig,
757 pluginMap,
758 pluginContext,
759 },
760 ];
761 }
762 const process = async (outputArgs) => {
763 const output = await codegen(outputArgs);
764 result.push({
765 filename: outputArgs.filename,
766 content: output,
767 hooks: outputConfig.hooks || {},
768 });
769 };
770 await Promise.all(outputs.map(process));
771 }, filename),
772 },
773 ], {
774 // it stops when one of tasks failed
775 exitOnError: true,
776 });
777 },
778 };
779 }), {
780 // it doesn't stop when one of tasks failed, to finish at least some of outputs
781 exitOnError: false,
782 // run 4 at once
783 concurrent: 4,
784 });
785 },
786 });
787 await listr.run();
788 return result;
789}
790
791const DEFAULT_HOOKS = {
792 afterStart: [],
793 beforeDone: [],
794 onWatchTriggered: [],
795 onError: [],
796 afterOneFileWrite: [],
797 afterAllFileWrite: [],
798 beforeOneFileWrite: [],
799 beforeAllFileWrite: [],
800};
801function normalizeHooks(_hooks) {
802 const keys = Object.keys({
803 ...DEFAULT_HOOKS,
804 ...(_hooks || {}),
805 });
806 return keys.reduce((prev, hookName) => {
807 if (typeof _hooks[hookName] === 'string') {
808 return {
809 ...prev,
810 [hookName]: [_hooks[hookName]],
811 };
812 }
813 else if (Array.isArray(_hooks[hookName])) {
814 return {
815 ...prev,
816 [hookName]: _hooks[hookName],
817 };
818 }
819 else {
820 return prev;
821 }
822 }, {});
823}
824function execShellCommand(cmd) {
825 return new Promise((resolve, reject) => {
826 exec(cmd, {
827 env: {
828 ...process.env,
829 PATH: `${process.env.PATH}${delimiter}${process.cwd()}${sep}node_modules${sep}.bin`,
830 },
831 }, (error, stdout, stderr) => {
832 if (error) {
833 reject(error);
834 }
835 else {
836 resolve(stdout || stderr);
837 }
838 });
839 });
840}
841async function executeHooks(hookName, scripts = [], args = []) {
842 debugLog(`Running lifecycle hook "${hookName}" scripts...`);
843 for (const script of scripts) {
844 if (typeof script === 'string') {
845 debugLog(`Running lifecycle hook "${hookName}" script: ${script} with args: ${args.join(' ')}...`);
846 await execShellCommand(`${script} ${args.join(' ')}`);
847 }
848 else {
849 debugLog(`Running lifecycle hook "${hookName}" script: ${script.name} with args: ${args.join(' ')}...`);
850 await script(...args);
851 }
852 }
853}
854const lifecycleHooks = (_hooks = {}) => {
855 const hooks = normalizeHooks(_hooks);
856 return {
857 afterStart: async () => executeHooks('afterStart', hooks.afterStart),
858 onWatchTriggered: async (event, path) => executeHooks('onWatchTriggered', hooks.onWatchTriggered, [event, path]),
859 onError: async (error) => executeHooks('onError', hooks.onError, [`"${error}"`]),
860 afterOneFileWrite: async (path) => executeHooks('afterOneFileWrite', hooks.afterOneFileWrite, [path]),
861 afterAllFileWrite: async (paths) => executeHooks('afterAllFileWrite', hooks.afterAllFileWrite, paths),
862 beforeOneFileWrite: async (path) => executeHooks('beforeOneFileWrite', hooks.beforeOneFileWrite, [path]),
863 beforeAllFileWrite: async (paths) => executeHooks('beforeAllFileWrite', hooks.beforeAllFileWrite, paths),
864 beforeDone: async () => executeHooks('beforeDone', hooks.beforeDone),
865 };
866};
867
868function log(msg) {
869 // double spaces to inline the message with Listr
870 getLogger().info(` ${msg}`);
871}
872function emitWatching() {
873 log(`${logSymbols.info} Watching for changes...`);
874}
875const createWatcher = (initalContext, onNext) => {
876 debugLog(`[Watcher] Starting watcher...`);
877 let config = initalContext.getConfig();
878 const files = [initalContext.filepath].filter(a => a);
879 const documents = normalizeInstanceOrArray(config.documents);
880 const schemas = normalizeInstanceOrArray(config.schema);
881 // Add schemas and documents from "generates"
882 Object.keys(config.generates)
883 .map(filename => normalizeOutputParam(config.generates[filename]))
884 .forEach(conf => {
885 schemas.push(...normalizeInstanceOrArray(conf.schema));
886 documents.push(...normalizeInstanceOrArray(conf.documents));
887 });
888 if (documents) {
889 documents.forEach(doc => {
890 if (typeof doc === 'string') {
891 files.push(doc);
892 }
893 else {
894 files.push(...Object.keys(doc));
895 }
896 });
897 }
898 schemas.forEach((schema) => {
899 if (isGlob(schema) || isValidPath(schema)) {
900 files.push(schema);
901 }
902 });
903 if (typeof config.watch !== 'boolean') {
904 files.push(...normalizeInstanceOrArray(config.watch));
905 }
906 let watcher;
907 const runWatcher = async () => {
908 const chokidar = await import('chokidar');
909 let isShutdown = false;
910 const debouncedExec = debounce(() => {
911 if (!isShutdown) {
912 executeCodegen(initalContext)
913 .then(onNext, () => Promise.resolve())
914 .then(() => emitWatching());
915 }
916 }, 100);
917 emitWatching();
918 const ignored = [];
919 Object.keys(config.generates)
920 .map(filename => ({ filename, config: normalizeOutputParam(config.generates[filename]) }))
921 .forEach(entry => {
922 if (entry.config.preset) {
923 const extension = entry.config.presetConfig && entry.config.presetConfig.extension;
924 if (extension) {
925 ignored.push(join(entry.filename, '**', '*' + extension));
926 }
927 }
928 else {
929 ignored.push(entry.filename);
930 }
931 });
932 watcher = chokidar.watch(files, {
933 persistent: true,
934 ignoreInitial: true,
935 followSymlinks: true,
936 cwd: process.cwd(),
937 disableGlobbing: false,
938 usePolling: true,
939 interval: 100,
940 binaryInterval: 300,
941 depth: 99,
942 awaitWriteFinish: true,
943 ignorePermissionErrors: false,
944 atomic: true,
945 ignored,
946 });
947 debugLog(`[Watcher] Started`);
948 const shutdown = () => {
949 isShutdown = true;
950 debugLog(`[Watcher] Shutting down`);
951 log(`Shutting down watch...`);
952 watcher.close();
953 lifecycleHooks(config.hooks).beforeDone();
954 };
955 // it doesn't matter what has changed, need to run whole process anyway
956 watcher.on('all', async (eventName, path) => {
957 lifecycleHooks(config.hooks).onWatchTriggered(eventName, path);
958 debugLog(`[Watcher] triggered due to a file ${eventName} event: ${path}`);
959 const fullPath = join(process.cwd(), path);
960 delete require.cache[fullPath];
961 if (eventName === 'change' && config.configFilePath && fullPath === config.configFilePath) {
962 log(`${logSymbols.info} Config file has changed, reloading...`);
963 const context = await loadContext(config.configFilePath);
964 const newParsedConfig = context.getConfig();
965 newParsedConfig.watch = config.watch;
966 newParsedConfig.silent = config.silent;
967 newParsedConfig.overwrite = config.overwrite;
968 newParsedConfig.configFilePath = config.configFilePath;
969 config = newParsedConfig;
970 }
971 debouncedExec();
972 });
973 process.once('SIGINT', shutdown);
974 process.once('SIGTERM', shutdown);
975 };
976 // the promise never resolves to keep process running
977 return new Promise((resolve, reject) => {
978 executeCodegen(initalContext)
979 .then(onNext, () => Promise.resolve())
980 .then(runWatcher)
981 .catch(err => {
982 watcher.close();
983 reject(err);
984 });
985 });
986};
987
988function writeSync(filepath, content) {
989 return writeFileSync(filepath, content);
990}
991function readSync(filepath) {
992 return readFileSync(filepath, 'utf-8');
993}
994function fileExists(filePath) {
995 try {
996 return statSync(filePath).isFile();
997 }
998 catch (err) {
999 return false;
1000 }
1001}
1002function unlinkFile(filePath, cb) {
1003 unlink(filePath, cb);
1004}
1005
1006const hash = (content) => createHash('sha1').update(content).digest('base64');
1007async function generate(input, saveToFile = true) {
1008 const context = ensureContext(input);
1009 const config = context.getConfig();
1010 await lifecycleHooks(config.hooks).afterStart();
1011 let previouslyGeneratedFilenames = [];
1012 function removeStaleFiles(config, generationResult) {
1013 const filenames = generationResult.map(o => o.filename);
1014 // find stale files from previous build which are not present in current build
1015 const staleFilenames = previouslyGeneratedFilenames.filter(f => !filenames.includes(f));
1016 staleFilenames.forEach(filename => {
1017 if (shouldOverwrite(config, filename)) {
1018 unlinkFile(filename, err => {
1019 const prettyFilename = filename.replace(`${input.cwd || process.cwd()}/`, '');
1020 if (err) {
1021 debugLog(`Cannot remove stale file: ${prettyFilename}\n${err}`);
1022 }
1023 else {
1024 debugLog(`Removed stale file: ${prettyFilename}`);
1025 }
1026 });
1027 }
1028 });
1029 previouslyGeneratedFilenames = filenames;
1030 }
1031 const recentOutputHash = new Map();
1032 async function writeOutput(generationResult) {
1033 if (!saveToFile) {
1034 return generationResult;
1035 }
1036 if (config.watch) {
1037 removeStaleFiles(config, generationResult);
1038 }
1039 await lifecycleHooks(config.hooks).beforeAllFileWrite(generationResult.map(r => r.filename));
1040 await Promise.all(generationResult.map(async (result) => {
1041 const exists = fileExists(result.filename);
1042 if (!shouldOverwrite(config, result.filename) && exists) {
1043 return;
1044 }
1045 const content = result.content || '';
1046 const currentHash = hash(content);
1047 let previousHash = recentOutputHash.get(result.filename);
1048 if (!previousHash && exists) {
1049 previousHash = hash(readSync(result.filename));
1050 }
1051 if (previousHash && currentHash === previousHash) {
1052 debugLog(`Skipping file (${result.filename}) writing due to indentical hash...`);
1053 return;
1054 }
1055 if (content.length === 0) {
1056 return;
1057 }
1058 recentOutputHash.set(result.filename, currentHash);
1059 const basedir = dirname(result.filename);
1060 await lifecycleHooks(result.hooks).beforeOneFileWrite(result.filename);
1061 await lifecycleHooks(config.hooks).beforeOneFileWrite(result.filename);
1062 sync(basedir);
1063 const absolutePath = isAbsolute(result.filename)
1064 ? result.filename
1065 : join(input.cwd || process.cwd(), result.filename);
1066 writeSync(absolutePath, result.content);
1067 await lifecycleHooks(result.hooks).afterOneFileWrite(result.filename);
1068 await lifecycleHooks(config.hooks).afterOneFileWrite(result.filename);
1069 }));
1070 await lifecycleHooks(config.hooks).afterAllFileWrite(generationResult.map(r => r.filename));
1071 return generationResult;
1072 }
1073 // watch mode
1074 if (config.watch) {
1075 return createWatcher(context, writeOutput);
1076 }
1077 const outputFiles = await executeCodegen(context);
1078 await writeOutput(outputFiles);
1079 lifecycleHooks(config.hooks).beforeDone();
1080 return outputFiles;
1081}
1082function shouldOverwrite(config, outputPath) {
1083 const globalValue = config.overwrite === undefined ? true : !!config.overwrite;
1084 const outputConfig = config.generates[outputPath];
1085 if (!outputConfig) {
1086 debugLog(`Couldn't find a config of ${outputPath}`);
1087 return globalValue;
1088 }
1089 if (isConfiguredOutput(outputConfig) && typeof outputConfig.overwrite === 'boolean') {
1090 return outputConfig.overwrite;
1091 }
1092 return globalValue;
1093}
1094function isConfiguredOutput(output) {
1095 return typeof output.plugins !== 'undefined';
1096}
1097
1098// Parses config and writes it to a file
1099async function writeConfig(answers, config) {
1100 const YAML = await import('json-to-pretty-yaml').then(m => ('default' in m ? m.default : m));
1101 const ext = answers.config.toLocaleLowerCase().endsWith('.json') ? 'json' : 'yml';
1102 const content = ext === 'json' ? JSON.stringify(config) : YAML.stringify(config);
1103 const fullPath = resolve(process.cwd(), answers.config);
1104 const relativePath = relative(process.cwd(), answers.config);
1105 writeFileSync(fullPath, content, {
1106 encoding: 'utf-8',
1107 });
1108 return {
1109 relativePath,
1110 fullPath,
1111 };
1112}
1113// Updates package.json (script and plugins as dependencies)
1114async function writePackage(answers, configLocation) {
1115 // script
1116 const pkgPath = resolve(process.cwd(), 'package.json');
1117 const pkgContent = readFileSync(pkgPath, {
1118 encoding: 'utf-8',
1119 });
1120 const pkg = JSON.parse(pkgContent);
1121 const { indent } = detectIndent(pkgContent);
1122 if (!pkg.scripts) {
1123 pkg.scripts = {};
1124 }
1125 pkg.scripts[answers.script] = `graphql-codegen --config ${configLocation}`;
1126 // plugin
1127 if (!pkg.devDependencies) {
1128 pkg.devDependencies = {};
1129 }
1130 // read codegen's version
1131 let version;
1132 const dynamicImport = (m) => import(m).then(m => ('default' in m ? m.default : m));
1133 try {
1134 // Works in tests
1135 const packageJson = await dynamicImport('../../package.json');
1136 version = packageJson.version;
1137 }
1138 catch (e) {
1139 // Works in production (package dist is flat, everything is in the same folder)
1140 const packageJson = await dynamicImport('./package.json');
1141 version = packageJson.version;
1142 }
1143 answers.plugins.forEach(plugin => {
1144 pkg.devDependencies[plugin.package] = version;
1145 });
1146 if (answers.introspection) {
1147 pkg.devDependencies['@graphql-codegen/introspection'] = version;
1148 }
1149 // If cli haven't installed yet
1150 pkg.devDependencies['@graphql-codegen/cli'] = version;
1151 writeFileSync(pkgPath, JSON.stringify(pkg, null, indent));
1152}
1153function bold(str) {
1154 return chalk.bold(str);
1155}
1156function grey(str) {
1157 return chalk.grey(str);
1158}
1159function italic(str) {
1160 return chalk.italic(str);
1161}
1162
1163var Tags;
1164(function (Tags) {
1165 Tags["browser"] = "Browser";
1166 Tags["node"] = "Node";
1167 Tags["typescript"] = "TypeScript";
1168 Tags["flow"] = "Flow";
1169 Tags["angular"] = "Angular";
1170 Tags["stencil"] = "Stencil";
1171 Tags["react"] = "React";
1172 Tags["vue"] = "Vue";
1173})(Tags || (Tags = {}));
1174
1175const plugins = [
1176 {
1177 name: `TypeScript ${italic('(required by other typescript plugins)')}`,
1178 package: '@graphql-codegen/typescript',
1179 value: 'typescript',
1180 pathInRepo: 'typescript/typescript',
1181 available: hasTag(Tags.typescript),
1182 shouldBeSelected: tags => oneOf(tags, Tags.angular, Tags.stencil) || allOf(tags, Tags.typescript, Tags.react) || noneOf(tags, Tags.flow),
1183 defaultExtension: '.ts',
1184 },
1185 {
1186 name: `TypeScript Operations ${italic('(operations and fragments)')}`,
1187 package: '@graphql-codegen/typescript-operations',
1188 value: 'typescript-operations',
1189 pathInRepo: 'typescript/operations',
1190 available: tags => allOf(tags, Tags.browser, Tags.typescript),
1191 shouldBeSelected: tags => oneOf(tags, Tags.angular, Tags.stencil) || allOf(tags, Tags.typescript, Tags.react),
1192 defaultExtension: '.ts',
1193 },
1194 {
1195 name: `TypeScript Resolvers ${italic('(strongly typed resolve functions)')}`,
1196 package: '@graphql-codegen/typescript-resolvers',
1197 value: 'typescript-resolvers',
1198 pathInRepo: 'typescript/resolvers',
1199 available: tags => allOf(tags, Tags.node, Tags.typescript),
1200 shouldBeSelected: tags => noneOf(tags, Tags.flow),
1201 defaultExtension: '.ts',
1202 },
1203 {
1204 name: `Flow ${italic('(required by other flow plugins)')}`,
1205 package: '@graphql-codegen/flow',
1206 value: 'flow',
1207 pathInRepo: 'flow/flow',
1208 available: hasTag(Tags.flow),
1209 shouldBeSelected: tags => noneOf(tags, Tags.typescript),
1210 defaultExtension: '.js',
1211 },
1212 {
1213 name: `Flow Operations ${italic('(operations and fragments)')}`,
1214 package: '@graphql-codegen/flow-operations',
1215 value: 'flow-operations',
1216 pathInRepo: 'flow/operations',
1217 available: tags => allOf(tags, Tags.browser, Tags.flow),
1218 shouldBeSelected: tags => noneOf(tags, Tags.typescript),
1219 defaultExtension: '.js',
1220 },
1221 {
1222 name: `Flow Resolvers ${italic('(strongly typed resolve functions)')}`,
1223 package: '@graphql-codegen/flow-resolvers',
1224 value: 'flow-resolvers',
1225 pathInRepo: 'flow/resolvers',
1226 available: tags => allOf(tags, Tags.node, Tags.flow),
1227 shouldBeSelected: tags => noneOf(tags, Tags.typescript),
1228 defaultExtension: '.js',
1229 },
1230 {
1231 name: `TypeScript Apollo Angular ${italic('(typed GQL services)')}`,
1232 package: '@graphql-codegen/typescript-apollo-angular',
1233 value: 'typescript-apollo-angular',
1234 pathInRepo: 'typescript/apollo-angular',
1235 available: hasTag(Tags.angular),
1236 shouldBeSelected: () => true,
1237 defaultExtension: '.js',
1238 },
1239 {
1240 name: `TypeScript Vue Apollo ${italic('(typed composition functions)')}`,
1241 package: '@graphql-codegen/typescript-vue-apollo',
1242 value: 'typescript-vue-apollo',
1243 pathInRepo: 'typescript/vue-apollo',
1244 available: tags => allOf(tags, Tags.vue, Tags.typescript),
1245 shouldBeSelected: () => true,
1246 defaultExtension: '.ts',
1247 },
1248 {
1249 name: `TypeScript React Apollo ${italic('(typed components and HOCs)')}`,
1250 package: '@graphql-codegen/typescript-react-apollo',
1251 value: 'typescript-react-apollo',
1252 pathInRepo: 'typescript/react-apollo',
1253 available: tags => allOf(tags, Tags.react, Tags.typescript),
1254 shouldBeSelected: () => true,
1255 defaultExtension: '.tsx',
1256 },
1257 {
1258 name: `TypeScript Stencil Apollo ${italic('(typed components)')}`,
1259 package: '@graphql-codegen/typescript-stencil-apollo',
1260 value: 'typescript-stencil-apollo',
1261 pathInRepo: 'typescript/stencil-apollo',
1262 available: hasTag(Tags.stencil),
1263 shouldBeSelected: () => true,
1264 defaultExtension: '.tsx',
1265 },
1266 {
1267 name: `TypeScript MongoDB ${italic('(typed MongoDB objects)')}`,
1268 package: '@graphql-codegen/typescript-mongodb',
1269 value: 'typescript-mongodb',
1270 pathInRepo: 'typescript/mongodb',
1271 available: tags => allOf(tags, Tags.node, Tags.typescript),
1272 shouldBeSelected: () => false,
1273 defaultExtension: '.ts',
1274 },
1275 {
1276 name: `TypeScript GraphQL files modules ${italic('(declarations for .graphql files)')}`,
1277 package: '@graphql-codegen/typescript-graphql-files-modules',
1278 value: 'typescript-graphql-files-modules',
1279 pathInRepo: 'typescript/graphql-files-modules',
1280 available: tags => allOf(tags, Tags.browser, Tags.typescript),
1281 shouldBeSelected: () => false,
1282 defaultExtension: '.ts',
1283 },
1284 {
1285 name: `TypeScript GraphQL document nodes ${italic('(embedded GraphQL document)')}`,
1286 package: '@graphql-codegen/typescript-document-nodes',
1287 value: 'typescript-document-nodes',
1288 pathInRepo: 'typescript/document-nodes',
1289 available: tags => allOf(tags, Tags.typescript),
1290 shouldBeSelected: () => false,
1291 defaultExtension: '.ts',
1292 },
1293 {
1294 name: `Introspection Fragment Matcher ${italic('(for Apollo Client)')}`,
1295 package: '@graphql-codegen/fragment-matcher',
1296 value: 'fragment-matcher',
1297 pathInRepo: 'other/fragment-matcher',
1298 available: hasTag(Tags.browser),
1299 shouldBeSelected: () => false,
1300 defaultExtension: '.ts',
1301 },
1302];
1303function hasTag(tag) {
1304 return (tags) => tags.includes(tag);
1305}
1306function oneOf(list, ...items) {
1307 return list.some(i => items.includes(i));
1308}
1309function noneOf(list, ...items) {
1310 return !list.some(i => items.includes(i));
1311}
1312function allOf(list, ...items) {
1313 return items.every(i => list.includes(i));
1314}
1315
1316function getQuestions(possibleTargets) {
1317 return [
1318 {
1319 type: 'checkbox',
1320 name: 'targets',
1321 message: `What type of application are you building?`,
1322 choices: getApplicationTypeChoices(possibleTargets),
1323 validate: ((targets) => targets.length > 0),
1324 },
1325 {
1326 type: 'input',
1327 name: 'schema',
1328 message: 'Where is your schema?:',
1329 suffix: grey(' (path or url)'),
1330 default: 'http://localhost:4000',
1331 validate: (str) => str.length > 0,
1332 },
1333 {
1334 type: 'input',
1335 name: 'documents',
1336 message: 'Where are your operations and fragments?:',
1337 when: answers => {
1338 // flatten targets
1339 // I can't find an API in Inquirer that would do that
1340 answers.targets = normalizeTargets(answers.targets);
1341 return answers.targets.includes(Tags.browser);
1342 },
1343 default: 'src/**/*.graphql',
1344 validate: (str) => str.length > 0,
1345 },
1346 {
1347 type: 'checkbox',
1348 name: 'plugins',
1349 message: 'Pick plugins:',
1350 choices: getPluginChoices,
1351 validate: ((plugins) => plugins.length > 0),
1352 },
1353 {
1354 type: 'input',
1355 name: 'output',
1356 message: 'Where to write the output:',
1357 default: getOutputDefaultValue,
1358 validate: (str) => str.length > 0,
1359 },
1360 {
1361 type: 'confirm',
1362 name: 'introspection',
1363 message: 'Do you want to generate an introspection file?',
1364 },
1365 {
1366 type: 'input',
1367 name: 'config',
1368 message: 'How to name the config file?',
1369 default: 'codegen.yml',
1370 validate: (str) => {
1371 const isNotEmpty = str.length > 0;
1372 const hasCorrectExtension = ['json', 'yml', 'yaml'].some(ext => str.toLocaleLowerCase().endsWith(`.${ext}`));
1373 return isNotEmpty && hasCorrectExtension;
1374 },
1375 },
1376 {
1377 type: 'input',
1378 name: 'script',
1379 message: 'What script in package.json should run the codegen?',
1380 validate: (str) => str.length > 0,
1381 },
1382 ];
1383}
1384function getApplicationTypeChoices(possibleTargets) {
1385 function withFlowOrTypescript(tags) {
1386 if (possibleTargets.TypeScript) {
1387 tags.push(Tags.typescript);
1388 }
1389 else if (possibleTargets.Flow) {
1390 tags.push(Tags.flow);
1391 }
1392 else {
1393 tags.push(Tags.flow, Tags.typescript);
1394 }
1395 return tags;
1396 }
1397 return [
1398 {
1399 name: 'Backend - API or server',
1400 key: 'backend',
1401 value: withFlowOrTypescript([Tags.node]),
1402 checked: possibleTargets.Node,
1403 },
1404 {
1405 name: 'Application built with Angular',
1406 key: 'angular',
1407 value: [Tags.angular, Tags.browser, Tags.typescript],
1408 checked: possibleTargets.Angular,
1409 },
1410 {
1411 name: 'Application built with React',
1412 key: 'react',
1413 value: withFlowOrTypescript([Tags.react, Tags.browser]),
1414 checked: possibleTargets.React,
1415 },
1416 {
1417 name: 'Application built with Stencil',
1418 key: 'stencil',
1419 value: [Tags.stencil, Tags.browser, Tags.typescript],
1420 checked: possibleTargets.Stencil,
1421 },
1422 {
1423 name: 'Application built with other framework or vanilla JS',
1424 key: 'client',
1425 value: [Tags.browser, Tags.typescript, Tags.flow],
1426 checked: possibleTargets.Browser && !possibleTargets.Angular && !possibleTargets.React && !possibleTargets.Stencil,
1427 },
1428 ];
1429}
1430function getPluginChoices(answers) {
1431 return plugins
1432 .filter(p => p.available(answers.targets))
1433 .map(p => {
1434 return {
1435 name: p.name,
1436 value: p,
1437 checked: p.shouldBeSelected(answers.targets),
1438 };
1439 });
1440}
1441function normalizeTargets(targets) {
1442 return [].concat(...targets);
1443}
1444function getOutputDefaultValue(answers) {
1445 if (answers.plugins.some(plugin => plugin.defaultExtension === '.tsx')) {
1446 return 'src/generated/graphql.tsx';
1447 }
1448 else if (answers.plugins.some(plugin => plugin.defaultExtension === '.ts')) {
1449 return 'src/generated/graphql.ts';
1450 }
1451 else {
1452 return 'src/generated/graphql.js';
1453 }
1454}
1455
1456async function guessTargets() {
1457 const pkg = JSON.parse(readFileSync(resolve(process.cwd(), 'package.json'), {
1458 encoding: 'utf-8',
1459 }));
1460 const dependencies = Object.keys({
1461 ...pkg.dependencies,
1462 ...pkg.devDependencies,
1463 });
1464 return {
1465 [Tags.angular]: isAngular(dependencies),
1466 [Tags.react]: isReact(dependencies),
1467 [Tags.stencil]: isStencil(dependencies),
1468 [Tags.vue]: isVue(dependencies),
1469 [Tags.browser]: false,
1470 [Tags.node]: false,
1471 [Tags.typescript]: isTypescript(dependencies),
1472 [Tags.flow]: isFlow(dependencies),
1473 };
1474}
1475function isAngular(dependencies) {
1476 return dependencies.includes('@angular/core');
1477}
1478function isReact(dependencies) {
1479 return dependencies.includes('react');
1480}
1481function isStencil(dependencies) {
1482 return dependencies.includes('@stencil/core');
1483}
1484function isVue(dependencies) {
1485 return dependencies.includes('vue') || dependencies.includes('nuxt');
1486}
1487function isTypescript(dependencies) {
1488 return dependencies.includes('typescript');
1489}
1490function isFlow(dependencies) {
1491 return dependencies.includes('flow');
1492}
1493
1494function log$1(...msgs) {
1495 // eslint-disable-next-line no-console
1496 console.log(...msgs);
1497}
1498async function init() {
1499 log$1(`
1500 Welcome to ${bold('GraphQL Code Generator')}!
1501 Answer few questions and we will setup everything for you.
1502 `);
1503 const possibleTargets = await guessTargets();
1504 const answers = await inquirer.prompt(getQuestions(possibleTargets));
1505 // define config
1506 const config = {
1507 overwrite: true,
1508 schema: answers.schema,
1509 documents: answers.targets.includes(Tags.browser) ? answers.documents : null,
1510 generates: {
1511 [answers.output]: {
1512 plugins: answers.plugins.map(p => p.value),
1513 },
1514 },
1515 };
1516 // introspection
1517 if (answers.introspection) {
1518 addIntrospection(config);
1519 }
1520 // config file
1521 const { relativePath } = await writeConfig(answers, config);
1522 // write package.json
1523 await writePackage(answers, relativePath);
1524 // Emit status to the terminal
1525 log$1(`
1526 Config file generated at ${bold(relativePath)}
1527
1528 ${bold('$ npm install')}
1529
1530 To install the plugins.
1531
1532 ${bold(`$ npm run ${answers.script}`)}
1533
1534 To run GraphQL Code Generator.
1535 `);
1536}
1537// adds an introspection to `generates`
1538function addIntrospection(config) {
1539 config.generates['./graphql.schema.json'] = {
1540 plugins: ['introspection'],
1541 };
1542}
1543
1544const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
1545const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
1546
1547function cliError(err, exitOnError = true) {
1548 let msg;
1549 if (err instanceof Error) {
1550 msg = err.message || err.toString();
1551 }
1552 else if (typeof err === 'string') {
1553 msg = err;
1554 }
1555 else {
1556 msg = JSON.stringify(err);
1557 }
1558 // eslint-disable-next-line no-console
1559 console.error(msg);
1560 if (exitOnError && isNode) {
1561 process.exit(1);
1562 }
1563 else if (exitOnError && isBrowser) {
1564 throw err;
1565 }
1566}
1567
1568function runCli(cmd) {
1569 ensureGraphQlPackage();
1570 switch (cmd) {
1571 case 'init':
1572 return init();
1573 default: {
1574 return createContext().then(context => {
1575 return generate(context).catch(async (error) => {
1576 await lifecycleHooks(context.getConfig().hooks).onError(error.toString());
1577 throw error;
1578 });
1579 });
1580 }
1581 }
1582}
1583function ensureGraphQlPackage() {
1584 try {
1585 require('graphql');
1586 }
1587 catch (e) {
1588 throw new DetailedError(`Unable to load "graphql" package. Please make sure to install "graphql" as a dependency!`, `
1589 To install "graphql", run:
1590 yarn add graphql
1591 Or, with NPM:
1592 npm install --save graphql
1593`);
1594 }
1595}
1596
1597export { CodegenContext, CodegenExtension, buildOptions, cliError, createContext, ensureContext, ensureGraphQlPackage, executeCodegen, findAndLoadGraphQLConfig, generate, init, loadContext, parseArgv, runCli, updateContextWithCliFlags };
1598//# sourceMappingURL=index.esm.js.map