Skip to content

Commit 9f26b67

Browse files
authored
fix(gatsby-source-wordpress): prevent inconsistent schema customization (#37749)
* panic or warn when schema customization is inconsistent * ensure types are always properly excluded * add blocklist of typename parts which cause a type to be excluded if it's name includes a part
1 parent 438ddda commit 9f26b67

File tree

11 files changed

+290
-117
lines changed

11 files changed

+290
-117
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ jobs:
220220

221221
integration_tests_gatsby_source_wordpress:
222222
machine:
223-
image: "ubuntu-2204:2022.10.1"
223+
image: "ubuntu-2204:2023.02.1"
224224
steps:
225225
- run:
226226
command: |

integration-tests/gatsby-source-wordpress/__tests__/__snapshots__/index.js.snap

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,12 +1015,6 @@ Array [
10151015
"fields": null,
10161016
"name": "WpAcfLinkSortInput",
10171017
},
1018-
Object {
1019-
"fields": Array [
1020-
"node",
1021-
],
1022-
"name": "WpActionMonitorActionConnectionEdgeType",
1023-
},
10241018
Object {
10251019
"fields": Array [
10261020
"default",
@@ -5716,18 +5710,6 @@ Array [
57165710
],
57175711
"name": "WpEdgeType",
57185712
},
5719-
Object {
5720-
"fields": Array [
5721-
"node",
5722-
],
5723-
"name": "WpEnqueuedScriptConnectionEdgeType",
5724-
},
5725-
Object {
5726-
"fields": Array [
5727-
"node",
5728-
],
5729-
"name": "WpEnqueuedStylesheetConnectionEdgeType",
5730-
},
57315713
Object {
57325714
"fields": null,
57335715
"name": "WpFieldSelector",
@@ -7549,12 +7531,6 @@ Array [
75497531
],
75507532
"name": "WpPage_Acfpagefields_repeaterField_RepeaterFlex_RepeaterFlexTitleLayout",
75517533
},
7552-
Object {
7553-
"fields": Array [
7554-
"node",
7555-
],
7556-
"name": "WpPluginConnectionEdgeType",
7557-
},
75587534
Object {
75597535
"fields": Array [
75607536
"author",
@@ -9113,12 +9089,6 @@ Array [
91139089
"fields": null,
91149090
"name": "WpTermNodeSortInput",
91159091
},
9116-
Object {
9117-
"fields": Array [
9118-
"node",
9119-
],
9120-
"name": "WpThemeConnectionEdgeType",
9121-
},
91229092
Object {
91239093
"fields": Array [
91249094
"author",

packages/gatsby-source-wordpress/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"gatsby-source-filesystem": "^5.8.0-next.0",
3434
"glob": "^7.2.3",
3535
"got": "^11.8.6",
36+
"json-diff": "^1.0.3",
3637
"lodash": "^4.17.21",
3738
"node-fetch": "^2.6.8",
3839
"p-queue": "^6.6.2",
@@ -60,8 +61,7 @@
6061
"identity-obj-proxy": "^3.0.0",
6162
"react-test-renderer": "^16.14.0",
6263
"rimraf": "^3.0.2",
63-
"tree-kill": "^1.2.2",
64-
"wait-on": "^4.0.2"
64+
"tree-kill": "^1.2.2"
6565
},
6666
"homepage": "https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-source-wordpress#readme",
6767
"keywords": [

packages/gatsby-source-wordpress/src/models/gatsby-api.ts

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -307,36 +307,6 @@ const defaultPluginOptions: IPluginOptions = {
307307
*/
308308
beforeChangeNode: menuBeforeChangeNode,
309309
},
310-
// the next two types can't be sourced in Gatsby properly yet
311-
// @todo instead of excluding these manually, auto exclude them
312-
// based on how they behave (no single node query available)
313-
EnqueuedScript: {
314-
exclude: true,
315-
},
316-
EnqueuedStylesheet: {
317-
exclude: true,
318-
},
319-
EnqueuedAsset: {
320-
exclude: true,
321-
},
322-
ContentNodeToEnqueuedScriptConnection: {
323-
exclude: true,
324-
},
325-
ContentNodeToEnqueuedStylesheetConnection: {
326-
exclude: true,
327-
},
328-
TermNodeToEnqueuedScriptConnection: {
329-
exclude: true,
330-
},
331-
TermNodeToEnqueuedStylesheetConnection: {
332-
exclude: true,
333-
},
334-
UserToEnqueuedScriptConnection: {
335-
exclude: true,
336-
},
337-
UserToEnqueuedStylesheetConnection: {
338-
exclude: true,
339-
},
340310
},
341311
}
342312

packages/gatsby-source-wordpress/src/steps/create-schema-customization/helpers.js

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import store from "~/store"
22
import { typeDefinitionFilters } from "./type-filters"
33
import { getPluginOptions } from "~/utils/get-gatsby-api"
44
import { cloneDeep, merge } from "lodash"
5+
import { diffString } from "json-diff"
6+
import { formatLogMessage } from "../../utils/format-log-message"
7+
import { CODES } from "../../utils/report"
58

69
export const buildInterfacesListForType = type => {
710
let shouldAddNodeType = false
@@ -298,3 +301,151 @@ export const introspectionFieldTypeToSDL = fieldType => {
298301

299302
return openingTagsList.join(``) + closingTagsList.reverse().join(``)
300303
}
304+
305+
/**
306+
* This is an expensive fn but it doesn't matter because it's only to show a debugging warning message when something is wrong.
307+
*/
308+
function mergeDuplicateTypesAndReturnDedupedList(typeDefs) {
309+
const clonedDefs = cloneDeep(typeDefs)
310+
311+
const newList = []
312+
313+
for (const def of clonedDefs) {
314+
if (!def) {
315+
continue
316+
}
317+
318+
const duplicateDefs = clonedDefs.filter(
319+
d => d.config.name === def.config.name
320+
)
321+
322+
const newDef = {}
323+
324+
for (const dDef of duplicateDefs) {
325+
merge(newDef, dDef)
326+
}
327+
328+
newList.push(newDef)
329+
}
330+
331+
return newList
332+
}
333+
334+
/**
335+
* Diffs the built types between this build and the last one with the same remote schema hash.
336+
* This is to catch and add helpful error messages for when an inconsistent schema between builds is inadvertently created due to some bug
337+
*/
338+
export async function diffBuiltTypeDefs(typeDefs) {
339+
if (
340+
process.env.NODE_ENV !== `development` &&
341+
process.env.WP_DIFF_SCHEMA_CUSTOMIZATION !== `true`
342+
) {
343+
return
344+
}
345+
346+
const state = store.getState()
347+
348+
const {
349+
gatsbyApi: {
350+
helpers: { cache, reporter },
351+
},
352+
remoteSchema,
353+
} = state
354+
355+
const previousTypeDefinitions = await cache.get(`previousTypeDefinitions`)
356+
const typeDefString = JSON.stringify(typeDefs)
357+
const typeNames = typeDefs.map(typeDef => typeDef.config.name)
358+
359+
const remoteSchemaChanged =
360+
!previousTypeDefinitions ||
361+
previousTypeDefinitions?.schemaHash !== remoteSchema.schemaHash
362+
363+
if (remoteSchemaChanged) {
364+
await cache.set(`previousTypeDefinitions`, {
365+
schemaHash: remoteSchema.schemaHash,
366+
typeDefString,
367+
typeNames,
368+
})
369+
return
370+
}
371+
372+
// type defs are the same as last time, so don't check for missing/inconsistent types
373+
if (previousTypeDefinitions?.typeDefString === typeDefString) {
374+
return
375+
}
376+
377+
const missingTypeNames = previousTypeDefinitions.typeNames.filter(
378+
name => !typeNames.includes(name)
379+
)
380+
381+
const previousTypeDefJson = mergeDuplicateTypesAndReturnDedupedList(
382+
JSON.parse(previousTypeDefinitions.typeDefString)
383+
)
384+
385+
const newParsedTypeDefs = mergeDuplicateTypesAndReturnDedupedList(
386+
JSON.parse(typeDefString)
387+
)
388+
389+
const changedTypeDefs = newParsedTypeDefs
390+
.map(typeDef => {
391+
const previousTypeDef = previousTypeDefJson.find(
392+
previousTypeDef => previousTypeDef.config.name === typeDef.config.name
393+
)
394+
395+
const isDifferent = diffString(previousTypeDef, typeDef)
396+
397+
if (isDifferent) {
398+
return `Typename ${typeDef.config.name} diff:\n${diffString(
399+
previousTypeDef,
400+
typeDef,
401+
{
402+
// diff again to also show unchanged lines
403+
full: true,
404+
}
405+
)}`
406+
}
407+
408+
return null
409+
})
410+
.filter(Boolean)
411+
412+
let errorMessage = formatLogMessage(
413+
`The remote WPGraphQL schema hasn't changed but local generated type definitions have. This is a bug, please open an issue on Github${
414+
missingTypeNames.length || changedTypeDefs.length
415+
? ` and include the following text.`
416+
: ``
417+
}.${
418+
missingTypeNames.length
419+
? `\n\nMissing type names: ${missingTypeNames.join(`\n`)}\n`
420+
: ``
421+
}${
422+
changedTypeDefs.length
423+
? `\n\nChanged type defs:\n\n${changedTypeDefs.join(`\n`)}`
424+
: ``
425+
}`
426+
)
427+
428+
const maxErrorLength = 5000
429+
430+
if (errorMessage.length > maxErrorLength) {
431+
errorMessage =
432+
errorMessage.substring(0, maxErrorLength) +
433+
`\n\n...\n[Diff exceeded ${maxErrorLength} characters and was truncated]`
434+
}
435+
436+
if (process.env.WP_INCONSISTENT_SCHEMA_WARN !== `true`) {
437+
reporter.info(
438+
formatLogMessage(
439+
`Panicking due to inconsistent schema customization. Turn this into a warning by setting process.env.WP_INCONSISTENT_SCHEMA_WARN to a string of "true"`
440+
)
441+
)
442+
reporter.panic({
443+
id: CODES.InconsistentSchemaCustomization,
444+
context: {
445+
sourceMessage: errorMessage,
446+
},
447+
})
448+
} else {
449+
reporter.warn(errorMessage)
450+
}
451+
}

packages/gatsby-source-wordpress/src/steps/create-schema-customization/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import store from "~/store"
22

3-
import { buildInterfacesListForType, fieldOfTypeWasFetched } from "./helpers"
3+
import { diffBuiltTypeDefs, fieldOfTypeWasFetched } from "./helpers"
44

55
import buildType from "./build-types"
66
import { getGatsbyNodeTypeNames } from "../source-nodes/fetch-nodes/fetch-nodes"
@@ -105,6 +105,7 @@ const customizeSchema = async ({ actions, schema, store: gatsbyStore }) => {
105105
)
106106
)
107107

108+
diffBuiltTypeDefs(typeDefs)
108109
actions.createTypes(typeDefs)
109110
}
110111

packages/gatsby-source-wordpress/src/steps/ingest-remote-schema/build-queries-from-introspection/recursively-transform-fields.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
fieldIsExcludedOnAll,
1010
} from "~/steps/ingest-remote-schema/is-excluded"
1111
import { returnAliasedFieldName } from "~/steps/create-schema-customization/transform-fields"
12+
import { typeIsExcluded } from "../is-excluded"
1213

1314
export const transformInlineFragments = ({
1415
possibleTypes,
@@ -63,6 +64,15 @@ export const transformInlineFragments = ({
6364
return false
6465
}
6566

67+
if (
68+
typeIsExcluded({
69+
pluginOptions,
70+
typeName: findNamedTypeName(type),
71+
})
72+
) {
73+
return false
74+
}
75+
6676
possibleType.type = { ...type }
6777

6878
// save this type so we can use it in schema customization
@@ -531,6 +541,10 @@ const transformFields = ({
531541
!fieldIsExcludedOnAll({
532542
pluginOptions,
533543
field,
544+
}) &&
545+
!typeIsExcluded({
546+
pluginOptions,
547+
typeName: findNamedTypeName(field.type),
534548
})
535549
)
536550
.map(field => {

0 commit comments

Comments
 (0)