UNPKG

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