UNPKG

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