UNPKG

9.42 kBJavaScriptView Raw
1import { isSchema, parse } from 'graphql';
2import { asArray, isValidPath, parseGraphQLSDL, isDocumentNode, AggregateError, } from '@graphql-tools/utils';
3import { gqlPluckFromCodeString, gqlPluckFromCodeStringSync, } from '@graphql-tools/graphql-tag-pluck';
4import globby from 'globby';
5import unixify from 'unixify';
6import { tryToLoadFromExport, tryToLoadFromExportSync } from './load-from-module.js';
7import { isAbsolute, resolve } from 'path';
8import { cwd, env } from 'process';
9import { readFileSync, promises as fsPromises, existsSync } from 'fs';
10import { createRequire } from 'module';
11const { readFile, access } = fsPromises;
12const FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.vue', '.svelte'];
13function createGlobbyOptions(options) {
14 return { absolute: true, ...options, ignore: [] };
15}
16const buildIgnoreGlob = (path) => `!${path}`;
17/**
18 * This loader loads GraphQL documents and type definitions from code files
19 * using `graphql-tag-pluck`.
20 *
21 * ```js
22 * const documents = await loadDocuments('queries/*.js', {
23 * loaders: [
24 * new CodeFileLoader()
25 * ]
26 * });
27 * ```
28 *
29 * Supported extensions include: `.ts`, `.tsx`, `.js`, `.jsx`, `.vue`, `.svelte`
30 */
31export class CodeFileLoader {
32 constructor(config) {
33 this.config = config !== null && config !== void 0 ? config : {};
34 }
35 getMergedOptions(options) {
36 return { ...this.config, ...options };
37 }
38 async canLoad(pointer, options) {
39 options = this.getMergedOptions(options);
40 if (isValidPath(pointer)) {
41 if (FILE_EXTENSIONS.find(extension => pointer.endsWith(extension))) {
42 const normalizedFilePath = isAbsolute(pointer) ? pointer : resolve(options.cwd || cwd(), pointer);
43 try {
44 await access(normalizedFilePath);
45 return true;
46 }
47 catch (_a) {
48 return false;
49 }
50 }
51 }
52 return false;
53 }
54 canLoadSync(pointer, options) {
55 options = this.getMergedOptions(options);
56 if (isValidPath(pointer)) {
57 if (FILE_EXTENSIONS.find(extension => pointer.endsWith(extension))) {
58 const normalizedFilePath = isAbsolute(pointer) ? pointer : resolve(options.cwd || cwd(), pointer);
59 return existsSync(normalizedFilePath);
60 }
61 }
62 return false;
63 }
64 _buildGlobs(glob, options) {
65 const ignores = asArray(options.ignore || []);
66 const globs = [unixify(glob), ...ignores.map(v => buildIgnoreGlob(unixify(v)))];
67 return globs;
68 }
69 async resolveGlobs(glob, options) {
70 options = this.getMergedOptions(options);
71 const globs = this._buildGlobs(glob, options);
72 return globby(globs, createGlobbyOptions(options));
73 }
74 resolveGlobsSync(glob, options) {
75 options = this.getMergedOptions(options);
76 const globs = this._buildGlobs(glob, options);
77 return globby.sync(globs, createGlobbyOptions(options));
78 }
79 async load(pointer, options) {
80 options = this.getMergedOptions(options);
81 const resolvedPaths = await this.resolveGlobs(pointer, options);
82 const finalResult = [];
83 const errors = [];
84 await Promise.all(resolvedPaths.map(async (path) => {
85 try {
86 const result = await this.handleSinglePath(path, options);
87 result === null || result === void 0 ? void 0 : result.forEach(result => finalResult.push(result));
88 }
89 catch (e) {
90 if (env['DEBUG']) {
91 console.error(e);
92 }
93 errors.push(e);
94 }
95 }));
96 if (errors.length > 0 && (options.noSilentErrors || finalResult.length === 0)) {
97 if (errors.length === 1) {
98 throw errors[0];
99 }
100 throw new AggregateError(errors, `Reading from ${pointer} failed ; \n ` + errors.map((e) => e.message).join('\n'));
101 }
102 return finalResult;
103 }
104 loadSync(pointer, options) {
105 options = this.getMergedOptions(options);
106 const resolvedPaths = this.resolveGlobsSync(pointer, options);
107 const finalResult = [];
108 const errors = [];
109 for (const path of resolvedPaths) {
110 if (this.canLoadSync(path, options)) {
111 try {
112 const result = this.handleSinglePathSync(path, options);
113 result === null || result === void 0 ? void 0 : result.forEach(result => finalResult.push(result));
114 }
115 catch (e) {
116 if (env['DEBUG']) {
117 console.error(e);
118 }
119 errors.push(e);
120 }
121 }
122 }
123 if (errors.length > 0 && (options.noSilentErrors || finalResult.length === 0)) {
124 if (errors.length === 1) {
125 throw errors[0];
126 }
127 throw new AggregateError(errors, `Reading from ${pointer} failed ; \n ` + errors.map((e) => e.message).join('\n'));
128 }
129 return finalResult;
130 }
131 async handleSinglePath(location, options) {
132 if (!(await this.canLoad(location, options))) {
133 return [];
134 }
135 options = this.getMergedOptions(options);
136 const normalizedFilePath = ensureAbsolutePath(location, options);
137 const errors = [];
138 if (!options.noPluck) {
139 try {
140 const content = await readFile(normalizedFilePath, { encoding: 'utf-8' });
141 const sources = await gqlPluckFromCodeString(normalizedFilePath, content, options.pluckConfig);
142 if (sources.length) {
143 return sources.map(source => ({
144 rawSDL: source.body,
145 document: parse(source),
146 location,
147 }));
148 }
149 }
150 catch (e) {
151 if (env['DEBUG']) {
152 console.error(`Failed to load schema from code file "${normalizedFilePath}": ${e.message}`);
153 }
154 errors.push(e);
155 }
156 }
157 if (!options.noRequire) {
158 try {
159 if (options && options.require) {
160 await Promise.all(asArray(options.require).map(m => import(m)));
161 }
162 const loaded = await tryToLoadFromExport(normalizedFilePath);
163 const sources = asArray(loaded)
164 .map(value => resolveSource(location, value, options))
165 .filter(Boolean);
166 if (sources.length) {
167 return sources;
168 }
169 }
170 catch (e) {
171 errors.push(e);
172 }
173 }
174 if (errors.length > 0) {
175 throw errors[0];
176 }
177 return [];
178 }
179 handleSinglePathSync(location, options) {
180 if (!this.canLoadSync(location, options)) {
181 return [];
182 }
183 options = this.getMergedOptions(options);
184 const normalizedFilePath = ensureAbsolutePath(location, options);
185 const errors = [];
186 if (!options.noPluck) {
187 try {
188 const content = readFileSync(normalizedFilePath, { encoding: 'utf-8' });
189 const sources = gqlPluckFromCodeStringSync(normalizedFilePath, content, options.pluckConfig);
190 if (sources.length) {
191 return sources.map(source => ({
192 rawSDL: source.body,
193 document: parse(source),
194 location,
195 }));
196 }
197 }
198 catch (e) {
199 if (env['DEBUG']) {
200 console.error(`Failed to load schema from code file "${normalizedFilePath}": ${e.message}`);
201 }
202 errors.push(e);
203 }
204 }
205 if (!options.noRequire) {
206 try {
207 if (options && options.require) {
208 const cwdRequire = createRequire(options.cwd || cwd());
209 for (const m of asArray(options.require)) {
210 cwdRequire(m);
211 }
212 }
213 const loaded = tryToLoadFromExportSync(normalizedFilePath);
214 const sources = asArray(loaded)
215 .map(value => resolveSource(location, value, options))
216 .filter(Boolean);
217 if (sources.length) {
218 return sources;
219 }
220 }
221 catch (e) {
222 errors.push(e);
223 }
224 }
225 if (errors.length > 0) {
226 throw errors[0];
227 }
228 return null;
229 }
230}
231function resolveSource(pointer, value, options) {
232 if (typeof value === 'string') {
233 return parseGraphQLSDL(pointer, value, options);
234 }
235 else if (isSchema(value)) {
236 return {
237 location: pointer,
238 schema: value,
239 };
240 }
241 else if (isDocumentNode(value)) {
242 return {
243 location: pointer,
244 document: value,
245 };
246 }
247 return null;
248}
249function ensureAbsolutePath(pointer, options) {
250 return isAbsolute(pointer) ? pointer : resolve(options.cwd || cwd(), pointer);
251}