UNPKG

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