UNPKG

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