UNPKG

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