1 | "use strict";
|
2 | var __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 | };
|
10 | var __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 | };
|
19 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
20 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
21 | };
|
22 | Object.defineProperty(exports, "__esModule", { value: true });
|
23 | const events_1 = require("events");
|
24 | const graphql_1 = require("graphql");
|
25 | const chalk_1 = __importDefault(require("chalk"));
|
26 | const path_1 = require("path");
|
27 | const fs_extra_1 = require("fs-extra");
|
28 | const chokidar_1 = require("chokidar");
|
29 | const graphql_config_1 = require("graphql-config");
|
30 | const graphql_tool_utilities_1 = require("graphql-tool-utilities");
|
31 | const print_1 = require("./print");
|
32 | const types_1 = require("./types");
|
33 | exports.EnumFormat = types_1.EnumFormat;
|
34 | class Builder extends events_1.EventEmitter {
|
35 | constructor(_a) {
|
36 | var { cwd } = _a, options = __rest(_a, ["cwd"]);
|
37 | super();
|
38 |
|
39 |
|
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 |
|
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 |
|
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 |
|
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 | }
|
238 | exports.Builder = Builder;
|
239 | function 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 | }
|
245 | function 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 | }
|
270 | function getDuplicateOperations(documentsMapByProject) {
|
271 | return Array.from(documentsMapByProject.entries()).map(([projectName, documents]) => {
|
272 | return {
|
273 | projectName,
|
274 | duplicates: getDuplicateProjectOperations(documents),
|
275 | };
|
276 | });
|
277 | }
|
278 | function 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 | }
|
300 | function isOperationDefinition(definition) {
|
301 | return definition.kind === 'OperationDefinition';
|
302 | }
|