UNPKG

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