Register
Login
Resources
Docs Blog Datasets Glossary Case Studies Tutorials & Webinars
Product
Data Engine LLMs Platform Enterprise
Pricing Explore
Connect to our Discord channel

build-changelog.js 9.4 KB

You have to be logged in to leave a comment. Sign In
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
  1. const { diff, ChangeType } = require('@graphql-inspector/core')
  2. const { loadSchema } = require('@graphql-tools/load')
  3. const fs = require('fs')
  4. /**
  5. * Tag `changelogEntry` with `date: YYYY-mm-dd`, then prepend it to the JSON
  6. * structure written to `targetPath`. (`changelogEntry` and that file are modified in place.)
  7. * @param {object} changelogEntry
  8. * @param {string} targetPath
  9. * @return {void}
  10. */
  11. function prependDatedEntry (changelogEntry, targetPath) {
  12. // Build a `yyyy-mm-dd`-formatted date string
  13. // and tag the changelog entry with it
  14. const todayString = new Date().toISOString().slice(0, 10)
  15. changelogEntry.date = todayString
  16. const previousChangelogString = fs.readFileSync(targetPath)
  17. const previousChangelog = JSON.parse(previousChangelogString)
  18. // add a new entry to the changelog data
  19. previousChangelog.unshift(changelogEntry)
  20. // rewrite the updated changelog
  21. fs.writeFileSync(targetPath, JSON.stringify(previousChangelog, null, 2))
  22. }
  23. /**
  24. * Compare `oldSchemaString` to `newSchemaString`, and if there are any
  25. * changes that warrant a changelog entry, return a changelog entry.
  26. * Based on the parsed `previews`, identify changes that are under a preview.
  27. * Otherwise, return null.
  28. * @param {string} [oldSchemaString]
  29. * @param {string} [newSchemaString]
  30. * @param {Array<object>} [previews]
  31. * @param {Array<object>} [oldUpcomingChanges]
  32. * @param {Array<object>} [newUpcomingChanges]
  33. * @return {object?}
  34. */
  35. async function createChangelogEntry (oldSchemaString, newSchemaString, previews, oldUpcomingChanges, newUpcomingChanges) {
  36. // Create schema objects out of the strings
  37. const oldSchema = await loadSchema(oldSchemaString)
  38. const newSchema = await loadSchema(newSchemaString)
  39. // Generate changes between the two schemas
  40. const changes = diff(oldSchema, newSchema)
  41. const changesToReport = []
  42. changes.forEach(function (change) {
  43. if (CHANGES_TO_REPORT.includes(change.type)) {
  44. changesToReport.push(change)
  45. } else if (CHANGES_TO_IGNORE.includes(change.type)) {
  46. // Do nothing
  47. } else {
  48. throw new Error('This change type should be added to CHANGES_TO_REPORT or CHANGES_TO_IGNORE: ' + change.type)
  49. }
  50. })
  51. const { schemaChangesToReport, previewChangesToReport } = segmentPreviewChanges(changesToReport, previews)
  52. const addedUpcomingChanges = newUpcomingChanges.filter(function (change) {
  53. // Manually check each of `newUpcomingChanges` for an equivalent entry
  54. // in `oldUpcomingChanges`.
  55. return !oldUpcomingChanges.find(function (oldChange) {
  56. return (oldChange.location === change.location &&
  57. oldChange.date === change.date &&
  58. oldChange.description === change.description
  59. )
  60. })
  61. })
  62. // If there were any changes, create a changelog entry
  63. if (schemaChangesToReport.length > 0 || previewChangesToReport.length > 0 || addedUpcomingChanges.length > 0) {
  64. const changelogEntry = {
  65. schemaChanges: [],
  66. previewChanges: [],
  67. upcomingChanges: []
  68. }
  69. const schemaChange = {
  70. title: 'The GraphQL schema includes these changes:',
  71. // Replace single quotes which wrap field/argument/type names with backticks
  72. changes: cleanMessagesFromChanges(schemaChangesToReport)
  73. }
  74. changelogEntry.schemaChanges.push(schemaChange)
  75. for (const previewTitle in previewChangesToReport) {
  76. const previewChanges = previewChangesToReport[previewTitle]
  77. const cleanTitle = cleanPreviewTitle(previewTitle)
  78. const entryTitle = 'The [' + cleanTitle + '](/graphql/overview/schema-previews#' + previewAnchor(cleanTitle) + ') includes these changes:'
  79. changelogEntry.previewChanges.push({
  80. title: entryTitle,
  81. changes: cleanMessagesFromChanges(previewChanges.changes)
  82. })
  83. }
  84. if (addedUpcomingChanges.length > 0) {
  85. changelogEntry.upcomingChanges.push({
  86. title: 'The following changes will be made to the schema:',
  87. changes: addedUpcomingChanges.map(function (change) {
  88. const location = change.location
  89. const description = change.description
  90. const date = change.date.split('T')[0]
  91. return 'On member `' + location + '`:' + description + ' **Effective ' + date + '**.'
  92. })
  93. })
  94. }
  95. return changelogEntry
  96. } else {
  97. return null
  98. }
  99. }
  100. /**
  101. * Prepare the preview title from github/github source for the docs.
  102. * @param {string} title
  103. * @return {string}
  104. */
  105. function cleanPreviewTitle (title) {
  106. if (title === 'UpdateRefsPreview') {
  107. title = 'Update refs preview'
  108. } else if (title === 'MergeInfoPreview') {
  109. title = 'Merge info preview'
  110. } else if (!title.endsWith('preview')) {
  111. title = title + ' preview'
  112. }
  113. return title
  114. }
  115. /**
  116. * Turn the given title into an HTML-ready anchor.
  117. * (ported from graphql-docs/lib/graphql_docs/update_internal_developer/change_log.rb#L281)
  118. * @param {string} [previewTitle]
  119. * @return {string}
  120. */
  121. function previewAnchor (previewTitle) {
  122. return previewTitle
  123. .toLowerCase()
  124. .replace(/ /g, '-')
  125. .replace(/[^\w-]/g, '')
  126. }
  127. /**
  128. * Turn changes from graphql-inspector into messages for the HTML changelog.
  129. * @param {Array<object>} changes
  130. * @return {Array<string>}
  131. */
  132. function cleanMessagesFromChanges (changes) {
  133. return changes.map(function (change) {
  134. // replace single quotes around graphql names with backticks,
  135. // to match previous behavior from graphql-schema-comparator
  136. return change.message.replace(/'([a-zA-Z. :!]+)'/g, '`$1`')
  137. })
  138. }
  139. /**
  140. * Split `changesToReport` into two parts,
  141. * one for changes in the main schema,
  142. * and another for changes that are under preview.
  143. * (Ported from /graphql-docs/lib/graphql_docs/update_internal_developer/change_log.rb#L230)
  144. * @param {Array<object>} changesToReport
  145. * @param {object} previews
  146. * @return {object}
  147. */
  148. function segmentPreviewChanges (changesToReport, previews) {
  149. // Build a map of `{ path => previewTitle` }
  150. // for easier lookup of change to preview
  151. const pathToPreview = {}
  152. previews.forEach(function (preview) {
  153. preview.toggled_on.forEach(function (path) {
  154. pathToPreview[path] = preview.title
  155. })
  156. })
  157. const schemaChanges = []
  158. const changesByPreview = {}
  159. changesToReport.forEach(function (change) {
  160. // For each change, see if its path _or_ one of its ancestors
  161. // is covered by a preview. If it is, mark this change as belonging to a preview
  162. const pathParts = change.path.split('.')
  163. let testPath = null
  164. let previewTitle = null
  165. let previewChanges = null
  166. while (pathParts.length > 0 && !previewTitle) {
  167. testPath = pathParts.join('.')
  168. previewTitle = pathToPreview[testPath]
  169. // If that path didn't find a match, then we'll
  170. // check the next ancestor.
  171. pathParts.pop()
  172. }
  173. if (previewTitle) {
  174. previewChanges = changesByPreview[previewTitle] || (changesByPreview[previewTitle] = {
  175. title: previewTitle,
  176. changes: []
  177. })
  178. previewChanges.changes.push(change)
  179. } else {
  180. schemaChanges.push(change)
  181. }
  182. })
  183. return { schemaChangesToReport: schemaChanges, previewChangesToReport: changesByPreview }
  184. }
  185. // We only want to report changes to schema structure.
  186. // Deprecations are covered by "upcoming changes."
  187. // By listing the changes explicitly here, we can make sure that,
  188. // if the library changes, we don't miss publishing anything that we mean to.
  189. // This was originally ported from graphql-docs/lib/graphql_docs/update_internal_developer/change_log.rb#L35-L103
  190. const CHANGES_TO_REPORT = [
  191. ChangeType.FieldArgumentDefaultChanged,
  192. ChangeType.FieldArgumentTypeChanged,
  193. ChangeType.EnumValueRemoved,
  194. ChangeType.EnumValueAdded,
  195. ChangeType.FieldRemoved,
  196. ChangeType.FieldAdded,
  197. ChangeType.FieldTypeChanged,
  198. ChangeType.FieldArgumentAdded,
  199. ChangeType.FieldArgumentRemoved,
  200. ChangeType.ObjectTypeInterfaceAdded,
  201. ChangeType.ObjectTypeInterfaceRemoved,
  202. ChangeType.InputFieldRemoved,
  203. ChangeType.InputFieldAdded,
  204. ChangeType.InputFieldDefaultValueChanged,
  205. ChangeType.InputFieldTypeChanged,
  206. ChangeType.TypeRemoved,
  207. ChangeType.TypeAdded,
  208. ChangeType.TypeKindChanged,
  209. ChangeType.UnionMemberRemoved,
  210. ChangeType.UnionMemberAdded,
  211. ChangeType.SchemaQueryTypeChanged,
  212. ChangeType.SchemaMutationTypeChanged,
  213. ChangeType.SchemaSubscriptionTypeChanged
  214. ]
  215. const CHANGES_TO_IGNORE = [
  216. ChangeType.FieldArgumentDescriptionChanged,
  217. ChangeType.DirectiveRemoved,
  218. ChangeType.DirectiveAdded,
  219. ChangeType.DirectiveDescriptionChanged,
  220. ChangeType.DirectiveLocationAdded,
  221. ChangeType.DirectiveLocationRemoved,
  222. ChangeType.DirectiveArgumentAdded,
  223. ChangeType.DirectiveArgumentRemoved,
  224. ChangeType.DirectiveArgumentDescriptionChanged,
  225. ChangeType.DirectiveArgumentDefaultValueChanged,
  226. ChangeType.DirectiveArgumentTypeChanged,
  227. ChangeType.EnumValueDescriptionChanged,
  228. ChangeType.EnumValueDeprecationReasonChanged,
  229. ChangeType.EnumValueDeprecationReasonAdded,
  230. ChangeType.EnumValueDeprecationReasonRemoved,
  231. ChangeType.FieldDescriptionChanged,
  232. ChangeType.FieldDescriptionAdded,
  233. ChangeType.FieldDescriptionRemoved,
  234. ChangeType.FieldDeprecationAdded,
  235. ChangeType.FieldDeprecationRemoved,
  236. ChangeType.FieldDeprecationReasonChanged,
  237. ChangeType.FieldDeprecationReasonAdded,
  238. ChangeType.FieldDeprecationReasonRemoved,
  239. ChangeType.InputFieldDescriptionAdded,
  240. ChangeType.InputFieldDescriptionRemoved,
  241. ChangeType.InputFieldDescriptionChanged,
  242. ChangeType.TypeDescriptionChanged,
  243. ChangeType.TypeDescriptionRemoved,
  244. ChangeType.TypeDescriptionAdded
  245. ]
  246. module.exports = { createChangelogEntry, cleanPreviewTitle, previewAnchor, prependDatedEntry }
Tip!

Press p or to see the previous file or, n or to see the next file

Comments

Loading...