UNPKG

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