UNPKG

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