UNPKG

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