|
1 | | -const githubOctokit = require(`./github-octokit`); |
2 | | -const { mergeDeep, removeKey } = require(`./utils`); |
3 | | -const { getGithubPlainResolverFields } = require(`./plugin-github-fragments`); |
4 | | - |
5 | | -const { createRemoteFileNode } = require(`gatsby-source-filesystem`); |
6 | | -const { |
7 | | - getPluginNodeTypes, |
8 | | - getGithubApiTypes, |
9 | | -} = require("./github-graphql-defs"); |
10 | | -const { MEDIA_TYPES } = require(`./media-types`); |
11 | | - |
12 | 1 | exports.onPreInit = () => {}; |
13 | 2 |
|
14 | | -exports.sourceNodes = async (...args) => { |
15 | | - const [ |
16 | | - { |
17 | | - actions: { createNode }, |
18 | | - createContentDigest, |
19 | | - createNodeId, |
20 | | - getNodesByType, |
21 | | - }, |
22 | | - userPluginOptions, |
23 | | - ] = args; |
24 | | - |
25 | | - const pluginOptions = userPluginOptions ?? {}; |
26 | | - |
27 | | - const { token, pluginNodeTypes: userPluginNodeTypes } = pluginOptions; |
28 | | - |
29 | | - const rawSubpluginsData = []; |
30 | | - |
31 | | - const githubApiTypes = getGithubApiTypes(); |
32 | | - const pluginNodeTypes = getPluginNodeTypes(userPluginNodeTypes); |
33 | | - const githubPlainResolverFields = getGithubPlainResolverFields({ |
34 | | - pluginNodeTypes, |
35 | | - }); |
36 | | - |
37 | | - for (const plugin of pluginOptions.plugins ?? []) { |
38 | | - const resolvedPlugin = plugin.module; |
39 | | - |
40 | | - const subpluginOptions = { ...(plugin.pluginOptions ?? {}) }; |
41 | | - |
42 | | - // Allow all plugins to have a custom token implicitly. |
43 | | - const { token: customToken } = subpluginOptions; |
44 | | - |
45 | | - const graphql = githubOctokit.createGraphQLWithAuth(customToken ?? token); |
46 | | - |
47 | | - const githubSourcePlugin = { |
48 | | - graphql, |
49 | | - githubPlainResolverFields, |
50 | | - githubApiTypes, |
51 | | - pluginNodeTypes, |
52 | | - }; |
53 | | - |
54 | | - const subpluginArgs = [ |
55 | | - { ...args[0], githubSourcePlugin }, |
56 | | - { |
57 | | - ...removeKey(subpluginOptions, `token`), |
58 | | - }, |
59 | | - ...args.slice(2), |
60 | | - ]; |
61 | | - |
62 | | - const pluginData = await resolvedPlugin?.sourceNodes?.(...subpluginArgs); |
63 | | - |
64 | | - // Plugins that doesn't return anything probably created |
65 | | - // the nodes by their own, so just skip it. |
66 | | - if (typeof pluginData === `undefined`) continue; |
67 | | - |
68 | | - for (const k in pluginData) { |
69 | | - pluginData[k] = pluginData[k].map((data) => ({ |
70 | | - ...data, |
71 | | - // Track each node instance by plugin. |
72 | | - // This way we can filter the nodes by the source plugin. |
73 | | - // This is required since we can fetch multiple repositories |
74 | | - // for different purposes. |
75 | | - meta: { |
76 | | - plugin: plugin.name, |
77 | | - key: subpluginOptions.key, |
78 | | - }, |
79 | | - })); |
80 | | - } |
81 | | - |
82 | | - // Otherwise they want we to create the nodes for them. |
83 | | - rawSubpluginsData.push(pluginData); |
84 | | - } |
85 | | - |
86 | | - const dataByType = {}; |
87 | | - |
88 | | - for (const pluginData of rawSubpluginsData) { |
89 | | - for (const k in pluginData) { |
90 | | - dataByType[k] = [...(dataByType[k] ?? []), ...(pluginData[k] ?? [])]; |
91 | | - } |
92 | | - } |
93 | | - |
94 | | - if (typeof pluginOptions?.createCustomMapper === `function`) { |
95 | | - const customMapper = pluginOptions.createCustomMapper({ |
96 | | - githubSourcePlugin: { pluginNodeTypes }, |
97 | | - }); |
98 | | - |
99 | | - for (const type of Object.values(pluginNodeTypes)) { |
100 | | - const mapper = customMapper[type]; |
101 | | - |
102 | | - if (typeof mapper !== `function`) continue; |
103 | | - |
104 | | - dataByType[type] = dataByType[type].map(mapper); |
105 | | - } |
106 | | - } |
107 | | - |
108 | | - /** [dataByType]: |
109 | | - * { |
110 | | - * [pluginNodeTypes.USER]: [{ ...GitHubGraphQLUserType }, { ...GitHubGraphQLUserType }], |
111 | | - * [pluginNodeTypes.REPOSITORY]: [... /** Same as above *\/], |
112 | | - * ... |
113 | | - * } |
114 | | - */ |
115 | | - for (const type in dataByType) { |
116 | | - // we do not recognize this node type, skip it. |
117 | | - if (!Object.values(pluginNodeTypes).includes(type)) continue; |
118 | | - |
119 | | - const items = dataByType[type] ?? []; |
120 | | - |
121 | | - function getMediaType(nodeType) { |
122 | | - switch (nodeType) { |
123 | | - case pluginNodeTypes.DISCUSSION: |
124 | | - case pluginNodeTypes.ISSUE: |
125 | | - return MEDIA_TYPES.MARKDOWN; |
126 | | - default: |
127 | | - return undefined; |
128 | | - } |
129 | | - } |
130 | | - |
131 | | - function getNodeContent(node, type) { |
132 | | - switch (type) { |
133 | | - case pluginNodeTypes.DISCUSSION: |
134 | | - case pluginNodeTypes.ISSUE: |
135 | | - return node.body; |
136 | | - default: |
137 | | - return undefined; |
138 | | - } |
139 | | - } |
140 | | - |
141 | | - const mediaType = getMediaType(type); |
142 | | - |
143 | | - for (const item of items) { |
144 | | - const content = getNodeContent(item, type); |
145 | | - |
146 | | - await createNode({ |
147 | | - ...item, |
148 | | - githubId: item.id, |
149 | | - id: createNodeId(`${type} >>> ${item.id}`), |
150 | | - parent: null, |
151 | | - children: [], |
152 | | - internal: { |
153 | | - type: type, |
154 | | - contentDigest: createContentDigest(item), |
155 | | - content: content, |
156 | | - mediaType: mediaType, |
157 | | - }, |
158 | | - }); |
159 | | - } |
160 | | - } |
161 | | -}; |
162 | | - |
163 | | -exports.onCreateNode = async (...args) => { |
164 | | - const [ |
165 | | - { |
166 | | - node, |
167 | | - actions: { createNode, createNodeField }, |
168 | | - createNodeId, |
169 | | - getCache, |
170 | | - }, |
171 | | - pluginOptions, |
172 | | - ] = args; |
173 | | - |
174 | | - const pluginNodeTypes = getPluginNodeTypes(pluginOptions.pluginNodeTypes); |
175 | | - |
176 | | - const internalTypes = [...Object.values(pluginNodeTypes)]; |
177 | | - |
178 | | - const checkIfIsInternalType = (type) => internalTypes.includes(type); |
179 | | - |
180 | | - const isInternalType = checkIfIsInternalType(node.internal.type); |
181 | | - |
182 | | - async function createFileNodeFrom({ node, key, fieldName } = {}) { |
183 | | - if (typeof node[key] !== `string`) return; |
184 | | - if (!node[key].startsWith(`http`)) return; |
185 | | - |
186 | | - const fileNode = await createRemoteFileNode({ |
187 | | - url: node[key], |
188 | | - parentNodeId: node.id, |
189 | | - createNode, |
190 | | - createNodeId, |
191 | | - getCache, |
192 | | - }); |
193 | | - |
194 | | - if (fileNode) { |
195 | | - createNodeField({ node, name: fieldName ?? key, value: fileNode.id }); |
196 | | - } |
197 | | - } |
198 | | - |
199 | | - // Allow subplugins make use of `onCreateNode` APIs. |
200 | | - for (const plugin of pluginOptions.plugins) { |
201 | | - const resolvedPlugin = plugin.module; |
202 | | - const onCreateNode = resolvedPlugin.onCreateNode; |
203 | | - |
204 | | - const subpluginOptions = plugin.pluginOptions; |
205 | | - |
206 | | - const githubSourcePlugin = { |
207 | | - createFileNodeFrom, |
208 | | - checkIfIsInternalType, |
209 | | - isInternalType, |
210 | | - pluginNodeTypes, |
211 | | - }; |
212 | | - |
213 | | - const subpluginArgs = [ |
214 | | - { ...args[0], githubSourcePlugin }, |
215 | | - { |
216 | | - ...removeKey(subpluginOptions, `token`), |
217 | | - }, |
218 | | - ...args.slice(2), |
219 | | - ]; |
220 | | - |
221 | | - if (typeof onCreateNode === `function`) { |
222 | | - onCreateNode(...subpluginArgs); |
223 | | - } |
224 | | - } |
225 | | - |
226 | | - // Allow end-users make use of `onCreateNode` APIs. |
227 | | - if (typeof pluginOptions?.onCreateNode === `function`) { |
228 | | - const githubSourcePlugin = { |
229 | | - createFileNodeFrom, |
230 | | - checkIfIsInternalType, |
231 | | - isInternalType, |
232 | | - pluginNodeTypes, |
233 | | - }; |
234 | | - |
235 | | - pluginOptions.onCreateNode( |
236 | | - ...[ |
237 | | - { |
238 | | - ...args[0], |
239 | | - githubSourcePlugin, |
240 | | - }, |
241 | | - { |
242 | | - ...removeKey(args[1], `token`), |
243 | | - }, |
244 | | - ...args.slice(2), |
245 | | - ] |
246 | | - ); |
247 | | - } |
248 | | - |
249 | | - if (!isInternalType) return; |
250 | | - |
251 | | - async function handleImageOptimizationForNode({ |
252 | | - node, |
253 | | - targetNodeType, |
254 | | - key, |
255 | | - optionableKey, |
256 | | - }) { |
257 | | - const IMAGE_OPTIMIZATION_KEY_SUFFIX = `SharpOptimized`; |
258 | | - |
259 | | - if (node.internal.type !== targetNodeType) return; |
260 | | - |
261 | | - if (pluginOptions[optionableKey] !== false) { |
262 | | - if (key in node) { |
263 | | - await createFileNodeFrom({ |
264 | | - node, |
265 | | - key: key, |
266 | | - // This is also linked on [createSchemaCustomization] step. See the [pluginNodeTypes.USER] type def. |
267 | | - fieldName: key + IMAGE_OPTIMIZATION_KEY_SUFFIX, |
268 | | - }); |
269 | | - } |
270 | | - } |
271 | | - } |
272 | | - |
273 | | - const targetOptimizationOptions = [ |
274 | | - { |
275 | | - targetNodeType: pluginNodeTypes.USER, |
276 | | - key: `avatarUrl`, |
277 | | - optionableKey: `generateOptimizedGitHubUserAvatarUrl`, |
278 | | - }, |
279 | | - { |
280 | | - targetNodeType: pluginNodeTypes.REPOSITORY, |
281 | | - key: `openGraphImageUrl`, |
282 | | - optionableKey: `generateOptimizedGitHubRepositoryOpenGraphImageUrl`, |
283 | | - }, |
284 | | - ]; |
285 | | - |
286 | | - for (const options of targetOptimizationOptions) { |
287 | | - await handleImageOptimizationForNode({ ...options, node }); |
288 | | - } |
| 3 | +exports.sourceNodes = require(`./source-nodes`); |
289 | 4 |
|
290 | | - // for (const key in node) { |
291 | | - // /** |
292 | | - // * Any key of any node of this plugin that has a key ending with `optimizedsharpimage` |
293 | | - // * will be automatically optimized. |
294 | | - // * This way subplugins can return the data like: |
295 | | - // * ``` |
296 | | - // * return { |
297 | | - // * ...user, |
298 | | - // * avatarOptimizedSharpImage: user.avatarUrl, // optimized version of the avatarUrl of the user |
299 | | - // * } |
300 | | - // * ``` |
301 | | - // */ |
302 | | - // if (key.toLowerCase().endsWith(`optimizedsharpimage`)) { |
303 | | - // await createFileNodeFrom({node, key}) |
304 | | - // } |
305 | | - // } |
306 | | -}; |
| 5 | +exports.onCreateNode = require(`./on-create-node`); |
307 | 6 |
|
308 | 7 | exports.createSchemaCustomization = require(`./create-schema-customization`); |
309 | 8 |
|
310 | | -exports.pluginOptionsSchema = function ({ Joi }) { |
311 | | - return Joi.object({ |
312 | | - token: Joi.string().description( |
313 | | - `Default token will be provided to subplugins to allow fetch data using GitHub GraphQL API v4. Though used for auth, this token is not directly shared.` |
314 | | - ), |
315 | | - optimizedImagesKeyPrefix: Joi.string().description( |
316 | | - `'optimizedsharpimage' by default` |
317 | | - ), |
318 | | - generateOptimizedGitHubUserAvatarUrl: Joi.boolean().description( |
319 | | - `Wether or not should generate the optimized version of the GitHub user [avatarUrl] field. Set to [false] if you do not want to. Defaults to [true].` |
320 | | - ), |
321 | | - generateOptimizedGitHubRepositoryOpenGraphImageUrl: |
322 | | - Joi.boolean().description( |
323 | | - `Wether or not should generate the optimized version of the GitHub repository [openGraphImageUrl] field. Set to [false] if you do not want to. Defaults to [true].` |
324 | | - ), |
325 | | - plugins: Joi.subPlugins().description( |
326 | | - `A list of subplugins. See also: https://github.com/libsrcdev/gatsby-source-github-graphql for examples` |
327 | | - ), |
328 | | - }); |
329 | | -}; |
| 9 | +exports.pluginOptionsSchema = require(`./plugin-options-schema`); |
0 commit comments