UNPKG

13.3 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
7const addPlugin = _interopDefault(require('@graphql-codegen/add'));
8const path = require('path');
9const graphql = require('graphql');
10const parsePath = _interopDefault(require('parse-filepath'));
11const pluginHelpers = require('@graphql-codegen/plugin-helpers');
12const visitorPluginCommon = require('@graphql-codegen/visitor-plugin-common');
13
14function defineFilepathSubfolder(baseFilePath, folder) {
15 const parsedPath = parsePath(baseFilePath);
16 return path.join(parsedPath.dir, folder, parsedPath.base).replace(/\\/g, '/');
17}
18function appendExtensionToFilePath(baseFilePath, extension) {
19 const parsedPath = parsePath(baseFilePath);
20 return path.join(parsedPath.dir, parsedPath.name + extension).replace(/\\/g, '/');
21}
22function extractExternalFragmentsInUse(documentNode, fragmentNameToFile, result = {}, level = 0) {
23 const ignoreList = new Set();
24 // First, take all fragments definition from the current file, and mark them as ignored
25 graphql.visit(documentNode, {
26 enter: {
27 FragmentDefinition: (node) => {
28 ignoreList.add(node.name.value);
29 },
30 },
31 });
32 // Then, look for all used fragments in this document
33 graphql.visit(documentNode, {
34 enter: {
35 FragmentSpread: (node) => {
36 if (!ignoreList.has(node.name.value)) {
37 if (result[node.name.value] === undefined ||
38 (result[node.name.value] !== undefined && level < result[node.name.value])) {
39 result[node.name.value] = level;
40 if (fragmentNameToFile[node.name.value]) {
41 extractExternalFragmentsInUse(fragmentNameToFile[node.name.value].node, fragmentNameToFile, result, level + 1);
42 }
43 }
44 }
45 },
46 },
47 });
48 return result;
49}
50
51/**
52 * Used by `buildFragmentResolver` to build a mapping of fragmentNames to paths, importNames, and other useful info
53 */
54function buildFragmentRegistry({ generateFilePath }, { documents, config }, schemaObject) {
55 const baseVisitor = new visitorPluginCommon.BaseVisitor(config, {
56 scalars: visitorPluginCommon.buildScalars(schemaObject, config.scalars),
57 dedupeOperationSuffix: visitorPluginCommon.getConfigValue(config.dedupeOperationSuffix, false),
58 omitOperationSuffix: visitorPluginCommon.getConfigValue(config.omitOperationSuffix, false),
59 fragmentVariablePrefix: visitorPluginCommon.getConfigValue(config.fragmentVariablePrefix, ''),
60 fragmentVariableSuffix: visitorPluginCommon.getConfigValue(config.fragmentVariableSuffix, 'FragmentDoc'),
61 });
62 const getFragmentImports = (possbileTypes, name) => {
63 const fragmentImports = [];
64 fragmentImports.push({ name: baseVisitor.getFragmentVariableName(name), kind: 'document' });
65 const fragmentSuffix = baseVisitor.getFragmentSuffix(name);
66 if (possbileTypes.length === 1) {
67 fragmentImports.push({
68 name: baseVisitor.convertName(name, {
69 useTypesPrefix: true,
70 suffix: fragmentSuffix,
71 }),
72 kind: 'type',
73 });
74 }
75 else if (possbileTypes.length !== 0) {
76 possbileTypes.forEach(typeName => {
77 fragmentImports.push({
78 name: baseVisitor.convertName(name, {
79 useTypesPrefix: true,
80 suffix: `_${typeName}_${fragmentSuffix}`,
81 }),
82 kind: 'type',
83 });
84 });
85 }
86 return fragmentImports;
87 };
88 const duplicateFragmentNames = [];
89 const registry = documents.reduce((prev, documentRecord) => {
90 const fragments = documentRecord.document.definitions.filter(d => d.kind === graphql.Kind.FRAGMENT_DEFINITION);
91 if (fragments.length > 0) {
92 for (const fragment of fragments) {
93 const schemaType = schemaObject.getType(fragment.typeCondition.name.value);
94 if (!schemaType) {
95 throw new Error(`Fragment "${fragment.name.value}" is set on non-existing type "${fragment.typeCondition.name.value}"!`);
96 }
97 const possibleTypes = visitorPluginCommon.getPossibleTypes(schemaObject, schemaType);
98 const filePath = generateFilePath(documentRecord.location);
99 const imports = getFragmentImports(possibleTypes.map(t => t.name), fragment.name.value);
100 if (prev[fragment.name.value] && graphql.print(fragment) !== graphql.print(prev[fragment.name.value].node)) {
101 duplicateFragmentNames.push(fragment.name.value);
102 }
103 prev[fragment.name.value] = {
104 filePath,
105 imports,
106 onType: fragment.typeCondition.name.value,
107 node: fragment,
108 };
109 }
110 }
111 return prev;
112 }, {});
113 if (duplicateFragmentNames.length) {
114 throw new Error(`Multiple fragments with the name(s) "${duplicateFragmentNames.join(', ')}" were found.`);
115 }
116 return registry;
117}
118/**
119 * Builds a fragment "resolver" that collects `externalFragments` definitions and `fragmentImportStatements`
120 */
121function buildFragmentResolver(collectorOptions, presetOptions, schemaObject) {
122 const fragmentRegistry = buildFragmentRegistry(collectorOptions, presetOptions, schemaObject);
123 const { baseOutputDir } = presetOptions;
124 const { baseDir, typesImport } = collectorOptions;
125 function resolveFragments(generatedFilePath, documentFileContent) {
126 const fragmentsInUse = extractExternalFragmentsInUse(documentFileContent, fragmentRegistry);
127 const externalFragments = [];
128 // fragment files to import names
129 const fragmentFileImports = {};
130 for (const fragmentName of Object.keys(fragmentsInUse)) {
131 const level = fragmentsInUse[fragmentName];
132 const fragmentDetails = fragmentRegistry[fragmentName];
133 if (fragmentDetails) {
134 // add top level references to the import object
135 // we don't checkf or global namespace because the calling config can do so
136 if (level === 0) {
137 if (fragmentFileImports[fragmentDetails.filePath] === undefined) {
138 fragmentFileImports[fragmentDetails.filePath] = fragmentDetails.imports;
139 }
140 else {
141 fragmentFileImports[fragmentDetails.filePath].push(...fragmentDetails.imports);
142 }
143 }
144 externalFragments.push({
145 level,
146 isExternal: true,
147 name: fragmentName,
148 onType: fragmentDetails.onType,
149 node: fragmentDetails.node,
150 });
151 }
152 }
153 return {
154 externalFragments,
155 fragmentImports: Object.entries(fragmentFileImports).map(([fragmentsFilePath, identifiers]) => ({
156 baseDir,
157 baseOutputDir,
158 outputPath: generatedFilePath,
159 importSource: {
160 path: fragmentsFilePath,
161 identifiers,
162 },
163 typesImport,
164 })),
165 };
166 }
167 return resolveFragments;
168}
169
170/**
171 * Transform the preset's provided documents into single-file generator sources, while resolving fragment and user-defined imports
172 *
173 * Resolves user provided imports and fragment imports using the `DocumentImportResolverOptions`.
174 * Does not define specific plugins, but rather returns a string[] of `importStatements` for the calling plugin to make use of
175 */
176function resolveDocumentImports(presetOptions, schemaObject, importResolverOptions) {
177 const resolveFragments = buildFragmentResolver(importResolverOptions, presetOptions, schemaObject);
178 const { baseOutputDir, documents } = presetOptions;
179 const { generateFilePath, schemaTypesSource, baseDir, typesImport } = importResolverOptions;
180 return documents.map(documentFile => {
181 try {
182 const generatedFilePath = generateFilePath(documentFile.location);
183 const importStatements = [];
184 const { externalFragments, fragmentImports } = resolveFragments(generatedFilePath, documentFile.document);
185 if (pluginHelpers.isUsingTypes(documentFile.document, externalFragments.map(m => m.name), schemaObject)) {
186 const schemaTypesImportStatement = visitorPluginCommon.generateImportStatement({
187 baseDir,
188 importSource: visitorPluginCommon.resolveImportSource(schemaTypesSource),
189 baseOutputDir,
190 outputPath: generatedFilePath,
191 typesImport,
192 });
193 importStatements.unshift(schemaTypesImportStatement);
194 }
195 return {
196 filename: generatedFilePath,
197 documents: [documentFile],
198 importStatements,
199 fragmentImports,
200 externalFragments,
201 };
202 }
203 catch (e) {
204 throw new pluginHelpers.DetailedError(`Unable to validate GraphQL document!`, `
205 File ${documentFile.location} caused error:
206 ${e.message || e.toString()}
207 `, documentFile.location);
208 }
209 });
210}
211
212const preset = {
213 buildGeneratesSection: options => {
214 var _a;
215 const schemaObject = options.schemaAst
216 ? options.schemaAst
217 : graphql.buildASTSchema(options.schema, options.config);
218 const baseDir = options.presetConfig.cwd || process.cwd();
219 const extension = options.presetConfig.extension || '.generated.ts';
220 const folder = options.presetConfig.folder || '';
221 const importTypesNamespace = options.presetConfig.importTypesNamespace || 'Types';
222 const importAllFragmentsFrom = options.presetConfig.importAllFragmentsFrom || null;
223 const baseTypesPath = options.presetConfig.baseTypesPath;
224 if (!baseTypesPath) {
225 throw new Error(`Preset "near-operation-file" requires you to specify "baseTypesPath" configuration and point it to your base types file (generated by "typescript" plugin)!`);
226 }
227 const shouldAbsolute = !baseTypesPath.startsWith('~');
228 const pluginMap = {
229 ...options.pluginMap,
230 add: addPlugin,
231 };
232 const sources = resolveDocumentImports(options, schemaObject, {
233 baseDir,
234 generateFilePath(location) {
235 const newFilePath = defineFilepathSubfolder(location, folder);
236 return appendExtensionToFilePath(newFilePath, extension);
237 },
238 schemaTypesSource: {
239 path: shouldAbsolute ? path.join(options.baseOutputDir, baseTypesPath) : baseTypesPath,
240 namespace: importTypesNamespace,
241 },
242 typesImport: (_a = options.config.useTypeImports) !== null && _a !== void 0 ? _a : false,
243 });
244 return sources.map(({ importStatements, externalFragments, fragmentImports, ...source }) => {
245 let fragmentImportsArr = fragmentImports;
246 if (importAllFragmentsFrom) {
247 fragmentImportsArr = fragmentImports.map(t => {
248 const newImportSource = typeof importAllFragmentsFrom === 'string'
249 ? { ...t.importSource, path: importAllFragmentsFrom }
250 : importAllFragmentsFrom(t.importSource, source.filename);
251 return {
252 ...t,
253 importSource: newImportSource || t.importSource,
254 };
255 });
256 }
257 const plugins = [
258 // TODO/NOTE I made globalNamespace include schema types - is that correct?
259 ...(options.config.globalNamespace
260 ? []
261 : importStatements.map(importStatement => ({ add: { content: importStatement } }))),
262 ...options.plugins,
263 ];
264 const config = {
265 ...options.config,
266 // This is set here in order to make sure the fragment spreads sub types
267 // are exported from operations file
268 exportFragmentSpreadSubTypes: true,
269 namespacedImportName: importTypesNamespace,
270 externalFragments,
271 fragmentImports: fragmentImportsArr,
272 };
273 return {
274 ...source,
275 plugins,
276 pluginMap,
277 config,
278 schema: options.schema,
279 schemaAst: schemaObject,
280 skipDocumentsValidation: true,
281 };
282 });
283 },
284};
285
286exports.default = preset;
287exports.preset = preset;
288exports.resolveDocumentImports = resolveDocumentImports;
289//# sourceMappingURL=index.cjs.js.map