UNPKG

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