1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, '__esModule', {
|
4 | value: true,
|
5 | });
|
6 | exports.DangerousChangeType = exports.BreakingChangeType = void 0;
|
7 | exports.findBreakingChanges = findBreakingChanges;
|
8 | exports.findDangerousChanges = findDangerousChanges;
|
9 |
|
10 | var _inspect = require('../jsutils/inspect.js');
|
11 |
|
12 | var _invariant = require('../jsutils/invariant.js');
|
13 |
|
14 | var _keyMap = require('../jsutils/keyMap.js');
|
15 |
|
16 | var _printer = require('../language/printer.js');
|
17 |
|
18 | var _definition = require('../type/definition.js');
|
19 |
|
20 | var _scalars = require('../type/scalars.js');
|
21 |
|
22 | var _astFromValue = require('./astFromValue.js');
|
23 |
|
24 | var _sortValueNode = require('./sortValueNode.js');
|
25 |
|
26 | var BreakingChangeType;
|
27 | exports.BreakingChangeType = BreakingChangeType;
|
28 |
|
29 | (function (BreakingChangeType) {
|
30 | BreakingChangeType['TYPE_REMOVED'] = 'TYPE_REMOVED';
|
31 | BreakingChangeType['TYPE_CHANGED_KIND'] = 'TYPE_CHANGED_KIND';
|
32 | BreakingChangeType['TYPE_REMOVED_FROM_UNION'] = 'TYPE_REMOVED_FROM_UNION';
|
33 | BreakingChangeType['VALUE_REMOVED_FROM_ENUM'] = 'VALUE_REMOVED_FROM_ENUM';
|
34 | BreakingChangeType['REQUIRED_INPUT_FIELD_ADDED'] =
|
35 | 'REQUIRED_INPUT_FIELD_ADDED';
|
36 | BreakingChangeType['IMPLEMENTED_INTERFACE_REMOVED'] =
|
37 | 'IMPLEMENTED_INTERFACE_REMOVED';
|
38 | BreakingChangeType['FIELD_REMOVED'] = 'FIELD_REMOVED';
|
39 | BreakingChangeType['FIELD_CHANGED_KIND'] = 'FIELD_CHANGED_KIND';
|
40 | BreakingChangeType['REQUIRED_ARG_ADDED'] = 'REQUIRED_ARG_ADDED';
|
41 | BreakingChangeType['ARG_REMOVED'] = 'ARG_REMOVED';
|
42 | BreakingChangeType['ARG_CHANGED_KIND'] = 'ARG_CHANGED_KIND';
|
43 | BreakingChangeType['DIRECTIVE_REMOVED'] = 'DIRECTIVE_REMOVED';
|
44 | BreakingChangeType['DIRECTIVE_ARG_REMOVED'] = 'DIRECTIVE_ARG_REMOVED';
|
45 | BreakingChangeType['REQUIRED_DIRECTIVE_ARG_ADDED'] =
|
46 | 'REQUIRED_DIRECTIVE_ARG_ADDED';
|
47 | BreakingChangeType['DIRECTIVE_REPEATABLE_REMOVED'] =
|
48 | 'DIRECTIVE_REPEATABLE_REMOVED';
|
49 | BreakingChangeType['DIRECTIVE_LOCATION_REMOVED'] =
|
50 | 'DIRECTIVE_LOCATION_REMOVED';
|
51 | })(
|
52 | BreakingChangeType || (exports.BreakingChangeType = BreakingChangeType = {}),
|
53 | );
|
54 |
|
55 | var DangerousChangeType;
|
56 | exports.DangerousChangeType = DangerousChangeType;
|
57 |
|
58 | (function (DangerousChangeType) {
|
59 | DangerousChangeType['VALUE_ADDED_TO_ENUM'] = 'VALUE_ADDED_TO_ENUM';
|
60 | DangerousChangeType['TYPE_ADDED_TO_UNION'] = 'TYPE_ADDED_TO_UNION';
|
61 | DangerousChangeType['OPTIONAL_INPUT_FIELD_ADDED'] =
|
62 | 'OPTIONAL_INPUT_FIELD_ADDED';
|
63 | DangerousChangeType['OPTIONAL_ARG_ADDED'] = 'OPTIONAL_ARG_ADDED';
|
64 | DangerousChangeType['IMPLEMENTED_INTERFACE_ADDED'] =
|
65 | 'IMPLEMENTED_INTERFACE_ADDED';
|
66 | DangerousChangeType['ARG_DEFAULT_VALUE_CHANGE'] = 'ARG_DEFAULT_VALUE_CHANGE';
|
67 | })(
|
68 | DangerousChangeType ||
|
69 | (exports.DangerousChangeType = DangerousChangeType = {}),
|
70 | );
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | function findBreakingChanges(oldSchema, newSchema) {
|
77 |
|
78 | return findSchemaChanges(oldSchema, newSchema).filter(
|
79 | (change) => change.type in BreakingChangeType,
|
80 | );
|
81 | }
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 | function findDangerousChanges(oldSchema, newSchema) {
|
88 |
|
89 | return findSchemaChanges(oldSchema, newSchema).filter(
|
90 | (change) => change.type in DangerousChangeType,
|
91 | );
|
92 | }
|
93 |
|
94 | function findSchemaChanges(oldSchema, newSchema) {
|
95 | return [
|
96 | ...findTypeChanges(oldSchema, newSchema),
|
97 | ...findDirectiveChanges(oldSchema, newSchema),
|
98 | ];
|
99 | }
|
100 |
|
101 | function findDirectiveChanges(oldSchema, newSchema) {
|
102 | const schemaChanges = [];
|
103 | const directivesDiff = diff(
|
104 | oldSchema.getDirectives(),
|
105 | newSchema.getDirectives(),
|
106 | );
|
107 |
|
108 | for (const oldDirective of directivesDiff.removed) {
|
109 | schemaChanges.push({
|
110 | type: BreakingChangeType.DIRECTIVE_REMOVED,
|
111 | description: `${oldDirective.name} was removed.`,
|
112 | });
|
113 | }
|
114 |
|
115 | for (const [oldDirective, newDirective] of directivesDiff.persisted) {
|
116 | const argsDiff = diff(oldDirective.args, newDirective.args);
|
117 |
|
118 | for (const newArg of argsDiff.added) {
|
119 | if ((0, _definition.isRequiredArgument)(newArg)) {
|
120 | schemaChanges.push({
|
121 | type: BreakingChangeType.REQUIRED_DIRECTIVE_ARG_ADDED,
|
122 | description: `A required arg ${newArg.name} on directive ${oldDirective.name} was added.`,
|
123 | });
|
124 | }
|
125 | }
|
126 |
|
127 | for (const oldArg of argsDiff.removed) {
|
128 | schemaChanges.push({
|
129 | type: BreakingChangeType.DIRECTIVE_ARG_REMOVED,
|
130 | description: `${oldArg.name} was removed from ${oldDirective.name}.`,
|
131 | });
|
132 | }
|
133 |
|
134 | if (oldDirective.isRepeatable && !newDirective.isRepeatable) {
|
135 | schemaChanges.push({
|
136 | type: BreakingChangeType.DIRECTIVE_REPEATABLE_REMOVED,
|
137 | description: `Repeatable flag was removed from ${oldDirective.name}.`,
|
138 | });
|
139 | }
|
140 |
|
141 | for (const location of oldDirective.locations) {
|
142 | if (!newDirective.locations.includes(location)) {
|
143 | schemaChanges.push({
|
144 | type: BreakingChangeType.DIRECTIVE_LOCATION_REMOVED,
|
145 | description: `${location} was removed from ${oldDirective.name}.`,
|
146 | });
|
147 | }
|
148 | }
|
149 | }
|
150 |
|
151 | return schemaChanges;
|
152 | }
|
153 |
|
154 | function findTypeChanges(oldSchema, newSchema) {
|
155 | const schemaChanges = [];
|
156 | const typesDiff = diff(
|
157 | Object.values(oldSchema.getTypeMap()),
|
158 | Object.values(newSchema.getTypeMap()),
|
159 | );
|
160 |
|
161 | for (const oldType of typesDiff.removed) {
|
162 | schemaChanges.push({
|
163 | type: BreakingChangeType.TYPE_REMOVED,
|
164 | description: (0, _scalars.isSpecifiedScalarType)(oldType)
|
165 | ? `Standard scalar ${oldType.name} was removed because it is not referenced anymore.`
|
166 | : `${oldType.name} was removed.`,
|
167 | });
|
168 | }
|
169 |
|
170 | for (const [oldType, newType] of typesDiff.persisted) {
|
171 | if (
|
172 | (0, _definition.isEnumType)(oldType) &&
|
173 | (0, _definition.isEnumType)(newType)
|
174 | ) {
|
175 | schemaChanges.push(...findEnumTypeChanges(oldType, newType));
|
176 | } else if (
|
177 | (0, _definition.isUnionType)(oldType) &&
|
178 | (0, _definition.isUnionType)(newType)
|
179 | ) {
|
180 | schemaChanges.push(...findUnionTypeChanges(oldType, newType));
|
181 | } else if (
|
182 | (0, _definition.isInputObjectType)(oldType) &&
|
183 | (0, _definition.isInputObjectType)(newType)
|
184 | ) {
|
185 | schemaChanges.push(...findInputObjectTypeChanges(oldType, newType));
|
186 | } else if (
|
187 | (0, _definition.isObjectType)(oldType) &&
|
188 | (0, _definition.isObjectType)(newType)
|
189 | ) {
|
190 | schemaChanges.push(
|
191 | ...findFieldChanges(oldType, newType),
|
192 | ...findImplementedInterfacesChanges(oldType, newType),
|
193 | );
|
194 | } else if (
|
195 | (0, _definition.isInterfaceType)(oldType) &&
|
196 | (0, _definition.isInterfaceType)(newType)
|
197 | ) {
|
198 | schemaChanges.push(
|
199 | ...findFieldChanges(oldType, newType),
|
200 | ...findImplementedInterfacesChanges(oldType, newType),
|
201 | );
|
202 | } else if (oldType.constructor !== newType.constructor) {
|
203 | schemaChanges.push({
|
204 | type: BreakingChangeType.TYPE_CHANGED_KIND,
|
205 | description:
|
206 | `${oldType.name} changed from ` +
|
207 | `${typeKindName(oldType)} to ${typeKindName(newType)}.`,
|
208 | });
|
209 | }
|
210 | }
|
211 |
|
212 | return schemaChanges;
|
213 | }
|
214 |
|
215 | function findInputObjectTypeChanges(oldType, newType) {
|
216 | const schemaChanges = [];
|
217 | const fieldsDiff = diff(
|
218 | Object.values(oldType.getFields()),
|
219 | Object.values(newType.getFields()),
|
220 | );
|
221 |
|
222 | for (const newField of fieldsDiff.added) {
|
223 | if ((0, _definition.isRequiredInputField)(newField)) {
|
224 | schemaChanges.push({
|
225 | type: BreakingChangeType.REQUIRED_INPUT_FIELD_ADDED,
|
226 | description: `A required field ${newField.name} on input type ${oldType.name} was added.`,
|
227 | });
|
228 | } else {
|
229 | schemaChanges.push({
|
230 | type: DangerousChangeType.OPTIONAL_INPUT_FIELD_ADDED,
|
231 | description: `An optional field ${newField.name} on input type ${oldType.name} was added.`,
|
232 | });
|
233 | }
|
234 | }
|
235 |
|
236 | for (const oldField of fieldsDiff.removed) {
|
237 | schemaChanges.push({
|
238 | type: BreakingChangeType.FIELD_REMOVED,
|
239 | description: `${oldType.name}.${oldField.name} was removed.`,
|
240 | });
|
241 | }
|
242 |
|
243 | for (const [oldField, newField] of fieldsDiff.persisted) {
|
244 | const isSafe = isChangeSafeForInputObjectFieldOrFieldArg(
|
245 | oldField.type,
|
246 | newField.type,
|
247 | );
|
248 |
|
249 | if (!isSafe) {
|
250 | schemaChanges.push({
|
251 | type: BreakingChangeType.FIELD_CHANGED_KIND,
|
252 | description:
|
253 | `${oldType.name}.${oldField.name} changed type from ` +
|
254 | `${String(oldField.type)} to ${String(newField.type)}.`,
|
255 | });
|
256 | }
|
257 | }
|
258 |
|
259 | return schemaChanges;
|
260 | }
|
261 |
|
262 | function findUnionTypeChanges(oldType, newType) {
|
263 | const schemaChanges = [];
|
264 | const possibleTypesDiff = diff(oldType.getTypes(), newType.getTypes());
|
265 |
|
266 | for (const newPossibleType of possibleTypesDiff.added) {
|
267 | schemaChanges.push({
|
268 | type: DangerousChangeType.TYPE_ADDED_TO_UNION,
|
269 | description: `${newPossibleType.name} was added to union type ${oldType.name}.`,
|
270 | });
|
271 | }
|
272 |
|
273 | for (const oldPossibleType of possibleTypesDiff.removed) {
|
274 | schemaChanges.push({
|
275 | type: BreakingChangeType.TYPE_REMOVED_FROM_UNION,
|
276 | description: `${oldPossibleType.name} was removed from union type ${oldType.name}.`,
|
277 | });
|
278 | }
|
279 |
|
280 | return schemaChanges;
|
281 | }
|
282 |
|
283 | function findEnumTypeChanges(oldType, newType) {
|
284 | const schemaChanges = [];
|
285 | const valuesDiff = diff(oldType.getValues(), newType.getValues());
|
286 |
|
287 | for (const newValue of valuesDiff.added) {
|
288 | schemaChanges.push({
|
289 | type: DangerousChangeType.VALUE_ADDED_TO_ENUM,
|
290 | description: `${newValue.name} was added to enum type ${oldType.name}.`,
|
291 | });
|
292 | }
|
293 |
|
294 | for (const oldValue of valuesDiff.removed) {
|
295 | schemaChanges.push({
|
296 | type: BreakingChangeType.VALUE_REMOVED_FROM_ENUM,
|
297 | description: `${oldValue.name} was removed from enum type ${oldType.name}.`,
|
298 | });
|
299 | }
|
300 |
|
301 | return schemaChanges;
|
302 | }
|
303 |
|
304 | function findImplementedInterfacesChanges(oldType, newType) {
|
305 | const schemaChanges = [];
|
306 | const interfacesDiff = diff(oldType.getInterfaces(), newType.getInterfaces());
|
307 |
|
308 | for (const newInterface of interfacesDiff.added) {
|
309 | schemaChanges.push({
|
310 | type: DangerousChangeType.IMPLEMENTED_INTERFACE_ADDED,
|
311 | description: `${newInterface.name} added to interfaces implemented by ${oldType.name}.`,
|
312 | });
|
313 | }
|
314 |
|
315 | for (const oldInterface of interfacesDiff.removed) {
|
316 | schemaChanges.push({
|
317 | type: BreakingChangeType.IMPLEMENTED_INTERFACE_REMOVED,
|
318 | description: `${oldType.name} no longer implements interface ${oldInterface.name}.`,
|
319 | });
|
320 | }
|
321 |
|
322 | return schemaChanges;
|
323 | }
|
324 |
|
325 | function findFieldChanges(oldType, newType) {
|
326 | const schemaChanges = [];
|
327 | const fieldsDiff = diff(
|
328 | Object.values(oldType.getFields()),
|
329 | Object.values(newType.getFields()),
|
330 | );
|
331 |
|
332 | for (const oldField of fieldsDiff.removed) {
|
333 | schemaChanges.push({
|
334 | type: BreakingChangeType.FIELD_REMOVED,
|
335 | description: `${oldType.name}.${oldField.name} was removed.`,
|
336 | });
|
337 | }
|
338 |
|
339 | for (const [oldField, newField] of fieldsDiff.persisted) {
|
340 | schemaChanges.push(...findArgChanges(oldType, oldField, newField));
|
341 | const isSafe = isChangeSafeForObjectOrInterfaceField(
|
342 | oldField.type,
|
343 | newField.type,
|
344 | );
|
345 |
|
346 | if (!isSafe) {
|
347 | schemaChanges.push({
|
348 | type: BreakingChangeType.FIELD_CHANGED_KIND,
|
349 | description:
|
350 | `${oldType.name}.${oldField.name} changed type from ` +
|
351 | `${String(oldField.type)} to ${String(newField.type)}.`,
|
352 | });
|
353 | }
|
354 | }
|
355 |
|
356 | return schemaChanges;
|
357 | }
|
358 |
|
359 | function findArgChanges(oldType, oldField, newField) {
|
360 | const schemaChanges = [];
|
361 | const argsDiff = diff(oldField.args, newField.args);
|
362 |
|
363 | for (const oldArg of argsDiff.removed) {
|
364 | schemaChanges.push({
|
365 | type: BreakingChangeType.ARG_REMOVED,
|
366 | description: `${oldType.name}.${oldField.name} arg ${oldArg.name} was removed.`,
|
367 | });
|
368 | }
|
369 |
|
370 | for (const [oldArg, newArg] of argsDiff.persisted) {
|
371 | const isSafe = isChangeSafeForInputObjectFieldOrFieldArg(
|
372 | oldArg.type,
|
373 | newArg.type,
|
374 | );
|
375 |
|
376 | if (!isSafe) {
|
377 | schemaChanges.push({
|
378 | type: BreakingChangeType.ARG_CHANGED_KIND,
|
379 | description:
|
380 | `${oldType.name}.${oldField.name} arg ${oldArg.name} has changed type from ` +
|
381 | `${String(oldArg.type)} to ${String(newArg.type)}.`,
|
382 | });
|
383 | } else if (oldArg.defaultValue !== undefined) {
|
384 | if (newArg.defaultValue === undefined) {
|
385 | schemaChanges.push({
|
386 | type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
|
387 | description: `${oldType.name}.${oldField.name} arg ${oldArg.name} defaultValue was removed.`,
|
388 | });
|
389 | } else {
|
390 |
|
391 |
|
392 |
|
393 | const oldValueStr = stringifyValue(oldArg.defaultValue, oldArg.type);
|
394 | const newValueStr = stringifyValue(newArg.defaultValue, newArg.type);
|
395 |
|
396 | if (oldValueStr !== newValueStr) {
|
397 | schemaChanges.push({
|
398 | type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
|
399 | description: `${oldType.name}.${oldField.name} arg ${oldArg.name} has changed defaultValue from ${oldValueStr} to ${newValueStr}.`,
|
400 | });
|
401 | }
|
402 | }
|
403 | }
|
404 | }
|
405 |
|
406 | for (const newArg of argsDiff.added) {
|
407 | if ((0, _definition.isRequiredArgument)(newArg)) {
|
408 | schemaChanges.push({
|
409 | type: BreakingChangeType.REQUIRED_ARG_ADDED,
|
410 | description: `A required arg ${newArg.name} on ${oldType.name}.${oldField.name} was added.`,
|
411 | });
|
412 | } else {
|
413 | schemaChanges.push({
|
414 | type: DangerousChangeType.OPTIONAL_ARG_ADDED,
|
415 | description: `An optional arg ${newArg.name} on ${oldType.name}.${oldField.name} was added.`,
|
416 | });
|
417 | }
|
418 | }
|
419 |
|
420 | return schemaChanges;
|
421 | }
|
422 |
|
423 | function isChangeSafeForObjectOrInterfaceField(oldType, newType) {
|
424 | if ((0, _definition.isListType)(oldType)) {
|
425 | return (
|
426 |
|
427 | ((0, _definition.isListType)(newType) &&
|
428 | isChangeSafeForObjectOrInterfaceField(
|
429 | oldType.ofType,
|
430 | newType.ofType,
|
431 | )) ||
|
432 | ((0, _definition.isNonNullType)(newType) &&
|
433 | isChangeSafeForObjectOrInterfaceField(oldType, newType.ofType))
|
434 | );
|
435 | }
|
436 |
|
437 | if ((0, _definition.isNonNullType)(oldType)) {
|
438 |
|
439 | return (
|
440 | (0, _definition.isNonNullType)(newType) &&
|
441 | isChangeSafeForObjectOrInterfaceField(oldType.ofType, newType.ofType)
|
442 | );
|
443 | }
|
444 |
|
445 | return (
|
446 |
|
447 | ((0, _definition.isNamedType)(newType) && oldType.name === newType.name) ||
|
448 | ((0, _definition.isNonNullType)(newType) &&
|
449 | isChangeSafeForObjectOrInterfaceField(oldType, newType.ofType))
|
450 | );
|
451 | }
|
452 |
|
453 | function isChangeSafeForInputObjectFieldOrFieldArg(oldType, newType) {
|
454 | if ((0, _definition.isListType)(oldType)) {
|
455 |
|
456 | return (
|
457 | (0, _definition.isListType)(newType) &&
|
458 | isChangeSafeForInputObjectFieldOrFieldArg(oldType.ofType, newType.ofType)
|
459 | );
|
460 | }
|
461 |
|
462 | if ((0, _definition.isNonNullType)(oldType)) {
|
463 | return (
|
464 |
|
465 |
|
466 | ((0, _definition.isNonNullType)(newType) &&
|
467 | isChangeSafeForInputObjectFieldOrFieldArg(
|
468 | oldType.ofType,
|
469 | newType.ofType,
|
470 | )) ||
|
471 | (!(0, _definition.isNonNullType)(newType) &&
|
472 | isChangeSafeForInputObjectFieldOrFieldArg(oldType.ofType, newType))
|
473 | );
|
474 | }
|
475 |
|
476 | return (0, _definition.isNamedType)(newType) && oldType.name === newType.name;
|
477 | }
|
478 |
|
479 | function typeKindName(type) {
|
480 | if ((0, _definition.isScalarType)(type)) {
|
481 | return 'a Scalar type';
|
482 | }
|
483 |
|
484 | if ((0, _definition.isObjectType)(type)) {
|
485 | return 'an Object type';
|
486 | }
|
487 |
|
488 | if ((0, _definition.isInterfaceType)(type)) {
|
489 | return 'an Interface type';
|
490 | }
|
491 |
|
492 | if ((0, _definition.isUnionType)(type)) {
|
493 | return 'a Union type';
|
494 | }
|
495 |
|
496 | if ((0, _definition.isEnumType)(type)) {
|
497 | return 'an Enum type';
|
498 | }
|
499 |
|
500 | if ((0, _definition.isInputObjectType)(type)) {
|
501 | return 'an Input type';
|
502 | }
|
503 |
|
504 |
|
505 |
|
506 | false ||
|
507 | (0, _invariant.invariant)(
|
508 | false,
|
509 | 'Unexpected type: ' + (0, _inspect.inspect)(type),
|
510 | );
|
511 | }
|
512 |
|
513 | function stringifyValue(value, type) {
|
514 | const ast = (0, _astFromValue.astFromValue)(value, type);
|
515 | ast != null || (0, _invariant.invariant)(false);
|
516 | return (0, _printer.print)((0, _sortValueNode.sortValueNode)(ast));
|
517 | }
|
518 |
|
519 | function diff(oldArray, newArray) {
|
520 | const added = [];
|
521 | const removed = [];
|
522 | const persisted = [];
|
523 | const oldMap = (0, _keyMap.keyMap)(oldArray, ({ name }) => name);
|
524 | const newMap = (0, _keyMap.keyMap)(newArray, ({ name }) => name);
|
525 |
|
526 | for (const oldItem of oldArray) {
|
527 | const newItem = newMap[oldItem.name];
|
528 |
|
529 | if (newItem === undefined) {
|
530 | removed.push(oldItem);
|
531 | } else {
|
532 | persisted.push([oldItem, newItem]);
|
533 | }
|
534 | }
|
535 |
|
536 | for (const newItem of newArray) {
|
537 | if (oldMap[newItem.name] === undefined) {
|
538 | added.push(newItem);
|
539 | }
|
540 | }
|
541 |
|
542 | return {
|
543 | added,
|
544 | persisted,
|
545 | removed,
|
546 | };
|
547 | }
|