UNPKG

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