createFields = ({
  pathPrefix,
  getNodeAndSavePathDependency,
  reporter,
  cache,
}) => {
  const nodeOptions = {
    pathPrefix,
    getNodeAndSavePathDependency,
    reporter,
    cache,
  }

  const fixedNode = fixedNodeType({ name: `ImageSharpFixed`, ...nodeOptions })
  const fluidNode = fluidNodeType({ name: `ImageSharpFluid`, ...nodeOptions })
  const imageNode = imageNodeType(nodeOptions)

  return {
    fixed: fixedNode,
    fluid: fluidNode,
    gatsbyImageData: imageNode,
    original: {
      type: new GraphQLObjectType({
        name: `ImageSharpOriginal`,
        fields: {
          width: { type: GraphQLFloat },
          height: { type: GraphQLFloat },
          src: { type: GraphQLString },
        },
      }),
      args: {},
      async resolve(image, fieldArgs, context) {
        const details = getNodeAndSavePathDependency(image.parent, context.path)
        const dimensions = imageSize.sync(
          toArray(fs.readFileSync(details.absolutePath))
        )
        const imageName = `${details.name}-${image.internal.contentDigest}${details.ext}`
        const publicPath = path.join(
          process.cwd(),
          `public`,
          `static`,
          imageName
        )

        if (!fs.existsSync(publicPath) && !inProgressCopy.has(publicPath)) {
          // keep track of in progress copy, we should rely on `existsSync` but
          // a race condition exists between the exists check and the copy
          inProgressCopy.add(publicPath)
          fs.copy(
            details.absolutePath,
            publicPath,
            { dereference: true },
            err => {
              // this is no longer in progress
              inProgressCopy.delete(publicPath)
              if (err) {
                reporter.panic(
                  {
                    id: prefixId(CODES.MissingResource),
                    context: {
                      sourceMessage: `error copying file from ${details.absolutePath} to ${publicPath}`,
                    },
                  },
                  err
                )
              }
            }
          )
        }

        return {
          width: dimensions.width,
          height: dimensions.height,
          src: `${pathPrefix}/static/${imageName}`,
        }
      },
    },
    resize: {
      type: new GraphQLObjectType({
        name: `ImageSharpResize`,
        fields: {
          src: { type: GraphQLString },
          tracedSVG: {
            type: GraphQLString,
            resolve: async parent => {
              if (!didShowTraceSVGRemovalWarningResize) {
                console.warn(
                  `"tracedSVG" placeholder field is no longer supported (used in ImageSharp.resize processing), falling back to "base64". See https://gatsby.dev/tracesvg-removal/`
                )
                didShowTraceSVGRemovalWarningResize = true
              }
              const { src } = await base64({
                file: parent.file,
                cache,
              })
              return src
            },
          },
          width: { type: GraphQLInt },
          height: { type: GraphQLInt },
          aspectRatio: { type: GraphQLFloat },
          originalName: { type: GraphQLString },
        },
      }),
      args: {
        width: {
          type: GraphQLInt,
        },
        height: {
          type: GraphQLInt,
        },
        quality: {
          type: GraphQLInt,
        },
        jpegQuality: {
          type: GraphQLInt,
        },
        pngQuality: {
          type: GraphQLInt,
        },
        webpQuality: {
          type: GraphQLInt,
        },
        jpegProgressive: {
          type: GraphQLBoolean,
          defaultValue: true,
        },
        pngCompressionLevel: {
          type: GraphQLInt,
          defaultValue: 9,
        },
        pngCompressionSpeed: {
          type: GraphQLInt,
          defaultValue: DEFAULT_PNG_COMPRESSION_SPEED,
        },
        grayscale: {
          type: GraphQLBoolean,
          defaultValue: false,
        },
        duotone: {
          type: DuotoneGradientType,
          defaultValue: false,
        },
        base64: {
          type: GraphQLBoolean,
          defaultValue: false,
        },
        traceSVG: {
          type: PotraceType,
          defaultValue: false,
        },
        toFormat: {
          type: ImageFormatType,
          defaultValue: ``,
        },
        cropFocus: {
          type: ImageCropFocusType,
          defaultValue: sharp.strategy.attention,
        },
        fit: {
          type: ImageFitType,
          defaultValue: sharp.fit.cover,
        },
        background: {
          type: GraphQLString,
          defaultValue: `rgba(0,0,0,1)`,
        },
        rotate: {
          type: GraphQLInt,
          defaultValue: 0,
        },
        trim: {
          type: GraphQLFloat,
          defaultValue: 0,
        },
      },
      resolve: (image, fieldArgs, context) => {
        const file = getNodeAndSavePathDependency(image.parent, context.path)
        const args = { ...fieldArgs, pathPrefix }
        return new Promise(resolve => {
          if (fieldArgs.base64) {
            resolve(
              base64({
                file,
                cache,
              })
            )
          } else {
            const o = queueImageResizing({
              file,
              args,
            })
            resolve(
              Object.assign({}, o, {
                image,
                file,
                fieldArgs: args,
              })
            )
          }
        })
      },
    },
  }
}
Keeps track of asynchronous file copy to prevent sequence errors in the
underlying fs-extra module during parallel copies of the same file