transformFields = ({
  fields,
  parentType,
  parentInterfacesImplementingTypes,
  peek = false,
}) => {
  if (!fields || !fields.length) {
    return null
  }

  const gatsbyNodeTypes = getGatsbyNodeTypeNames()

  const { fieldAliases, fieldBlacklist } = getStore().getState().remoteSchema

  const parentTypeSettings = getTypeSettingsByType(parentType)

  const parentInterfacesImplementingTypeSettings =
    parentInterfacesImplementingTypes
      ? parentInterfacesImplementingTypes.map(type =>
          getTypeSettingsByType(type)
        )
      : null

  const transformedFields = fields.reduce((fieldsObject, field) => {
    // if there's no field name this field is unusable
    if (field.name === ``) {
      return fieldsObject
    }

    const thisTypeSettings = getTypeSettingsByType(field.type)

    const fieldName = getAliasedFieldName({ fieldAliases, field })

    if (
      fieldIsExcluded({
        field,
        fieldName,
        thisTypeSettings,
        fieldBlacklist,
        parentTypeSettings,
        parentInterfacesImplementingTypeSettings,
      })
    ) {
      return fieldsObject
    }

    const { typeMap } = getStore().getState().remoteSchema

    const type = typeMap.get(findNamedTypeName(field.type))

    const includedChildFields = type?.fields?.filter(field => {
      const childFieldTypeSettings = getTypeSettingsByType(field.type)
      const fieldName = getAliasedFieldName({ fieldAliases, field })
      return !fieldIsExcluded({
        field,
        fieldName,
        thisTypeSettings: childFieldTypeSettings,
        fieldBlacklist,
        parentTypeSettings: thisTypeSettings,
        parentInterfacesImplementingTypeSettings,
      })
    })

    // if the child fields of this field are all excluded,
    // we shouldn't add this field
    // @todo move this to a central location.
    // if a type is missing all it's child fields due to exclusion
    // it should be globally excluded automatically.
    if (Array.isArray(includedChildFields) && !includedChildFields.length) {
      return fieldsObject
    }

    field = handleCustomScalars(field)

    const { transform, description } =
      peek === false
        ? getFieldTransformers().find(({ test }) => test(field)) || {}
        : {}

    if (transform && typeof transform === `function` && peek === false) {
      const transformerApi = {
        field,
        fieldsObject,
        fieldName,
        gatsbyNodeTypes,
        description,
        pluginOptions: getPluginOptions(),
      }

      let transformedField = transform(transformerApi)

      // add default resolver
      if (typeof transformedField === `string`) {
        // we need to add a custom resolver to override the default resolver
        // and check for aliased fields
        // fields are aliased automatically if they have conflicting types
        // with other fields of the same name when placed in side-by-side
        // inlineFragments on the same union or interface type.
        transformedField = {
          type: transformedField,
          resolve: buildDefaultResolver(transformerApi),
          description: field.description,
        }
      } else {
        transformedField.description = field.description
      }

      fieldsObject[fieldName] = transformedField
    } else if (peek) {
      fieldsObject[fieldName] = true
    }

    return fieldsObject
  }, {})

  if (!Object.keys(transformedFields).length) {
    return null
  }

  return transformedFields
}
Transforms fields from the WPGQL schema to work in the Gatsby schema
with proper node linking and type namespacing
also filters out unusable fields and types