UNPKG

13.1 kBJavaScriptView Raw
1"use strict";
2var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 return new (P || (P = Promise))(function (resolve, reject) {
4 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6 function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
7 step((generator = generator.apply(thisArg, _arguments || [])).next());
8 });
9};
10var __rest = (this && this.__rest) || function (s, e) {
11 var t = {};
12 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
13 t[p] = s[p];
14 if (s != null && typeof Object.getOwnPropertySymbols === "function")
15 for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
16 t[p[i]] = s[p[i]];
17 return t;
18};
19var __importDefault = (this && this.__importDefault) || function (mod) {
20 return (mod && mod.__esModule) ? mod : { "default": mod };
21};
22Object.defineProperty(exports, "__esModule", { value: true });
23const events_1 = require("events");
24const graphql_1 = require("graphql");
25const chalk_1 = __importDefault(require("chalk"));
26const path_1 = require("path");
27const fs_extra_1 = require("fs-extra");
28const chokidar_1 = require("chokidar");
29const graphql_config_1 = require("graphql-config");
30const graphql_tool_utilities_1 = require("graphql-tool-utilities");
31const print_1 = require("./print");
32const types_1 = require("./types");
33exports.EnumFormat = types_1.EnumFormat;
34class Builder extends events_1.EventEmitter {
35 constructor(_a) {
36 var { cwd } = _a, options = __rest(_a, ["cwd"]);
37 super();
38 // projectName -> {filePath -> document}
39 // NOTE: projectName can be undefined for nameless graphql-config projects
40 this.documentMapByProject = new Map();
41 this.watchers = [];
42 this.options = options;
43 this.config = graphql_config_1.getGraphQLConfig(cwd ? path_1.resolve(cwd) : undefined);
44 }
45 once(event, handler) {
46 return super.once(event, handler);
47 }
48 on(event, handler) {
49 return super.on(event, handler);
50 }
51 emit(event, ...args) {
52 return super.emit(event, ...args);
53 }
54 run({ watch: watchGlobs = false } = {}) {
55 return __awaiter(this, void 0, void 0, function* () {
56 let schemaPaths;
57 try {
58 schemaPaths = graphql_tool_utilities_1.getGraphQLSchemaPaths(this.config);
59 }
60 catch (error) {
61 this.emit('error', error);
62 return;
63 }
64 if (watchGlobs) {
65 this.watchers.push(...this.setupDocumentWatchers().concat(this.setupSchemaWatcher()));
66 // wait for all watchers to be ready
67 yield Promise.all(this.watchers.map((watcher) => new Promise((resolve) => watcher.on('ready', () => resolve()))));
68 }
69 try {
70 this.emit('start:schema');
71 yield Promise.all(schemaPaths.map((schemaPath) => this.generateSchemaTypes(schemaPath)));
72 this.emit('end:schema');
73 }
74 catch (error) {
75 this.emit('error', error);
76 return;
77 }
78 try {
79 yield Promise.all(graphql_tool_utilities_1.getGraphQLProjects(this.config).map((projectConfig) => this.updateDocumentsForProject(projectConfig)));
80 }
81 catch (error) {
82 this.emit('error', error);
83 return;
84 }
85 yield this.generateDocumentTypes();
86 });
87 }
88 stop() {
89 this.watchers.forEach((watcher) => {
90 watcher.close();
91 });
92 this.watchers.length = 0;
93 }
94 setupDocumentWatchers() {
95 const update = (filePath, projectConfig) => __awaiter(this, void 0, void 0, function* () {
96 try {
97 yield this.updateDocumentForFile(filePath, projectConfig);
98 }
99 catch (error) {
100 this.emit('error', error);
101 return;
102 }
103 yield this.generateDocumentTypes();
104 });
105 return graphql_tool_utilities_1.getGraphQLProjects(this.config)
106 .filter(({ includes }) => includes.length > 0)
107 .map((projectConfig) => {
108 return chokidar_1.watch(projectConfig.includes.map((include) => graphql_tool_utilities_1.resolvePathRelativeToConfig(projectConfig, include)), {
109 ignored: projectConfig.excludes.map((exclude) => graphql_tool_utilities_1.resolvePathRelativeToConfig(projectConfig, exclude)),
110 ignoreInitial: true,
111 })
112 .on('add', (filePath) => update(filePath, projectConfig))
113 .on('change', (filePath) => update(filePath, projectConfig))
114 .on('unlink', (filePath) => __awaiter(this, void 0, void 0, function* () {
115 const documents = this.documentMapByProject.get(projectConfig.projectName);
116 if (documents) {
117 documents.delete(filePath);
118 }
119 yield this.generateDocumentTypes();
120 }));
121 });
122 }
123 setupSchemaWatcher() {
124 const update = (schemaPath) => __awaiter(this, void 0, void 0, function* () {
125 try {
126 this.emit('start:schema');
127 yield this.generateSchemaTypes(schemaPath);
128 this.emit('end:schema');
129 yield this.generateDocumentTypes();
130 }
131 catch (error) {
132 // intentional noop
133 }
134 });
135 return chokidar_1.watch(graphql_tool_utilities_1.getGraphQLSchemaPaths(this.config), { ignoreInitial: true }).on('change', update);
136 }
137 generateSchemaTypes(schemaPath) {
138 return __awaiter(this, void 0, void 0, function* () {
139 const projectConfig = graphql_tool_utilities_1.getGraphQLProjectForSchemaPath(this.config, schemaPath);
140 const schemaTypesPath = getSchemaTypesPath(projectConfig, this.options);
141 const definitions = print_1.generateSchemaTypes(projectConfig.getSchema(), this.options);
142 yield fs_extra_1.mkdirp(schemaTypesPath);
143 yield Promise.all(Array.from(definitions.entries()).map(([fileName, definition]) => fs_extra_1.writeFile(path_1.join(schemaTypesPath, fileName), definition)));
144 this.emit('build:schema', {
145 schemaPath,
146 schemaTypesPath,
147 });
148 });
149 }
150 generateDocumentTypes() {
151 return __awaiter(this, void 0, void 0, function* () {
152 this.emit('start:docs');
153 this.checkForDuplicateOperations();
154 yield Promise.all(Array.from(this.documentMapByProject.entries()).map(([projectName, documents]) => this.generateDocumentTypesForProject(this.config.getProjectConfig(projectName), documents)));
155 this.emit('end:docs');
156 });
157 }
158 checkForDuplicateOperations() {
159 getDuplicateOperations(this.documentMapByProject).forEach(({ projectName, duplicates }) => {
160 if (duplicates.length) {
161 duplicates.forEach(({ operationName, filePaths }) => {
162 const message = `GraphQL operations must have a unique name. The operation ${chalk_1.default.bold(operationName)} is declared in:\n ${filePaths.sort().join('\n ')}${projectName ? ` (${chalk_1.default.bold(projectName)})` : ''}`;
163 this.emit('error', new Error(message));
164 });
165 }
166 });
167 }
168 generateDocumentTypesForProject(projectConfig, documents) {
169 return __awaiter(this, void 0, void 0, function* () {
170 let ast;
171 try {
172 ast = graphql_tool_utilities_1.compile(projectConfig.getSchema(), graphql_1.concatAST(Array.from(documents.values())));
173 }
174 catch (error) {
175 this.emit('error', error);
176 return;
177 }
178 try {
179 yield Promise.all(Array.from(groupOperationsAndFragmentsByFile(ast).values()).map((file) => this.writeDocumentFile(file, ast, projectConfig)));
180 }
181 catch (error) {
182 // intentional noop
183 }
184 });
185 }
186 writeDocumentFile(file, ast, projectConfig) {
187 return __awaiter(this, void 0, void 0, function* () {
188 const definitionPath = `${file.path}.d.ts`;
189 yield fs_extra_1.writeFile(definitionPath, this.getDocumentDefinition(file, ast, projectConfig));
190 this.emit('build:docs', {
191 documentPath: file.path,
192 definitionPath,
193 operation: file.operation,
194 fragments: file.fragments,
195 });
196 });
197 }
198 getDocumentDefinition(file, ast, projectConfig) {
199 try {
200 return print_1.printDocument(file, ast, {
201 enumFormat: this.options.enumFormat,
202 addTypename: this.options.addTypename,
203 schemaTypesPath: getSchemaTypesPath(projectConfig, this.options),
204 });
205 }
206 catch ({ message }) {
207 const error = new Error(`Error in ${file.path}: ${message[0].toLowerCase()}${message.slice(1)}`);
208 this.emit('error', error);
209 throw error;
210 }
211 }
212 updateDocumentsForProject(projectConfig) {
213 return __awaiter(this, void 0, void 0, function* () {
214 const filePaths = yield graphql_tool_utilities_1.getGraphQLProjectIncludedFilePaths(projectConfig);
215 return Promise.all(filePaths.map((filePath) => this.updateDocumentForFile(filePath, projectConfig)));
216 });
217 }
218 updateDocumentForFile(filePath, projectConfig) {
219 return __awaiter(this, void 0, void 0, function* () {
220 const contents = yield fs_extra_1.readFile(filePath, 'utf8');
221 return this.setDocumentForFilePath(filePath, projectConfig, contents);
222 });
223 }
224 setDocumentForFilePath(filePath, projectConfig, contents) {
225 let documents = this.documentMapByProject.get(projectConfig.projectName);
226 if (!documents) {
227 documents = new Map();
228 this.documentMapByProject.set(projectConfig.projectName, documents);
229 }
230 if (contents.trim().length === 0) {
231 return undefined;
232 }
233 const document = graphql_1.parse(new graphql_1.Source(contents, filePath));
234 documents.set(filePath, document);
235 return document;
236 }
237}
238exports.Builder = Builder;
239function getSchemaTypesPath(projectConfig, options) {
240 if (typeof projectConfig.extensions.schemaTypesPath === 'string') {
241 return graphql_tool_utilities_1.resolvePathRelativeToConfig(projectConfig, projectConfig.extensions.schemaTypesPath);
242 }
243 return graphql_tool_utilities_1.resolvePathRelativeToConfig(projectConfig, path_1.join(options.schemaTypesPath, `${projectConfig.projectName ? `${projectConfig.projectName}-` : ''}types`));
244}
245function groupOperationsAndFragmentsByFile({ operations, fragments }) {
246 return Object.values(operations)
247 .concat(Object.values(fragments))
248 .reduce((map, item) => {
249 if (!item.filePath) {
250 return map;
251 }
252 let file = map.get(item.filePath);
253 if (!file) {
254 file = {
255 path: item.filePath,
256 operation: undefined,
257 fragments: [],
258 };
259 map.set(item.filePath, file);
260 }
261 if (graphql_tool_utilities_1.isOperation(item)) {
262 file.operation = item;
263 }
264 else {
265 file.fragments.push(item);
266 }
267 return map;
268 }, new Map());
269}
270function getDuplicateOperations(documentsMapByProject) {
271 return Array.from(documentsMapByProject.entries()).map(([projectName, documents]) => {
272 return {
273 projectName,
274 duplicates: getDuplicateProjectOperations(documents),
275 };
276 });
277}
278function getDuplicateProjectOperations(documents) {
279 const operations = new Map();
280 Array.from(documents.entries()).forEach(([filePath, document]) => {
281 document.definitions.filter(isOperationDefinition).forEach((definition) => {
282 const { name } = definition;
283 if (name && name.value) {
284 const map = operations.get(name.value);
285 if (map) {
286 map.add(filePath);
287 }
288 else {
289 operations.set(name.value, new Set([filePath]));
290 }
291 }
292 });
293 });
294 return Array.from(operations.entries())
295 .filter(([, filePaths]) => filePaths.size > 1)
296 .map(([operationName, filePath]) => {
297 return { operationName, filePaths: Array.from(filePath) };
298 });
299}
300function isOperationDefinition(definition) {
301 return definition.kind === 'OperationDefinition';
302}