UNPKG

60.9 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._pluginContext = {};
591 this._config = config;
592 this._graphqlConfig = graphqlConfig;
593 this.filepath = this._graphqlConfig ? this._graphqlConfig.filepath : filepath;
594 this.cwd = this._graphqlConfig ? this._graphqlConfig.dirpath : process.cwd();
595 }
596 useProject(name) {
597 this._project = name;
598 }
599 getConfig() {
600 if (!this.config) {
601 if (this._graphqlConfig) {
602 const project = this._graphqlConfig.getProject(this._project);
603 this.config = {
604 ...project.extension('codegen'),
605 schema: project.schema,
606 documents: project.documents,
607 pluginContext: this._pluginContext,
608 };
609 }
610 else {
611 this.config = { ...this._config, pluginContext: this._pluginContext };
612 }
613 }
614 return this.config;
615 }
616 updateConfig(config) {
617 this.config = {
618 ...this.getConfig(),
619 ...config,
620 };
621 }
622 getPluginContext() {
623 return this._pluginContext;
624 }
625 async loadSchema(pointer) {
626 if (this._graphqlConfig) {
627 // TODO: SchemaWithLoader won't work here
628 return this._graphqlConfig.getProject(this._project).loadSchema(pointer);
629 }
630 return loadSchema(pointer, this.getConfig());
631 }
632 async loadDocuments(pointer) {
633 if (this._graphqlConfig) {
634 // TODO: pointer won't work here
635 const documents = await this._graphqlConfig.getProject(this._project).loadDocuments(pointer);
636 return documents;
637 }
638 return loadDocuments(pointer, this.getConfig());
639 }
640}
641function ensureContext(input) {
642 return input instanceof CodegenContext ? input : new CodegenContext({ config: input });
643}
644
645const defaultLoader = (mod) => new Promise(function (resolve) { resolve(_interopNamespace(require(mod))); });
646async function executeCodegen(input) {
647 function wrapTask(task, source) {
648 return async () => {
649 try {
650 await Promise.resolve().then(() => task());
651 }
652 catch (error) {
653 if (source && !(error instanceof graphql.GraphQLError)) {
654 error.source = source;
655 }
656 throw error;
657 }
658 };
659 }
660 const context = ensureContext(input);
661 const config = context.getConfig();
662 const pluginContext = context.getPluginContext();
663 const result = [];
664 const commonListrOptions = {
665 exitOnError: true,
666 };
667 const Listr = await new Promise(function (resolve) { resolve(_interopNamespace(require('listr'))); }).then(m => ('default' in m ? m.default : m));
668 let listr;
669 if (process.env.VERBOSE) {
670 listr = new Listr({
671 ...commonListrOptions,
672 renderer: 'verbose',
673 nonTTYRenderer: 'verbose',
674 });
675 }
676 else if (process.env.NODE_ENV === 'test') {
677 listr = new Listr({
678 ...commonListrOptions,
679 renderer: 'silent',
680 nonTTYRenderer: 'silent',
681 });
682 }
683 else {
684 listr = new Listr({
685 ...commonListrOptions,
686 renderer: config.silent ? 'silent' : Renderer,
687 nonTTYRenderer: config.silent ? 'silent' : 'default',
688 collapse: true,
689 clearOutput: false,
690 });
691 }
692 let rootConfig = {};
693 let rootSchemas;
694 let rootDocuments;
695 const generates = {};
696 async function normalize() {
697 /* Load Require extensions */
698 const requireExtensions = pluginHelpers.normalizeInstanceOrArray(config.require);
699 for (const mod of requireExtensions) {
700 await new Promise(function (resolve) { resolve(_interopNamespace(require(mod))); });
701 }
702 /* Root plugin config */
703 rootConfig = config.config || {};
704 /* Normalize root "schema" field */
705 rootSchemas = pluginHelpers.normalizeInstanceOrArray(config.schema);
706 /* Normalize root "documents" field */
707 rootDocuments = pluginHelpers.normalizeInstanceOrArray(config.documents);
708 /* Normalize "generators" field */
709 const generateKeys = Object.keys(config.generates || {});
710 if (generateKeys.length === 0) {
711 throw new pluginHelpers.DetailedError('Invalid Codegen Configuration!', `
712 Please make sure that your codegen config file contains the "generates" field, with a specification for the plugins you need.
713
714 It should looks like that:
715
716 schema:
717 - my-schema.graphql
718 generates:
719 my-file.ts:
720 - plugin1
721 - plugin2
722 - plugin3
723 `);
724 }
725 for (const filename of generateKeys) {
726 generates[filename] = pluginHelpers.normalizeOutputParam(config.generates[filename]);
727 if (generates[filename].plugins.length === 0) {
728 throw new pluginHelpers.DetailedError('Invalid Codegen Configuration!', `
729 Please make sure that your codegen config file has defined plugins list for output "${filename}".
730
731 It should looks like that:
732
733 schema:
734 - my-schema.graphql
735 generates:
736 my-file.ts:
737 - plugin1
738 - plugin2
739 - plugin3
740 `);
741 }
742 }
743 if (rootSchemas.length === 0 &&
744 Object.keys(generates).some(filename => !generates[filename].schema || generates[filename].schema.length === 0)) {
745 throw new pluginHelpers.DetailedError('Invalid Codegen Configuration!', `
746 Please make sure that your codegen config file contains either the "schema" field
747 or every generated file has its own "schema" field.
748
749 It should looks like that:
750 schema:
751 - my-schema.graphql
752
753 or:
754 generates:
755 path/to/output:
756 schema: my-schema.graphql
757 `);
758 }
759 }
760 listr.add({
761 title: 'Parse configuration',
762 task: () => normalize(),
763 });
764 listr.add({
765 title: 'Generate outputs',
766 task: () => {
767 return new Listr(Object.keys(generates).map(filename => {
768 const outputConfig = generates[filename];
769 const hasPreset = !!outputConfig.preset;
770 return {
771 title: hasPreset
772 ? `Generate to ${filename} (using EXPERIMENTAL preset "${outputConfig.preset}")`
773 : `Generate ${filename}`,
774 task: () => {
775 let outputSchemaAst;
776 let outputSchema;
777 const outputFileTemplateConfig = outputConfig.config || {};
778 const outputDocuments = [];
779 const outputSpecificSchemas = pluginHelpers.normalizeInstanceOrArray(outputConfig.schema);
780 const outputSpecificDocuments = pluginHelpers.normalizeInstanceOrArray(outputConfig.documents);
781 return new Listr([
782 {
783 title: 'Load GraphQL schemas',
784 task: wrapTask(async () => {
785 debugLog(`[CLI] Loading Schemas`);
786 const schemaPointerMap = {};
787 const allSchemaUnnormalizedPointers = [...rootSchemas, ...outputSpecificSchemas];
788 for (const unnormalizedPtr of allSchemaUnnormalizedPointers) {
789 if (typeof unnormalizedPtr === 'string') {
790 schemaPointerMap[unnormalizedPtr] = {};
791 }
792 else if (typeof unnormalizedPtr === 'object') {
793 Object.assign(schemaPointerMap, unnormalizedPtr);
794 }
795 }
796 outputSchemaAst = await context.loadSchema(schemaPointerMap);
797 outputSchema = graphql.parse(utils.printSchemaWithDirectives(outputSchemaAst));
798 }, filename),
799 },
800 {
801 title: 'Load GraphQL documents',
802 task: wrapTask(async () => {
803 debugLog(`[CLI] Loading Documents`);
804 const allDocuments = [...rootDocuments, ...outputSpecificDocuments];
805 const documents = await context.loadDocuments(allDocuments);
806 if (documents.length > 0) {
807 outputDocuments.push(...documents);
808 }
809 }, filename),
810 },
811 {
812 title: 'Generate',
813 task: wrapTask(async () => {
814 debugLog(`[CLI] Generating output`);
815 const normalizedPluginsArray = pluginHelpers.normalizeConfig(outputConfig.plugins);
816 const pluginLoader = config.pluginLoader || defaultLoader;
817 const pluginPackages = await Promise.all(normalizedPluginsArray.map(plugin => getPluginByName(Object.keys(plugin)[0], pluginLoader)));
818 const pluginMap = {};
819 const preset = hasPreset
820 ? typeof outputConfig.preset === 'string'
821 ? await getPresetByName(outputConfig.preset, defaultLoader)
822 : outputConfig.preset
823 : null;
824 pluginPackages.forEach((pluginPackage, i) => {
825 const plugin = normalizedPluginsArray[i];
826 const name = Object.keys(plugin)[0];
827 pluginMap[name] = pluginPackage;
828 });
829 const mergedConfig = {
830 ...rootConfig,
831 ...(typeof outputFileTemplateConfig === 'string'
832 ? { value: outputFileTemplateConfig }
833 : outputFileTemplateConfig),
834 };
835 let outputs = [];
836 if (hasPreset) {
837 outputs = await preset.buildGeneratesSection({
838 baseOutputDir: filename,
839 presetConfig: outputConfig.presetConfig || {},
840 plugins: normalizedPluginsArray,
841 schema: outputSchema,
842 schemaAst: outputSchemaAst,
843 documents: outputDocuments,
844 config: mergedConfig,
845 pluginMap,
846 pluginContext,
847 });
848 }
849 else {
850 outputs = [
851 {
852 filename,
853 plugins: normalizedPluginsArray,
854 schema: outputSchema,
855 schemaAst: outputSchemaAst,
856 documents: outputDocuments,
857 config: mergedConfig,
858 pluginMap,
859 pluginContext,
860 },
861 ];
862 }
863 const process = async (outputArgs) => {
864 const output = await core.codegen(outputArgs);
865 result.push({
866 filename: outputArgs.filename,
867 content: output,
868 hooks: outputConfig.hooks || {},
869 });
870 };
871 await Promise.all(outputs.map(process));
872 }, filename),
873 },
874 ], {
875 // it stops when one of tasks failed
876 exitOnError: true,
877 });
878 },
879 };
880 }), {
881 // it doesn't stop when one of tasks failed, to finish at least some of outputs
882 exitOnError: false,
883 // run 4 at once
884 concurrent: 4,
885 });
886 },
887 });
888 await listr.run();
889 return result;
890}
891
892function log(msg) {
893 // double spaces to inline the message with Listr
894 getLogger().info(` ${msg}`);
895}
896function emitWatching() {
897 log(`${logSymbols.info} Watching for changes...`);
898}
899const createWatcher = (initalContext, onNext) => {
900 debugLog(`[Watcher] Starting watcher...`);
901 let config = initalContext.getConfig();
902 const files = [initalContext.filepath].filter(a => a);
903 const documents = pluginHelpers.normalizeInstanceOrArray(config.documents);
904 const schemas = pluginHelpers.normalizeInstanceOrArray(config.schema);
905 // Add schemas and documents from "generates"
906 Object.keys(config.generates)
907 .map(filename => pluginHelpers.normalizeOutputParam(config.generates[filename]))
908 .forEach(conf => {
909 schemas.push(...pluginHelpers.normalizeInstanceOrArray(conf.schema));
910 documents.push(...pluginHelpers.normalizeInstanceOrArray(conf.documents));
911 });
912 if (documents) {
913 documents.forEach(doc => {
914 if (typeof doc === 'string') {
915 files.push(doc);
916 }
917 else {
918 files.push(...Object.keys(doc));
919 }
920 });
921 }
922 schemas.forEach((schema) => {
923 if (isGlob(schema) || utils.isValidPath(schema)) {
924 files.push(schema);
925 }
926 });
927 if (typeof config.watch !== 'boolean') {
928 files.push(...pluginHelpers.normalizeInstanceOrArray(config.watch));
929 }
930 let watcher;
931 const runWatcher = async () => {
932 const chokidar = await new Promise(function (resolve) { resolve(_interopNamespace(require('chokidar'))); });
933 let isShutdown = false;
934 const debouncedExec = debounce(() => {
935 if (!isShutdown) {
936 executeCodegen(initalContext)
937 .then(onNext, () => Promise.resolve())
938 .then(() => emitWatching());
939 }
940 }, 100);
941 emitWatching();
942 const ignored = [];
943 Object.keys(config.generates)
944 .map(filename => ({ filename, config: pluginHelpers.normalizeOutputParam(config.generates[filename]) }))
945 .forEach(entry => {
946 if (entry.config.preset) {
947 const extension = entry.config.presetConfig && entry.config.presetConfig.extension;
948 if (extension) {
949 ignored.push(path.join(entry.filename, '**', '*' + extension));
950 }
951 }
952 else {
953 ignored.push(entry.filename);
954 }
955 });
956 watcher = chokidar.watch(files, {
957 persistent: true,
958 ignoreInitial: true,
959 followSymlinks: true,
960 cwd: process.cwd(),
961 disableGlobbing: false,
962 usePolling: true,
963 interval: 100,
964 binaryInterval: 300,
965 depth: 99,
966 awaitWriteFinish: true,
967 ignorePermissionErrors: false,
968 atomic: true,
969 ignored,
970 });
971 debugLog(`[Watcher] Started`);
972 const shutdown = () => {
973 isShutdown = true;
974 debugLog(`[Watcher] Shutting down`);
975 log(`Shutting down watch...`);
976 watcher.close();
977 lifecycleHooks(config.hooks).beforeDone();
978 };
979 // it doesn't matter what has changed, need to run whole process anyway
980 watcher.on('all', async (eventName, path$1) => {
981 lifecycleHooks(config.hooks).onWatchTriggered(eventName, path$1);
982 debugLog(`[Watcher] triggered due to a file ${eventName} event: ${path$1}`);
983 const fullPath = path.join(process.cwd(), path$1);
984 delete require.cache[fullPath];
985 if (eventName === 'change' && config.configFilePath && fullPath === config.configFilePath) {
986 log(`${logSymbols.info} Config file has changed, reloading...`);
987 const context = await loadContext(config.configFilePath);
988 const newParsedConfig = context.getConfig();
989 newParsedConfig.watch = config.watch;
990 newParsedConfig.silent = config.silent;
991 newParsedConfig.overwrite = config.overwrite;
992 newParsedConfig.configFilePath = config.configFilePath;
993 config = newParsedConfig;
994 }
995 debouncedExec();
996 });
997 process.once('SIGINT', shutdown);
998 process.once('SIGTERM', shutdown);
999 };
1000 // the promise never resolves to keep process running
1001 return new Promise((resolve, reject) => {
1002 executeCodegen(initalContext)
1003 .then(onNext, () => Promise.resolve())
1004 .then(runWatcher)
1005 .catch(err => {
1006 watcher.close();
1007 reject(err);
1008 });
1009 });
1010};
1011
1012function writeSync(filepath, content) {
1013 return fs.writeFileSync(filepath, content);
1014}
1015function readSync(filepath) {
1016 return fs.readFileSync(filepath, 'utf-8');
1017}
1018function fileExists(filePath) {
1019 try {
1020 return fs.statSync(filePath).isFile();
1021 }
1022 catch (err) {
1023 return false;
1024 }
1025}
1026function unlinkFile(filePath, cb) {
1027 fs.unlink(filePath, cb);
1028}
1029
1030const hash = (content) => crypto.createHash('sha1').update(content).digest('base64');
1031async function generate(input, saveToFile = true) {
1032 const context = ensureContext(input);
1033 const config = context.getConfig();
1034 await lifecycleHooks(config.hooks).afterStart();
1035 let previouslyGeneratedFilenames = [];
1036 function removeStaleFiles(config, generationResult) {
1037 const filenames = generationResult.map(o => o.filename);
1038 // find stale files from previous build which are not present in current build
1039 const staleFilenames = previouslyGeneratedFilenames.filter(f => !filenames.includes(f));
1040 staleFilenames.forEach(filename => {
1041 if (shouldOverwrite(config, filename)) {
1042 unlinkFile(filename, err => {
1043 const prettyFilename = filename.replace(`${input.cwd || process.cwd()}/`, '');
1044 if (err) {
1045 debugLog(`Cannot remove stale file: ${prettyFilename}\n${err}`);
1046 }
1047 else {
1048 debugLog(`Removed stale file: ${prettyFilename}`);
1049 }
1050 });
1051 }
1052 });
1053 previouslyGeneratedFilenames = filenames;
1054 }
1055 const recentOutputHash = new Map();
1056 async function writeOutput(generationResult) {
1057 if (!saveToFile) {
1058 return generationResult;
1059 }
1060 if (config.watch) {
1061 removeStaleFiles(config, generationResult);
1062 }
1063 await lifecycleHooks(config.hooks).beforeAllFileWrite(generationResult.map(r => r.filename));
1064 await Promise.all(generationResult.map(async (result) => {
1065 const exists = fileExists(result.filename);
1066 if (!shouldOverwrite(config, result.filename) && exists) {
1067 return;
1068 }
1069 const content = result.content || '';
1070 const currentHash = hash(content);
1071 let previousHash = recentOutputHash.get(result.filename);
1072 if (!previousHash && exists) {
1073 previousHash = hash(readSync(result.filename));
1074 }
1075 if (previousHash && currentHash === previousHash) {
1076 debugLog(`Skipping file (${result.filename}) writing due to indentical hash...`);
1077 return;
1078 }
1079 if (content.length === 0) {
1080 return;
1081 }
1082 recentOutputHash.set(result.filename, currentHash);
1083 const basedir = path.dirname(result.filename);
1084 await lifecycleHooks(result.hooks).beforeOneFileWrite(result.filename);
1085 await lifecycleHooks(config.hooks).beforeOneFileWrite(result.filename);
1086 mkdirp.sync(basedir);
1087 const absolutePath = path.isAbsolute(result.filename)
1088 ? result.filename
1089 : path.join(input.cwd || process.cwd(), result.filename);
1090 writeSync(absolutePath, result.content);
1091 await lifecycleHooks(result.hooks).afterOneFileWrite(result.filename);
1092 await lifecycleHooks(config.hooks).afterOneFileWrite(result.filename);
1093 }));
1094 await lifecycleHooks(config.hooks).afterAllFileWrite(generationResult.map(r => r.filename));
1095 return generationResult;
1096 }
1097 // watch mode
1098 if (config.watch) {
1099 return createWatcher(context, writeOutput);
1100 }
1101 const outputFiles = await executeCodegen(context);
1102 await writeOutput(outputFiles);
1103 lifecycleHooks(config.hooks).beforeDone();
1104 return outputFiles;
1105}
1106function shouldOverwrite(config, outputPath) {
1107 const globalValue = config.overwrite === undefined ? true : !!config.overwrite;
1108 const outputConfig = config.generates[outputPath];
1109 if (!outputConfig) {
1110 debugLog(`Couldn't find a config of ${outputPath}`);
1111 return globalValue;
1112 }
1113 if (isConfiguredOutput(outputConfig) && typeof outputConfig.overwrite === 'boolean') {
1114 return outputConfig.overwrite;
1115 }
1116 return globalValue;
1117}
1118function isConfiguredOutput(output) {
1119 return typeof output.plugins !== 'undefined';
1120}
1121
1122// Parses config and writes it to a file
1123async function writeConfig(answers, config) {
1124 const YAML = await new Promise(function (resolve) { resolve(_interopNamespace(require('json-to-pretty-yaml'))); }).then(m => ('default' in m ? m.default : m));
1125 const ext = answers.config.toLocaleLowerCase().endsWith('.json') ? 'json' : 'yml';
1126 const content = ext === 'json' ? JSON.stringify(config) : YAML.stringify(config);
1127 const fullPath = path.resolve(process.cwd(), answers.config);
1128 const relativePath = path.relative(process.cwd(), answers.config);
1129 fs.writeFileSync(fullPath, content, {
1130 encoding: 'utf-8',
1131 });
1132 return {
1133 relativePath,
1134 fullPath,
1135 };
1136}
1137// Updates package.json (script and plugins as dependencies)
1138async function writePackage(answers, configLocation) {
1139 // script
1140 const pkgPath = path.resolve(process.cwd(), 'package.json');
1141 const pkgContent = fs.readFileSync(pkgPath, {
1142 encoding: 'utf-8',
1143 });
1144 const pkg = JSON.parse(pkgContent);
1145 const { indent } = detectIndent(pkgContent);
1146 if (!pkg.scripts) {
1147 pkg.scripts = {};
1148 }
1149 pkg.scripts[answers.script] = `graphql-codegen --config ${configLocation}`;
1150 // plugin
1151 if (!pkg.devDependencies) {
1152 pkg.devDependencies = {};
1153 }
1154 // read codegen's version
1155 let version;
1156 const dynamicImport = (m) => new Promise(function (resolve) { resolve(_interopNamespace(require(m))); }).then(m => ('default' in m ? m.default : m));
1157 try {
1158 // Works in tests
1159 const packageJson = await dynamicImport('../../package.json');
1160 version = packageJson.version;
1161 }
1162 catch (e) {
1163 // Works in production (package dist is flat, everything is in the same folder)
1164 const packageJson = await dynamicImport('./package.json');
1165 version = packageJson.version;
1166 }
1167 answers.plugins.forEach(plugin => {
1168 pkg.devDependencies[plugin.package] = version;
1169 });
1170 if (answers.introspection) {
1171 pkg.devDependencies['@graphql-codegen/introspection'] = version;
1172 }
1173 // If cli haven't installed yet
1174 pkg.devDependencies['@graphql-codegen/cli'] = version;
1175 fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, indent));
1176}
1177function bold(str) {
1178 return chalk.bold(str);
1179}
1180function grey(str) {
1181 return chalk.grey(str);
1182}
1183function italic(str) {
1184 return chalk.italic(str);
1185}
1186
1187var Tags;
1188(function (Tags) {
1189 Tags["browser"] = "Browser";
1190 Tags["node"] = "Node";
1191 Tags["typescript"] = "TypeScript";
1192 Tags["flow"] = "Flow";
1193 Tags["angular"] = "Angular";
1194 Tags["stencil"] = "Stencil";
1195 Tags["react"] = "React";
1196 Tags["vue"] = "Vue";
1197})(Tags || (Tags = {}));
1198
1199const plugins = [
1200 {
1201 name: `TypeScript ${italic('(required by other typescript plugins)')}`,
1202 package: '@graphql-codegen/typescript',
1203 value: 'typescript',
1204 pathInRepo: 'typescript/typescript',
1205 available: hasTag(Tags.typescript),
1206 shouldBeSelected: tags => oneOf(tags, Tags.angular, Tags.stencil) || allOf(tags, Tags.typescript, Tags.react) || noneOf(tags, Tags.flow),
1207 defaultExtension: '.ts',
1208 },
1209 {
1210 name: `TypeScript Operations ${italic('(operations and fragments)')}`,
1211 package: '@graphql-codegen/typescript-operations',
1212 value: 'typescript-operations',
1213 pathInRepo: 'typescript/operations',
1214 available: tags => allOf(tags, Tags.browser, Tags.typescript),
1215 shouldBeSelected: tags => oneOf(tags, Tags.angular, Tags.stencil) || allOf(tags, Tags.typescript, Tags.react),
1216 defaultExtension: '.ts',
1217 },
1218 {
1219 name: `TypeScript Resolvers ${italic('(strongly typed resolve functions)')}`,
1220 package: '@graphql-codegen/typescript-resolvers',
1221 value: 'typescript-resolvers',
1222 pathInRepo: 'typescript/resolvers',
1223 available: tags => allOf(tags, Tags.node, Tags.typescript),
1224 shouldBeSelected: tags => noneOf(tags, Tags.flow),
1225 defaultExtension: '.ts',
1226 },
1227 {
1228 name: `Flow ${italic('(required by other flow plugins)')}`,
1229 package: '@graphql-codegen/flow',
1230 value: 'flow',
1231 pathInRepo: 'flow/flow',
1232 available: hasTag(Tags.flow),
1233 shouldBeSelected: tags => noneOf(tags, Tags.typescript),
1234 defaultExtension: '.js',
1235 },
1236 {
1237 name: `Flow Operations ${italic('(operations and fragments)')}`,
1238 package: '@graphql-codegen/flow-operations',
1239 value: 'flow-operations',
1240 pathInRepo: 'flow/operations',
1241 available: tags => allOf(tags, Tags.browser, Tags.flow),
1242 shouldBeSelected: tags => noneOf(tags, Tags.typescript),
1243 defaultExtension: '.js',
1244 },
1245 {
1246 name: `Flow Resolvers ${italic('(strongly typed resolve functions)')}`,
1247 package: '@graphql-codegen/flow-resolvers',
1248 value: 'flow-resolvers',
1249 pathInRepo: 'flow/resolvers',
1250 available: tags => allOf(tags, Tags.node, Tags.flow),
1251 shouldBeSelected: tags => noneOf(tags, Tags.typescript),
1252 defaultExtension: '.js',
1253 },
1254 {
1255 name: `TypeScript Apollo Angular ${italic('(typed GQL services)')}`,
1256 package: '@graphql-codegen/typescript-apollo-angular',
1257 value: 'typescript-apollo-angular',
1258 pathInRepo: 'typescript/apollo-angular',
1259 available: hasTag(Tags.angular),
1260 shouldBeSelected: () => true,
1261 defaultExtension: '.js',
1262 },
1263 {
1264 name: `TypeScript Vue Apollo ${italic('(typed composition functions)')}`,
1265 package: '@graphql-codegen/typescript-vue-apollo',
1266 value: 'typescript-vue-apollo',
1267 pathInRepo: 'typescript/vue-apollo',
1268 available: tags => allOf(tags, Tags.vue, Tags.typescript),
1269 shouldBeSelected: () => true,
1270 defaultExtension: '.ts',
1271 },
1272 {
1273 name: `TypeScript React Apollo ${italic('(typed components and HOCs)')}`,
1274 package: '@graphql-codegen/typescript-react-apollo',
1275 value: 'typescript-react-apollo',
1276 pathInRepo: 'typescript/react-apollo',
1277 available: tags => allOf(tags, Tags.react, Tags.typescript),
1278 shouldBeSelected: () => true,
1279 defaultExtension: '.tsx',
1280 },
1281 {
1282 name: `TypeScript Stencil Apollo ${italic('(typed components)')}`,
1283 package: '@graphql-codegen/typescript-stencil-apollo',
1284 value: 'typescript-stencil-apollo',
1285 pathInRepo: 'typescript/stencil-apollo',
1286 available: hasTag(Tags.stencil),
1287 shouldBeSelected: () => true,
1288 defaultExtension: '.tsx',
1289 },
1290 {
1291 name: `TypeScript MongoDB ${italic('(typed MongoDB objects)')}`,
1292 package: '@graphql-codegen/typescript-mongodb',
1293 value: 'typescript-mongodb',
1294 pathInRepo: 'typescript/mongodb',
1295 available: tags => allOf(tags, Tags.node, Tags.typescript),
1296 shouldBeSelected: () => false,
1297 defaultExtension: '.ts',
1298 },
1299 {
1300 name: `TypeScript GraphQL files modules ${italic('(declarations for .graphql files)')}`,
1301 package: '@graphql-codegen/typescript-graphql-files-modules',
1302 value: 'typescript-graphql-files-modules',
1303 pathInRepo: 'typescript/graphql-files-modules',
1304 available: tags => allOf(tags, Tags.browser, Tags.typescript),
1305 shouldBeSelected: () => false,
1306 defaultExtension: '.ts',
1307 },
1308 {
1309 name: `TypeScript GraphQL document nodes ${italic('(embedded GraphQL document)')}`,
1310 package: '@graphql-codegen/typescript-document-nodes',
1311 value: 'typescript-document-nodes',
1312 pathInRepo: 'typescript/document-nodes',
1313 available: tags => allOf(tags, Tags.typescript),
1314 shouldBeSelected: () => false,
1315 defaultExtension: '.ts',
1316 },
1317 {
1318 name: `Introspection Fragment Matcher ${italic('(for Apollo Client)')}`,
1319 package: '@graphql-codegen/fragment-matcher',
1320 value: 'fragment-matcher',
1321 pathInRepo: 'other/fragment-matcher',
1322 available: hasTag(Tags.browser),
1323 shouldBeSelected: () => false,
1324 defaultExtension: '.ts',
1325 },
1326];
1327function hasTag(tag) {
1328 return (tags) => tags.includes(tag);
1329}
1330function oneOf(list, ...items) {
1331 return list.some(i => items.includes(i));
1332}
1333function noneOf(list, ...items) {
1334 return !list.some(i => items.includes(i));
1335}
1336function allOf(list, ...items) {
1337 return items.every(i => list.includes(i));
1338}
1339
1340function getQuestions(possibleTargets) {
1341 return [
1342 {
1343 type: 'checkbox',
1344 name: 'targets',
1345 message: `What type of application are you building?`,
1346 choices: getApplicationTypeChoices(possibleTargets),
1347 validate: ((targets) => targets.length > 0),
1348 },
1349 {
1350 type: 'input',
1351 name: 'schema',
1352 message: 'Where is your schema?:',
1353 suffix: grey(' (path or url)'),
1354 default: 'http://localhost:4000',
1355 validate: (str) => str.length > 0,
1356 },
1357 {
1358 type: 'input',
1359 name: 'documents',
1360 message: 'Where are your operations and fragments?:',
1361 when: answers => {
1362 // flatten targets
1363 // I can't find an API in Inquirer that would do that
1364 answers.targets = normalizeTargets(answers.targets);
1365 return answers.targets.includes(Tags.browser);
1366 },
1367 default: 'src/**/*.graphql',
1368 validate: (str) => str.length > 0,
1369 },
1370 {
1371 type: 'checkbox',
1372 name: 'plugins',
1373 message: 'Pick plugins:',
1374 choices: getPluginChoices,
1375 validate: ((plugins) => plugins.length > 0),
1376 },
1377 {
1378 type: 'input',
1379 name: 'output',
1380 message: 'Where to write the output:',
1381 default: getOutputDefaultValue,
1382 validate: (str) => str.length > 0,
1383 },
1384 {
1385 type: 'confirm',
1386 name: 'introspection',
1387 message: 'Do you want to generate an introspection file?',
1388 },
1389 {
1390 type: 'input',
1391 name: 'config',
1392 message: 'How to name the config file?',
1393 default: 'codegen.yml',
1394 validate: (str) => {
1395 const isNotEmpty = str.length > 0;
1396 const hasCorrectExtension = ['json', 'yml', 'yaml'].some(ext => str.toLocaleLowerCase().endsWith(`.${ext}`));
1397 return isNotEmpty && hasCorrectExtension;
1398 },
1399 },
1400 {
1401 type: 'input',
1402 name: 'script',
1403 message: 'What script in package.json should run the codegen?',
1404 validate: (str) => str.length > 0,
1405 },
1406 ];
1407}
1408function getApplicationTypeChoices(possibleTargets) {
1409 function withFlowOrTypescript(tags) {
1410 if (possibleTargets.TypeScript) {
1411 tags.push(Tags.typescript);
1412 }
1413 else if (possibleTargets.Flow) {
1414 tags.push(Tags.flow);
1415 }
1416 else {
1417 tags.push(Tags.flow, Tags.typescript);
1418 }
1419 return tags;
1420 }
1421 return [
1422 {
1423 name: 'Backend - API or server',
1424 key: 'backend',
1425 value: withFlowOrTypescript([Tags.node]),
1426 checked: possibleTargets.Node,
1427 },
1428 {
1429 name: 'Application built with Angular',
1430 key: 'angular',
1431 value: [Tags.angular, Tags.browser, Tags.typescript],
1432 checked: possibleTargets.Angular,
1433 },
1434 {
1435 name: 'Application built with React',
1436 key: 'react',
1437 value: withFlowOrTypescript([Tags.react, Tags.browser]),
1438 checked: possibleTargets.React,
1439 },
1440 {
1441 name: 'Application built with Stencil',
1442 key: 'stencil',
1443 value: [Tags.stencil, Tags.browser, Tags.typescript],
1444 checked: possibleTargets.Stencil,
1445 },
1446 {
1447 name: 'Application built with other framework or vanilla JS',
1448 key: 'client',
1449 value: [Tags.browser, Tags.typescript, Tags.flow],
1450 checked: possibleTargets.Browser && !possibleTargets.Angular && !possibleTargets.React && !possibleTargets.Stencil,
1451 },
1452 ];
1453}
1454function getPluginChoices(answers) {
1455 return plugins
1456 .filter(p => p.available(answers.targets))
1457 .map(p => {
1458 return {
1459 name: p.name,
1460 value: p,
1461 checked: p.shouldBeSelected(answers.targets),
1462 };
1463 });
1464}
1465function normalizeTargets(targets) {
1466 return [].concat(...targets);
1467}
1468function getOutputDefaultValue(answers) {
1469 if (answers.plugins.some(plugin => plugin.defaultExtension === '.tsx')) {
1470 return 'src/generated/graphql.tsx';
1471 }
1472 else if (answers.plugins.some(plugin => plugin.defaultExtension === '.ts')) {
1473 return 'src/generated/graphql.ts';
1474 }
1475 else {
1476 return 'src/generated/graphql.js';
1477 }
1478}
1479
1480async function guessTargets() {
1481 const pkg = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), 'package.json'), {
1482 encoding: 'utf-8',
1483 }));
1484 const dependencies = Object.keys({
1485 ...pkg.dependencies,
1486 ...pkg.devDependencies,
1487 });
1488 return {
1489 [Tags.angular]: isAngular(dependencies),
1490 [Tags.react]: isReact(dependencies),
1491 [Tags.stencil]: isStencil(dependencies),
1492 [Tags.vue]: isVue(dependencies),
1493 [Tags.browser]: false,
1494 [Tags.node]: false,
1495 [Tags.typescript]: isTypescript(dependencies),
1496 [Tags.flow]: isFlow(dependencies),
1497 };
1498}
1499function isAngular(dependencies) {
1500 return dependencies.includes('@angular/core');
1501}
1502function isReact(dependencies) {
1503 return dependencies.includes('react');
1504}
1505function isStencil(dependencies) {
1506 return dependencies.includes('@stencil/core');
1507}
1508function isVue(dependencies) {
1509 return dependencies.includes('vue') || dependencies.includes('nuxt');
1510}
1511function isTypescript(dependencies) {
1512 return dependencies.includes('typescript');
1513}
1514function isFlow(dependencies) {
1515 return dependencies.includes('flow');
1516}
1517
1518function log$1(...msgs) {
1519 // eslint-disable-next-line no-console
1520 console.log(...msgs);
1521}
1522async function init() {
1523 log$1(`
1524 Welcome to ${bold('GraphQL Code Generator')}!
1525 Answer few questions and we will setup everything for you.
1526 `);
1527 const possibleTargets = await guessTargets();
1528 const answers = await inquirer.prompt(getQuestions(possibleTargets));
1529 // define config
1530 const config = {
1531 overwrite: true,
1532 schema: answers.schema,
1533 documents: answers.targets.includes(Tags.browser) ? answers.documents : null,
1534 generates: {
1535 [answers.output]: {
1536 plugins: answers.plugins.map(p => p.value),
1537 },
1538 },
1539 };
1540 // introspection
1541 if (answers.introspection) {
1542 addIntrospection(config);
1543 }
1544 // config file
1545 const { relativePath } = await writeConfig(answers, config);
1546 // write package.json
1547 await writePackage(answers, relativePath);
1548 // Emit status to the terminal
1549 log$1(`
1550 Config file generated at ${bold(relativePath)}
1551
1552 ${bold('$ npm install')}
1553
1554 To install the plugins.
1555
1556 ${bold(`$ npm run ${answers.script}`)}
1557
1558 To run GraphQL Code Generator.
1559 `);
1560}
1561// adds an introspection to `generates`
1562function addIntrospection(config) {
1563 config.generates['./graphql.schema.json'] = {
1564 plugins: ['introspection'],
1565 };
1566}
1567
1568function runCli(cmd) {
1569 ensureGraphQlPackage();
1570 switch (cmd) {
1571 case 'init':
1572 return init();
1573 default: {
1574 return createContext().then(context => {
1575 return generate(context).catch(async (error) => {
1576 await lifecycleHooks(context.getConfig().hooks).onError(error.toString());
1577 throw error;
1578 });
1579 });
1580 }
1581 }
1582}
1583function ensureGraphQlPackage() {
1584 try {
1585 require('graphql');
1586 }
1587 catch (e) {
1588 throw new pluginHelpers.DetailedError(`Unable to load "graphql" package. Please make sure to install "graphql" as a dependency!`, `
1589 To install "graphql", run:
1590 yarn add graphql
1591 Or, with NPM:
1592 npm install --save graphql
1593`);
1594 }
1595}
1596
1597const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
1598const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
1599
1600function cliError(err, exitOnError = true) {
1601 let msg;
1602 if (err instanceof Error) {
1603 msg = err.message || err.toString();
1604 }
1605 else if (typeof err === 'string') {
1606 msg = err;
1607 }
1608 else {
1609 msg = JSON.stringify(err);
1610 }
1611 // eslint-disable-next-line no-console
1612 console.error(msg);
1613 if (exitOnError && isNode) {
1614 process.exit(1);
1615 }
1616 else if (exitOnError && isBrowser) {
1617 throw err;
1618 }
1619}
1620
1621const [, , cmd] = process.argv;
1622runCli(cmd)
1623 .then(() => {
1624 process.exit(0);
1625})
1626 .catch(error => {
1627 cliError(error);
1628});