UNPKG

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