UNPKG

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