UNPKG

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