UNPKG

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