diff --git a/content/04/001-cosmic/index.md b/content/04/001-cosmic/index.md
new file mode 100644
index 0000000..129c076
--- /dev/null
+++ b/content/04/001-cosmic/index.md
@@ -0,0 +1,10 @@
+Gatsby Source for Cosmic
+
+
+843 downloads July 31.
+https://www.gatsbyjs.com/plugins/gatsby-source-cosmicjs/
+
+https://github.com/cosmicjs/gatsby-source-cosmicjs
+
+
+issue by _ _ _
diff --git a/content/04/001-craft/index.md b/content/04/001-craft/index.md
new file mode 100644
index 0000000..df50a3f
--- /dev/null
+++ b/content/04/001-craft/index.md
@@ -0,0 +1,4 @@
+1.7 down aug 9
+
+
+https://github.com/craftcms/gatsby-source-craft/issues/55
\ No newline at end of file
diff --git a/content/04/002-builder.io/index.md b/content/04/002-builder.io/index.md
new file mode 100644
index 0000000..d38d93f
--- /dev/null
+++ b/content/04/002-builder.io/index.md
@@ -0,0 +1,4 @@
+
+2-k- down 29.04
+
+https://github.com/BuilderIO/builder/issues
\ No newline at end of file
diff --git a/content/04/002-google-spreadsheets/index.md b/content/04/002-google-spreadsheets/index.md
new file mode 100644
index 0000000..20d95a0
--- /dev/null
+++ b/content/04/002-google-spreadsheets/index.md
@@ -0,0 +1,3 @@
+2- k down 29 april
+
+and 2 plugins
\ No newline at end of file
diff --git a/content/04/005-graphcms/index.md b/content/04/005-graphcms/index.md
new file mode 100644
index 0000000..07d0dc6
--- /dev/null
+++ b/content/04/005-graphcms/index.md
@@ -0,0 +1,6 @@
+
+
+5 k downloads april 29.
+3.5 k down aug 9.
+
+https://github.com/GraphCMS/gatsby-source-graphcms/issues
\ No newline at end of file
diff --git a/content/04/007-contentstack/index.md b/content/04/007-contentstack/index.md
new file mode 100644
index 0000000..ce6cf7c
--- /dev/null
+++ b/content/04/007-contentstack/index.md
@@ -0,0 +1,5 @@
+
+7 k downloads april 29.
+10.4 k dowloads august 9.
+
+https://github.com/contentstack/gatsby-source-contentstack/issues/127
\ No newline at end of file
diff --git a/content/04/008-k-youtube/index.md b/content/04/008-k-youtube/index.md
new file mode 100644
index 0000000..b180d05
--- /dev/null
+++ b/content/04/008-k-youtube/index.md
@@ -0,0 +1 @@
+https://queenraaecodesmain-doneforyouimagecdnupgradeofyou.gtsb.io/done-for-you-image-cdn-upgrade-of-your-gatsby-source-plugin/
\ No newline at end of file
diff --git a/content/04/009-ghost/index.md b/content/04/009-ghost/index.md
new file mode 100644
index 0000000..0ab604a
--- /dev/null
+++ b/content/04/009-ghost/index.md
@@ -0,0 +1,35 @@
+Gatsby Source Ghost
+
+9 k downloads april 29.
+6.8 k downloads august 8.
+
+
+https://github.com/TryGhost/gatsby-source-ghost
+
+issue by dustin
+[feat]: add Gatsby Cloud functionality and "hot reloading" #15
+
+https://github.com/TryGhost/gatsby-source-ghost/issues/15
+
+Aileen
+aileen@ghost.org
+https://twitter.com/aileen_bkr
+
+
+@aileen_bkr
+Always learning at
+@ghost
+. Currently freezing in Europe 🥶
+Follow my path here 👉 http://nomadlist.com/@aileencgn
+
+not a dev-rel?
+https://github.com/aileen?tab=repositories
+
+Ghost is an open source, professional publishing platform built on a modern Node.js technology stack — designed for teams who need power, flexibility and performance.
+
+Hannah Wolfe
+ErisDS
+CTO @TryGhost 😁 👻 😁 💃
+
+https://twitter.com/ErisDS
+Ghost CTO & co-founder, former MOO Crew, polyglot developer, lover of JavaScript & Open Source
\ No newline at end of file
diff --git a/content/04/013-k-storyblok/index.md b/content/04/013-k-storyblok/index.md
new file mode 100644
index 0000000..d174cfc
--- /dev/null
+++ b/content/04/013-k-storyblok/index.md
@@ -0,0 +1,10 @@
+gatsby-source-storyblok
+
+13 k downloads april 1.
+
+commented out, means new code in this PR
+
+```js
+
+
+```
\ No newline at end of file
diff --git a/content/04/021-k-drupal/index.md b/content/04/021-k-drupal/index.md
new file mode 100644
index 0000000..6ee5e18
--- /dev/null
+++ b/content/04/021-k-drupal/index.md
@@ -0,0 +1,104 @@
+gatsby-source-drupal
+
+
+21 k downloads april 1.
+
+17 k august 9.
+[feat(gatsby-source-drupal): Image CDN support
+#35265](https://github.com/gatsbyjs/gatsby/pull/35265/files)
+```js
+// packages/gatsby-source-drupal/src/normalize.js
+
+// const probeImageSize = require(`probe-image-size`)
+
+....
+// const nodeFromData = async (
+// datum,
+// createNodeId,
+// entityReferenceRevisions = [],
+// pluginOptions,
+// fileNodesExtendedData
+// ) => {
+ const { attributes: { id: attributeId, ...attributes } = {} } = datum
+ const preservedId =
+ typeof attributeId !== `undefined` ? { _attributes_id: attributeId } : {}
+ const langcode = attributes.langcode || `und`
+// const type = datum.type.replace(/-|__|:|\.|\s/g, `_`)
+
+// const isFile = isFileNode(datum, type)
+
+// const url = isFile
+// ? pluginOptions.baseUrl + getFileUrl(datum.attributes)
+// : null
+
+// const extraNodeData = fileNodesExtendedData?.get(datum.id) || null
+// const imageSize = isFile ? extraNodeData || (await probeImageSize(url)) : null
+
+// const gatsbyImageCdnFields =
+// isFile &&
+// imageSize &&
+// imageSize.width &&
+// imageSize.height &&
+// url &&
+// datum.attributes.filemime
+// ? {
+// filename: attributes?.filename,
+// url,
+// placeholderUrl: url,
+// width: imageSize.width,
+// height: imageSize.height,
+// mimeType: datum.attributes.filemime,
+// }
+// : {}
+
+
+
+
+// packages/gatsby-source-drupal/src/utils.js
+/**
+ * This FN returns a Map with additional file node information that Drupal doesn't return on actual file nodes (namely the width/height of images)
+ */
+exports.getExtendedFileNodeData = allData => {
+ const fileNodesExtendedData = new Map()
+
+ for (const contentType of allData) {
+ if (!contentType) {
+ continue
+ }
+
+ contentType.data.forEach(node => {
+ if (!node) {
+ return
+ }
+
+ const { relationships } = node
+
+ if (relationships) {
+ for (const relationship of Object.values(relationships)) {
+ const relationshipNodes = Array.isArray(relationship.data)
+ ? relationship.data
+ : [relationship.data]
+
+ relationshipNodes.forEach(relationshipNode => {
+ if (!relationshipNode) {
+ return
+ }
+
+ if (
+ relationshipNode.type === `file--file` &&
+ relationshipNode.meta
+ ) {
+ fileNodesExtendedData.set(
+ relationshipNode.id,
+ relationshipNode.meta
+ )
+ }
+ })
+ }
+ }
+ })
+ }
+
+ return fileNodesExtendedData
+}
+```
\ No newline at end of file
diff --git a/content/04/022-datoCMS/index.md b/content/04/022-datoCMS/index.md
new file mode 100644
index 0000000..cafce82
--- /dev/null
+++ b/content/04/022-datoCMS/index.md
@@ -0,0 +1,4 @@
+
+22 k downloads april 29.
+
+https://github.com/datocms/gatsby-source-datocms/issues
\ No newline at end of file
diff --git a/content/04/031-k-shopify/index.md b/content/04/031-k-shopify/index.md
new file mode 100644
index 0000000..e140827
--- /dev/null
+++ b/content/04/031-k-shopify/index.md
@@ -0,0 +1,11 @@
+gatsby-source-shopify
+
+31 k downloads april 1.
+26.2 k downloads april 29.
+
+No PR or issue
+
+commented out, means new code in this PR
+
+```js
+```
\ No newline at end of file
diff --git a/content/04/033-k-strapi/index.md b/content/04/033-k-strapi/index.md
new file mode 100644
index 0000000..0298208
--- /dev/null
+++ b/content/04/033-k-strapi/index.md
@@ -0,0 +1,12 @@
+gatsby-source-strapi
+
+33.8 k downloads april 1.
+38.9 k downloads april 29.
+33 k downloads aug 9.
+
+No PR or issue
+
+commented out, means new code in this PR
+
+```js
+```
\ No newline at end of file
diff --git a/content/04/040-prismic/index.md b/content/04/040-prismic/index.md
new file mode 100644
index 0000000..dde12bc
--- /dev/null
+++ b/content/04/040-prismic/index.md
@@ -0,0 +1,5 @@
+
+
+40 k downloads april 29.
+
+https://github.com/prismicio/prismic-gatsby/issues/486
\ No newline at end of file
diff --git a/content/04/056-k-sanity/index.md b/content/04/056-k-sanity/index.md
new file mode 100644
index 0000000..d3e687d
--- /dev/null
+++ b/content/04/056-k-sanity/index.md
@@ -0,0 +1,67 @@
+gatsby-source-sanity@^7.4.0
+56.8 k downloads april 1.
+61.7 k downloads april 29.
+54 k downloads aug 9.
+
+
+https://gatsby.dev/img
+
+```js
+// package.json
+"gatsby-plugin-utils": "^3.5.0-next.0",
+```
+
+// src/gatsby-node.ts
+
+[feat: Add Gatsby Image CDN support
+#148](https://github.com/sanity-io/gatsby-source-sanity/pull/148/files#)
+
+commented out, means new code in this PR
+
+```js
+// src/util/normalize.ts
+
+// Transform a Sanity document into a Gatsby node
+export function toGatsbyNode(doc: SanityDocument, options: ProcessingOptions): SanityInputNode {
+ const {createNodeId, createContentDigest, overlayDrafts} = options
+ const safe = prefixConflictingKeys(doc)
+ const withRefs = rewriteNodeReferences(safe, options)
+ const type = getTypeName(doc._type)
+
+// const gatsbyImageCdnFields = [`SanityImageAsset`, `SanityFileAsset`].includes(type)
+// ? {
+// filename: withRefs.originalFilename,
+// width: withRefs?.metadata?.dimensions?.width,
+// height: withRefs?.metadata?.dimensions?.height,
+// url: withRefs?.url,
+// placeholderUrl:
+// type === `SanityImageAsset`
+// ? urlBuilder
+// .image(withRefs.url)
+// .width(20)
+// .height(30)
+// .quality(80)
+// .url()
+// // this makes placeholder urls dynamic in the gatsbyImage resolver
+// ?.replace(`w=20`, `w=%width%`)
+// ?.replace(`h=30`, `h=%height%`)
+// : null,
+// }
+// : {}
+
+ return {
+ ...withRefs,
+ ...rawAliases,
+// ...gatsbyImageCdnFields,
+
+ id: safeId(overlayDrafts ? unprefixId(doc._id) : doc._id, createNodeId),
+ children: [],
+ internal: {
+// type,
+ contentDigest: createContentDigest(JSON.stringify(withRefs)),
+ },
+ }
+
+
+
+```
\ No newline at end of file
diff --git a/content/04/062-netlify-cms/index.md b/content/04/062-netlify-cms/index.md
new file mode 100644
index 0000000..e78ccfb
--- /dev/null
+++ b/content/04/062-netlify-cms/index.md
@@ -0,0 +1,6 @@
+gatsby-plugin-netlify-cms
+
+62 k downloads april 29.
+56 k down aug 9
+
+https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-netlify-cms
\ No newline at end of file
diff --git a/content/04/079-k-wordpress/index.md b/content/04/079-k-wordpress/index.md
new file mode 100644
index 0000000..db55a6d
--- /dev/null
+++ b/content/04/079-k-wordpress/index.md
@@ -0,0 +1,61 @@
+gatsby-source-wordpress
+79 k downloads april 1.
+64 k downloads april 29.
+47 k down aug 9.
+
+[feat(gatsby-source-wordpress): enable image-cdn
+#34832](https://github.com/gatsbyjs/gatsby/pull/34832/files)
+
+commented out, means new code in this PR
+
+```js
+//packages/gatsby-source-wordpress/src/steps/source-nodes/fetch-nodes/fetch-referenced-media-items.js
+import { getPlaceholderUrlFromMediaItemNode } from "../create-nodes/process-node"
+
+....
+ // const placeholderUrl = getPlaceholderUrlFromMediaItemNode(
+ // node,
+ // pluginOptions
+ // )
+
+ // const url = node.sourceUrl || node.mediaItemUrl
+
+ // const filename =
+ // node?.mediaDetails?.file?.split(`/`)?.pop() ||
+ // path.basename(urlUtil.parse(url).pathname)
+
+ node = {
+ ...node,
+ // url,
+ // contentType: node.contentType,
+ // mimeType: node.mimeType,
+ // filename,
+ // filesize: node?.mediaDetails?.fileSize,
+ // width: node?.mediaDetails?.width,
+ // height: node?.mediaDetails?.height,
+ // placeholderUrl:
+ // placeholderUrl ?? node?.mediaDetails?.sizes?.[0]?.sourceUrl ?? url,
+ parent: null,
+ internal: {
+ contentDigest: createContentDigest(node),
+ type: buildTypeName(`MediaItem`),
+ },
+ }
+
+ // if (localFileNode) {
+ // node.localFile = localFileNode?.id
+ // }
+
+ const normalizedNode = normalizeNode({ node, nodeTypeName: `MediaItem` })
+
+ await actions.createNode(normalizedNode)
+ @@ -394,7 +416,7 @@ export const fetchMediaItemsBySourceUrl = async ({
+ )
+
+ nodes.forEach((node, index) => {
+// if (!node || !node?.localFile?.id) {
+ return
+
+
+```
+
diff --git a/content/04/278-k-contentful/index.md b/content/04/278-k-contentful/index.md
new file mode 100644
index 0000000..d0a1893
--- /dev/null
+++ b/content/04/278-k-contentful/index.md
@@ -0,0 +1,49 @@
+gatsby-source-contentful@^7.8.0
+278.1 k downloads april 1.
+274.8 k downloads april 29.
+262 k down aug 9.
+
+https://www.gatsbyjs.com/plugins/gatsby-source-contentful/?=gatsby-source-contentful
+
+
+[feat(gatsby-source-contentful): enable image-cdn
+#34831](https://github.com/gatsbyjs/gatsby/pull/34831/files)
+
+commented out, means new code in this PR
+```js
+// packages/gatsby-source-contentful/src/normalize.js
+
+
+// const file = assetItem.fields.file ? getField(assetItem.fields.file) : {}
+ const assetNode = {
+ contentful_id: assetItem.sys.id,
+ spaceId: space.sys.id,
+ id: mId(space.sys.id, assetItem.sys.id, assetItem.sys.type),
+ createdAt: assetItem.sys.createdAt,
+ updatedAt: assetItem.sys.updatedAt,
+ parent: null,
+ children: [],
+// file,
+ title: assetItem.fields.title ? getField(assetItem.fields.title) : ``,
+ description: assetItem.fields.description
+ ? getField(assetItem.fields.description)
+ : ``,
+ node_locale: locale.code,
+ internal: {
+ type: `${makeTypeName(`Asset`)}`,
+ },
+ sys: {
+ type: assetItem.sys.type,
+ },
+ // url: `https:${file.url}`,
+ // placeholderUrl: `https:${file.url}?w=%width%&h=%height%`,
+ // mimeType: file.contentType,
+ // filename: file.fileName,
+ // width: file.details?.image?.width,
+ // height: file.details?.image?.height,
+ }
+
+
+
+
+```
\ No newline at end of file
diff --git a/content/04/CMS-no-plugin-yet/index.md b/content/04/CMS-no-plugin-yet/index.md
new file mode 100644
index 0000000..a744df9
--- /dev/null
+++ b/content/04/CMS-no-plugin-yet/index.md
@@ -0,0 +1,166 @@
+
+
+
+directus
+
+tina
+
+keystone
+
+webiny
+
+ponzu
+
+prose
+
+publii
+
+umbraco
+
+jekyll
+
+squidex
+
+hexo
+
+daptin
+
+dotCMS
+
+payload
+
+superdesk
+
+flextype
+
+contenta
+
+coisas
+
+unite
+
+rooftop
+
+abeCMS
+
+b2evolution
+
+hashbrown
+
+any JSON
+
+scrivito
+
+tritan
+
+crystalize (starter)
+
+editlayer
+
+accoustic
+
+airship
+
+amplience
+
+apirocket
+
+appernetic
+
+aventum
+
+blogody
+
+bloomreach
+
+bowtie
+
+cloud
+
+comfortable
+
+forestry
+
+frontaid
+
+
+
+gridly
+
+headless
+
+headly
+
+lexas
+
+magnolia
+
+nodewrite
+
+primcore
+
+prepr
+
+quintype
+
+siteleaf
+
+stastic
+
+troglio
+
+typewriter
+
+versioned
+
+**less than 1 k downloads**
+
+cockpit
+
+apostrophe
+on 14 Jul 2021
+Git stats
+
+statamic
+
+genetics mesh
+
+enonic
+
+sensenet
+
+slicknode
+
+flotiq 800
+
+crownpeak
+
+agility
+
+bagelDB
+
+cloudcannon
+
+flamelink
+
+fridge
+/Users/olavea/Desktop/Develop/4_Rubys_timemachine/OlaVea-TimeShip/Rubys-TimeShip/content/04/008-k-youtube
+gathercontent
+
+mozaik
+
+takeshape
+
+zesty
+
+
+**Removed?**
+
+plasmic
+
+
+**other**
+
+transformer-cloudinary 183
+
+WPengine
\ No newline at end of file
diff --git a/content/05/029-cloudinary/index.md b/content/05/029-cloudinary/index.md
new file mode 100644
index 0000000..622bfee
--- /dev/null
+++ b/content/05/029-cloudinary/index.md
@@ -0,0 +1,155 @@
+```js
+gatsby-transformer-cloudinary
+
+2.9 k downloads May 5
+
+
+gatsby-source-cloudinary
+
+2.1 k downloads May 5
+
+
+
+https://cloudinary.com/documentation/node_integration
+
+https://dev.to/chuloo/handling-images-in-gatsby-with-high-performance-10ea
+
+https://www.npmjs.com/package/cloudinary/v/1.11.0
+
+https://cloudinary.com/documentation/upload_images
+
+https://getcontenttools.com/tutorials/image-uploads-with-cloudinary
+
+https://cloudinary.com/documentation/upload_widget
+
+https://cloudinary.com/documentation/node_integration
+
+plugins
+
+https://github.com/gatsbyjs/gatsby/blob/master/examples/creating-source-plugins/source-plugin/gatsby-node.js
+
+
+```
+```js
+
+const {newCloudinary, getResourceOptions} = require('./utils');
+// Does `type` come from utils and from "cloudinary"?
+// const DEFAULT_KEYS = ["resourceType", _ _ _ _, "type" ];
+// _ _ _ _ _ result.type = result.type || DEFAULT_TYPE;
+// return result
+
+const type = `CloudinaryMedia`;
+// media comes from Cloudinary?
+const getNodeData = (gatsby, media) => {
+ return {
+ ...media,
+ id: gatsby.createNodeId(`cloudinary-media-${media.public_id}`),
+ parent: null,
+ internal: {
+ type,
+ content: JSON.stringify(media),
+ contentDigest: gatsby.createContentDigest(media)
+ }
+ };
+};
+
+const addTransformations = (resource, transformation, secure)=>{
+ const splitURL = secure ? resource.secure_url.split('/') : resource.url.split('/');
+ splitURL.splice( 6, 0, transformation);
+
+ const transformedURL = splitURL.join('/');
+ return transformedURL;
+
+}
+
+const createCloudinaryNodes = (gatsby, cloudinary, options) => {
+ return cloudinary.api.resources(options, (error, result) => {
+ // What does res....length mean?
+ // pagination?
+ const hasResources = (result && result.resources && result.resources.length);
+
+ if (error) {
+ console.error(error);
+ return;
+ }
+
+ if (!hasResources) {
+ console.warn('\n ~Yikes! No nodes created because no Cloudinary resources found. Try a different query?');
+ return;
+ }
+
+ result.resources.forEach(resource => {
+ const transformations = "q_auto,f_auto" // Default CL transformations, todo: fetch base transformations from config maybe.
+
+ resource.url = addTransformations(resource, transformations);
+ resource.secure_url = addTransformations(resource, transformations, true);
+
+ const nodeData = getNodeData(gatsby, resource);
+ // creating nodes
+ gatsby.actions.createNode(nodeData);
+ });
+
+ console.info(`Added ${hasResources} CloudinaryMedia ${hasResources > 1 ? 'nodes' : 'node'}`);
+ });
+};
+
+exports.createSchemaCustomization = ({ actions }) => {
+ const { createTypes } = actions;
+
+ createTypes(`
+ type TobbieCloudinaryMedia implements Node {
+
+ }
+ `)
+}
+
+exports.sourceNodes = (gatsby, options) => {
+ // 3. Global state
+ // The Old Way?
+ // Using `exports.sourceNodes` passing along `options`
+ // There is no onPreBootstrap (nor onPreInit) setting global state
+ // so that other functions can access that global state.
+ // https://www.gatsbyjs.com/docs/reference/release-notes/migrating-source-plugin-from-v3-to-v4/#the-old-way-2
+ const cloudinary = newCloudinary(options);
+ const resourceOptions = getResourceOptions(options);
+// calling createCloudinaryNodes( gatsbyUtils ++)
+ return createCloudinaryNodes(gatsby, cloudinary, resourceOptions);
+};
+
+// 3. Global state
+// The New Gatsby Way
+// https://www.gatsbyjs.com/docs/reference/release-notes/migrating-source-plugin-from-v3-to-v4/#the-new-way-2
+let coreSupportsOnPluginInit = 'unstable' | 'stable' | undefined;
+
+try {
+ const { isGatsbyNodeLifeCycleSupported } = require(`gatsby-plugin-utils`);
+ if (isGatsbyNodeLifeCycleSupported(`onPluginInit`)) {
+ coreSupportsOnPluginInit = 'stable';
+ } else if (isGatsbyNodeLifeCycleSupported(`unstable_onPluginInit`)) {
+ coreSupportsOnPluginInit = 'unstable';
+ }
+} catch (error) {
+ console.error(
+ `Could not check if Gatsby supports onPluginInit lifecycle 🚴♀️ `,
+ );
+}
+
+// variable that is changable
+let globalPluginOptions = {};
+
+// function that is _ _ _ _ _
+const initializeGlobalState = (_, pluginOptions) => {
+ globalPluginOptions = pluginOptions;
+};
+
+if (coreSupportsOnPluginInit === 'stable') {
+ exports.onPluginInit = initializeGlobalState;
+} else if (coreSupportsOnPluginInit === 'unstable') {
+ exports.unstable_onPluginInit = initializeGlobalState;
+} else {
+ exports.onPreBootstrap = initializeGlobalState;
+}
+
+// func
+
+```
\ No newline at end of file
diff --git a/content/plugin-workbook/image-cdn-chapter-i/again.jpg b/content/plugin-workbook/image-cdn-chapter-i/again.jpg
new file mode 100644
index 0000000..d20baaa
Binary files /dev/null and b/content/plugin-workbook/image-cdn-chapter-i/again.jpg differ
diff --git a/content/plugin-workbook/image-cdn-chapter-i/cdn.jpg b/content/plugin-workbook/image-cdn-chapter-i/cdn.jpg
new file mode 100644
index 0000000..a9c8305
Binary files /dev/null and b/content/plugin-workbook/image-cdn-chapter-i/cdn.jpg differ
diff --git a/content/plugin-workbook/image-cdn-chapter-i/draft.md b/content/plugin-workbook/image-cdn-chapter-i/draft.md
new file mode 100644
index 0000000..f045e97
--- /dev/null
+++ b/content/plugin-workbook/image-cdn-chapter-i/draft.md
@@ -0,0 +1,901 @@
+## GATSBY IMAGE CDN 🧚♀️
+
+Taskerbell-task: 🧚♀️
+
+- **WHAT**: IMAGE CDN upgrade on 👑 Queen @raae's gatsby-demo-web-scraping
+- **WHY**: faster builds and faster develop time so our user can escape the Croco-Clock
+- **HOW**: is 8 sub-tasks in 3 parts
+
+## How: part 1 of 3
+
+- [ ] I. Inside. Inside! I must climb inside! 🏠
+
+- [ ] M. Must I `add....face(` to Tobbie? 🃏
+
+
+
+
+
+In the middle of a wildly windy night Ruby hears a crash on her rooftop. She climbs up and sees a weird ....ship thing with two weird windows. One yellow window and one light blue window, broken glass. Dark darkness inside.
+
+
+## I. «Inside. Inside! I must climb inside, but into which window?» Says Ruby to the Lizabeth.
+
+### Inside which `exports....` window should Ruby climb?
+
+
+🪟 + 💜
+```js
+exports.sourceNodes = ( gatsbyUtils ) => {
+```
+
+or
+🪟 + 💙 + 🕳️ + 💔
+
+```js
+exports.createSchemaCustomization = ({ actions, schema }) => {
+```
+
+I guess you'll follow your reckless ❤️ into the dangerously broken window. Says Lizabeth.
+
+
+
+## Inside Ruby finds a baby-kraken 🦑 who looks murdered, or maybe .... fixable?
+
+In what order will Tobbie's broken hearted code work again?
+
+### In what order will the code work again?
+
+```js
+interfaces: [`Node`, `RemoteFile`],
+```
+
+```js
+name: `....TobbieThumbnail`,
+fields: {
+ // 🍓
+},
+```
+
+```js
+schema
+```
+
+```js
+.buildObjectType({
+```
+«»
+«Aha! A murderess and a sourceress! Chaught red handed doing their wicked work in the deep dark night. I'll report you both to Queen Mary herself, she has been suspecting you two of treasonous treachery for years. This looks like just the kind of thing I have been spying on you to reveal. See you loosers later on the inside of a Tower prison cell. Where you both belong. Mo-ha-ha-ha!» Simona "" Renard departs.
+
+«Hurry, we must find the real kraken-killer to win our freedom. Says Lizabeth
+
+## M. Must I `add....face(` to Tobbie? 🃏
+
+### Must I `add....face(` to Tobbie?
+
+«No, This is not a plugin.»
+
+«What's a plugin?»
+
+### You've helped Ruby do these 2 sub-tasks:
+
+
+- [ ] I. Inside Tobbie's ship 🏠
+
+- [ ] M. Must I `add....face(` to Tobbie? 🃏
+
+
+
+## You DID Great! 1/3 of the treasure hunt 💪 😺 🏴☠️
+
+
+### Next up:
+
+Because Fix the ship's nodes
+
+- [ ] A. Add another window `exports....`?
+
+- [ ] G. is for GraphiQL where we find 🍓 fields
+
+- [ ] O. is for onCreateDev....? 🃏
+
+## A. Add another window `exports....`?
+
+Ruby climbs out onto the rooftop again, now what window?
+
+
+### Add another window and name it `exports....`?
+
+
+🪟 +
+```js
+exports.sourceNodes = async (gatsbyUtils) => {
+
+
+}
+```
+
+or
+
+🪟 +
+```js
+exports.onCreateNode = (gatsbyUtils) => {
+ const { actions, node, createNodeId } = gatsbyUtils;
+
+```
+
+or
+
+🪟 +
+```js
+exports.createSchemaCustomization = (gatsbyUtils) => {
+ const { actions, node, createNodeId } = gatsbyUtils;
+
+
+}
+```
+
+
+
+
+### Deconstruct `actions` to `create....` Tobbie's ....?
+
+```js
+
+const { createNode, createType } = actions;
+createType(
+
+)
+
+```
+
+or
+
+
+```js
+const { createNode, createType } = actions;
+createNode(
+
+)
+```
+
+
+
+
+
+
+
+
+
+
+## G. is for GraphiQL where we find 🍓 fields
+
+Here is a piece of code, but half of it is missing
+
+### Copy and paste inside `create....(`
+
+```js
+{
+ height: 630,
+ url: "node.coverSrc",
+ mimeType: "image/jpeg",
+ parent: "node.id",
+
+ width: 1200,
+ id: "an id",
+ filename: "node.id" + ".jpg",
+ internal: {
+ type: "prefixAsset",
+ contentDigest: "node.internal.contentDigest",
+ },
+
+ crowdcastId: "node.id",
+}
+```
+
+## G. Go into GraphiQL 🍓 fields and find a 🍓 for each of the h.u.m.p.-w.i.f.i.-c. fields
+
+
+
+
+
+
+
+- [ ] h.
+- [ ] u. 💰 node.coverSrc or node.url or node.rawScrape.path
+- [ ] m. "image/jpeg" or node.internal.mediaType or node.internal.type
+- [ ] p. node.id or node.parent
+
+- [ ] w.
+- [ ] i. createNodeId(node.coverSrc) or createNodeId(node.url) or
+- [ ] f. node.id + ".png" or node.title + ".png"
+- [ ] i. internal: {
+ type: "CrowdcastTobbieThumbnail" or node.internal.type
+ contentDigest: node.internal.contentDigest or node.internal.content
+ },
+ or or
+
+- [ ] c. node.url or node.title
+
+
+## O. OK add another window this we must code ourselves first is for onCreateDev....? 🃏
+
+
+
+### O. is for onCreateDev....? 🃏
+
+
+How many windows does one ship need? Says Ruby
+
+since we are 1554 we need a backwards compatible plugin to get ...(?)
+
+I say no to this, it's still not a plugin
+
+
+
+### You've done these 3 sub-tasks:
+
+
+
+- [ ] A. Another
+
+- [ ] G. is for GraphiQL where we find 🍓 fields
+
+- [ ] O. is for onCreateDev....? 🃏
+
+
+
+## You DID it! 2/3 of the treasure hunt 💪 😺 🏴☠️
+
+### Next up:
+
+- [ ] C. createNodeId( 🦑
+
+- [ ] D. blind date for Tobbie 😎 🍸
+
+- [ ] N. Now you can add Gatsby Image! 🖼️
+
+
+
+
+
+### C. createNodeId(
+
+### C. copy :
+
+```js
+ const TobbieThumbnailNodeId = createNodeId(
+ `${YOUTUBE_THUMBNAIL_TYPE} >>> ${....}`
+ );
+```
+
+### C. ${....}`
+
+```js
+
+```
+
+or
+
+```js
+
+```
+
+### D. blind Date for Tobbie 😎 🍸
+How can we tell the ... to go on a blind date with the CrowdcastThumbnail node?
+
+
+### D. Copy:
+
+
+```js
+const createCrowdcastTypes = (gatsbyUtils) => {
+ const { actions, schema } = gatsbyUtils;
+
+ actions.createTypes([
+ `
+ type YouTube implements Node {
+ thumbnail: YouTubeThumbnail @link(
+ from: "...."
+ by: "...."
+ )
+ }
+ `,
+```
+
+
+### D. from: "...." by: "...."
+
+```js
+
+```
+
+or
+
+```js
+
+```
+
+### N. is for Now you can add Gatsby Image 🖼️
+And maybe see a drawing of the real kraken-killer
+
+## Copy into terminal 💀
+
+```shell
+yarn add gatsby-plugin-image gatsby-plugin-sharp gatsby-source-filesystem gatsby-transformer-sharp
+```
+
+## Add the plugins to your gatsby-config.js:
+
+```js
+// gatsby-config.js
+module.exports = {
+ plugins: [
+ `gatsby-plugin-image`,
+ `gatsby-plugin-sharp`,
+ `gatsby-transformer-sharp`,
+ ],
+}
+```
+
+«Who is this?»
+
+«Doesn't look like a cold blooded kraken-killer to me. What do you think?»
+
+«No...»
+
+«What' that smokey smell?»
+
+«Somebody burned their .... midnight snack in the stove?»
+
+«Look down! It's OUR house burning!»
+
+«Jingle the fire bells! befor the whole bridge burns!»
+
+«Jingle bells
+
+Batman smells
+
+Ruby laid an egg
+
+Batmobile lost it's wheel
+
+joker got away, hey!»
+«»
+«»
+## Follow Docs:
+
+[https://www.gatsbyjs.com/docs/how-to/images-and-media/using-gatsby-plugin-image/](https://www.gatsbyjs.com/docs/how-to/images-and-media/using-gatsby-plugin-image/)
+
+
+
+- [ ] C. createNodeId( 🦑
+
+- [ ] D. blind date for Tobbie and Alice 😎 🍸
+
+- [ ] N. Now you can add Gatsby Image 🖼️
+
+
+## You DID it! The whole treasure hunt 💪 😺 🏴☠️
+
+
+
+
+- G. GraphQL type Tobbie 🦑
+
+- I. I. Inside Tobbie's home 🏠
+
+- M. Must I `add....face(` to Tobbie? 🃏
+
+
+
+- A. Alice's assetNode 🐴
+
+- G. is for GraphiQL where we find 🍓 fields
+
+- O. is for onCreateDev....? 🃏
+
+
+
+- C. createNodeId( 🦑
+
+- D. blind date for Tobbie and Alice 😎 🍸
+
+- N. Now you can Gatsby Image 🖼️
+
+
+
+
+
+stop
+
+### Alice our new uniDonkey is a `const ....`
+
+```js
+const uniAssNode = { height: 630 , width: 1200, }
+const assetNode = { height: 630 , width: 1200, }
+const assetType = { height: 630 , width: 1200, }
+const aliceNode = { height: 630 , width: 1200, }
+```
+
+### Alice uniDonkey goes inside which `const ....`?
+
+```js
+const createCrowdcastNode = async (gatsbyUtils, pluginOptions, youTubeId) => {
+const scrapeCrowdcast = async () => {
+const createCrowdcastThumbnailNode = (gatsbyUtils) => {
+const createCrowdcastTypes = (gatsbyUtils) => {
+```
+
+
+
+### Alice uniDonkey should be called inside which `exports....`?
+
+```js
+exports.pluginOptionsSchema = ({ Joi }) => {
+exports.createSchemaCustomization = (gatsbyUtils) => {
+exports.sourceNodes = async (gatsbyUtils, pluginOptions) => {
+exports.onCreateNode = (gatsbyUtils) => {
+```
+
+
+### Alice uniDonkey is created with `actions.create....`?
+
+```js
+actions.createAssetNode(
+actions.createNode(
+actions.createNodeField(
+actions.createTypes(
+```
+
+
+
+
+
+
+
+### G. is for GrapiQL where we find 🍓 fields
+
+
+```js
+{
+ height: 630,
+ url: allCrowdcastWebinar.node.url,
+ mimeType: "image/jpeg",
+ parent: node.id,
+
+ width: 1200,
+ id: youTubeThumbnailNodeId,
+ filename: node.youTubeId + ".jpg",
+ internal: {
+ type: YOUTUBE_THUMBNAIL_TYPE,
+ contentDigest: node.internal.contentDigest,
+ },
+
+ youTubeId: node.youTubeId,
+}
+```
+
+### O. is for onCreateDev....?
+
+```js
+exports.onCreateDevServer = ({ app }) => {};
+exports.onCreateDev = ({ app }) => {};
+exports.onCreateDev = ({ app }) => {};
+exports.onCreateDev = ({ app }) => {};
+
+```
+
+
+### Inside goes which `........ImageServiceDevRoutes(app);`?
+
+```js
+filePlayImageServiceDevRoutes(app);
+filePolyImageServiceDevRoutes(app);
+filePollyImageServiceDevRoutes(app);
+polyfillImageServiceDevRoutes(app);
+
+```
+
+
+
+
+### C. is for createNodeId(
+
+### C. is for copy this:
+
+```js
+ const youTubeThumbnailNodeId = createNodeId(
+ `${YOUTUBE_THUMBNAIL_TYPE} >>> ${....}`
+ );
+```
+
+### C. ${....}`
+
+```js
+node.youTubeId
+node.oEmbed.id
+youTubeThumbnailNodeId,
+node.id
+```
+
+
+### D. is for Dating
+How can we tell the Crowdcast node it is going on a blind date with the CrowdcastThumbnail node?
+
+
+### D. Copy:
+
+
+```js
+const createYouTubeTypes = (gatsbyUtils) => {
+ const { actions, schema } = gatsbyUtils;
+
+ actions.createTypes([
+ `
+ type YouTube implements Node {
+ thumbnail: YouTubeThumbnail @link(
+ from: "...."
+ by: "...."
+ )
+ }
+ `,
+```
+
+
+### D. from: "...." by: "...."
+
+
+```js
+@link(
+ from: "youTube"
+ by: "youTube"
+)
+```
+
+```js
+@link(
+ from: "youTubeId"
+ by: "youTubeId"
+)
+```
+
+```js
+@link(
+ from: "youTube"
+ by: "youTubeId"
+)
+```
+
+```js
+@link(
+ from: "youTubeId"
+ by: "youTube")
+)
+```
+
+or
+
+```js
+"youTubeId"
+"youTubeThumbnailNodeId"
+"youTubeNodeId"
+"youTubeIds"
+
+```
+
+
+
+**Yo-HOW! Yo-HOW! A pirate’s life for you!**
+
+Your first sub-task:
+
+## G. A new GraphQL _ _ _ _ _ _ type
+
+Guess. Guess. Guess. And guess.
+
+
+Ola Vea is a Gatsby-plugin-Dev who also helps Gatsby-Devs waste less time on unsticky dev-learning. Dev at POW! — the private menstrual cycle journal (usepow.app) 👑 @raae You can reach him at: ola@lillylabs
+
+twiter
+
+Ola Vea😺🏴☠️ is writing a Gatsby-plugin workbook
+
+
+A Gatsby-plugin-Dev who also helps Gatsby-Devs waste less time on unsticky dev-learning. Dev at POW! — the private menstrual cycle journal (usepow.app) 👑 @raae
+
+
+https://olavea.gatsbyjs.io/plugin-workbook/
+
+
+`FULL_NAME` is a `DISCIPLINE` who helps `TARGET_MARKET` with `EXPENSIVE_PROBLEM`. You can reach `HIM/HER` at: `EMAIL_ADDRESS`
+
+
+
+
+a Gatsby-plugin-Dev and consultant who helps Gatsby-Developers stop wasting time on unsticky dev-learning.
+
+A Gatsby-plugin-Dev who also helps Gatsby-Devs waste less time on unsticky dev-learning.
+
+On a mission to rid the Jamstack of Half-Ass-Structured Skill-Builder-Sessions.
+@GatsbyJS
+-dev at POW! — the private menstrual cycle journal (http://usepow.app) 👑
+@raae
+
+[https://ia601302.us.archive.org/18/items/suppliantmaidens_1512_librivox/suppliants_01_aeschylus_64kb.mp3](https://ia601302.us.archive.org/18/items/suppliantmaidens_1512_librivox/suppliants_01_aeschylus_64kb.mp3)
+
+draft:
+
+Ass 1.
+
+
+
+
+
+### Guess the `const ....` of our new uniDonkey, Alice
+
+```js
+const uniAssNode =
+const assetNode =
+const assetType =
+const aliceNode =
+
+```
+
+
+
+
+
+```js
+{
+ height: node.oEmbed.thumbnail_height,
+ url: node.oEmbed.thumbnail_url,
+ mimeType: "image/jpeg",
+ parent: node.id,
+
+ width: node.oEmbed.thumbnail_width,
+ id: youTubeThumbnailNodeId,
+ filename: node.youTubeId + ".jpg",
+ internal: {
+ type: YOUTUBE_THUMBNAIL_TYPE,
+ contentDigest: node.internal.contentDigest,
+ },
+
+ youTubeId: node.youTubeId,
+}
+```
+
+
+Actions 2.
+
+### Guess the `actions....` to `create....` Alice
+
+```js
+actions.createUniAssNode(
+actions.createNode(
+actions.createNodeField(
+actions.createTypes(
+```
+
+
+
+Ass 3.
+
+### Inside which `const ....` do you put Alice uniDonkey's `create....`?
+
+```js
+const createYouTubeNode = async (gatsbyUtils, pluginOptions, youTubeId) => {
+const fetchEmbed = async (id) => {
+const createYouTubeThumbnailNode = (gatsbyUtils) => {
+const createYouTubeTypes = (gatsbyUtils) => {
+```
+
+Ass 4.
+
+### Inside which `exports....` do you call uniDonkey Alice ?
+
+```js
+exports.pluginOptionsSchema = ({ Joi }) => {
+exports.createSchemaCustomization = (gatsbyUtils) => {
+exports.sourceNodes = async (gatsbyUtils, pluginOptions) => {
+exports.onCreateNode = (gatsbyUtils) => {
+```
+
+Stop
+
+GrapiQL H.
+### Guess the `height: node....` to add to our 🍓 fields
+
+```js
+node.thumbnail.height,
+node.oEmbed.height,
+node.oEmbed.thumbnail_height,
+node.oEmbed.html,
+```
+
+GrapiQL U.
+
+### Guess the `url: node....` to add to our 🍓 fields
+
+```js
+node.thumbnail.publicUrl,,
+node.oEmbed.thumbnail_url,
+node.oEmbed.url,
+node.oEmbed.provider_url
+```
+
+GrapiQL M.
+
+
+### Guess the `mimeType: node....` to add to our 🍓 fields
+
+```js
+node.file.contentType,
+"image/jpeg",
+node.thumbnail.mimeType;
+node.thumbnail.internal.mediaType;
+```
+
+
+GrapiQL P.
+
+
+### Guess the `parent: node....` to add to our 🍓 fields
+
+```js
+node.oEmbed.id,
+node.thumbnail.id,
+node.parent,
+node.id,
+```
+
+GrapiQL W.
+
+
+### Guess the `width: node....` to add to our 🍓 fields
+
+```js
+node.oEmbed.thumbnail_width,
+node.oEmbed.width,
+node.thumbnail.width,
+node.thumbnail.resize.width,
+```
+
+GrapiQL I. internal
+
+
+
+### Guess the `contentDigest: node....` to add to our 🍓 fields
+
+```js
+node.thumbnail.internal.contentDigest,
+node.internal.contentDigest,
+node.internal.content,
+node.thumbnail.internal.content,
+```
+
+GrapiQL F.
+
+
+### Guess the `filename: node....` to add to our 🍓 fields
+
+```js
+node.id + ".jpg",
+node.youTubeId + ".jpg",
+node.oEmbed.id + ".jpg",
+node.oEmbed.author_name + ".jpg",
+```
+
+GrapiQL I.
+
+
+### Guess the `id: node....` to add to our 🍓 fields
+
+```js
+node.youTubeId
+node.oEmbed.id
+youTubeThumbnailNodeId,
+node.id
+```
+
+
+O
+onCreateDevServer
+
+
+
+### Must I `........ImageServiceDevRoutes,` to ....?
+
+```js
+filePlayImageServiceDevRoutes,
+filePolyImageServiceDevRoutes,
+filePollyImageServiceDevRoutes,
+polyfillImageServiceDevRoutes,
+
+```
+
+### Must we require ….
+
+```js
+"gatsby-plugin-tools/polyfill-remote-file"
+"gatsby-plugin-utils/polyfill-remote-file"
+"gatsby-utils/polyfill-remote-file"
+"gatsby-plugin-timeship/polyfill-remote-file"
+```
+
+
+
+C
+createNodeId(
+
+```js
+node.youTubeId
+node.oEmbed.id
+youTubeThumbnailNodeId,
+node.id
+```
+
+
+```js
+ const youTubeThumbnailNodeId = createNodeId(
+ `${YOUTUBE_THUMBNAIL_TYPE} >>> ${node.youTubeId}`
+ );
+```
+
+
+D
+Dating how can we get the Crowdcast node to start dating the CrowdcastThumbnail node?
+
+```js
+@link(
+ from: "youTube"
+ by: "youTube")
+)
+```
+
+```js
+@link(
+ from: "youTubeId"
+ by: "youTubeId")
+)
+```
+
+```js
+@link(
+ from: "youTube"
+ by: "youTubeId")
+)
+```
+
+```js
+@link(
+ from: "youTubeId"
+ by: "youTube")
+)
+```
+
+looky
+
+
+```js
+const createYouTubeTypes = (gatsbyUtils) => {
+ const { actions, schema } = gatsbyUtils;
+
+ actions.createTypes([
+ `
+ type YouTube implements Node {
+ thumbnail: YouTubeThumbnail @link(....)
+ }
+ `,
+```
+
+or
+
+```js
+"youTubeId"
+"youTubeThumbnailNodeId"
+"youTubeNodeId"
+"youTubeIds"
+
+```
+
diff --git a/content/plugin-workbook/image-cdn-chapter-i/g1.jpg b/content/plugin-workbook/image-cdn-chapter-i/g1.jpg
new file mode 100644
index 0000000..afb3056
Binary files /dev/null and b/content/plugin-workbook/image-cdn-chapter-i/g1.jpg differ
diff --git a/content/plugin-workbook/image-cdn-chapter-i/g2.jpg b/content/plugin-workbook/image-cdn-chapter-i/g2.jpg
new file mode 100644
index 0000000..bdb3cbe
Binary files /dev/null and b/content/plugin-workbook/image-cdn-chapter-i/g2.jpg differ
diff --git a/content/plugin-workbook/image-cdn-chapter-i/g3.jpg b/content/plugin-workbook/image-cdn-chapter-i/g3.jpg
new file mode 100644
index 0000000..ef60c2c
Binary files /dev/null and b/content/plugin-workbook/image-cdn-chapter-i/g3.jpg differ
diff --git a/content/plugin-workbook/image-cdn-chapter-i/g4.jpg b/content/plugin-workbook/image-cdn-chapter-i/g4.jpg
new file mode 100644
index 0000000..cccb59c
Binary files /dev/null and b/content/plugin-workbook/image-cdn-chapter-i/g4.jpg differ
diff --git a/content/plugin-workbook/image-cdn-chapter-i/index.md b/content/plugin-workbook/image-cdn-chapter-i/index.md
new file mode 100644
index 0000000..94aeeca
--- /dev/null
+++ b/content/plugin-workbook/image-cdn-chapter-i/index.md
@@ -0,0 +1,185 @@
+---
+title: Gatsby-plugin-workbook
+author: "@OlaHolstVea"
+date: 2022-04-30
+---
+
+#### Chapter i.
+
+## GATSBY IMAGE CDN
+
+A time-saving print-out.
+
+- For a Gatsby-Developer or
+- For a Dev Rel at a headless CMS
+- For a Plugin Pirate Princess between 7 and 11 years
+
+
+
+
+### Before diving deep down into IMAGE CDN
+Let’s talk about:
+- what doing ONE dev-task again && again is like. And
+- how you can automate in your dev-brain that ONE dev-task by doing it again && again. On and on.
+
+
+#### What is it like?
+Doing your dev-task again && again is a little like playing Happy Birthday on a piano until the melody sticks to your fingers.
+
+So why do we devs so seldom do our dev-tasks again && again?
+
+Because….
+
+But wait! Since I am a plugin-pirate captain I can say «Let’s skip ahead to my next question!»
+
+What can you **do** to find out **if a thing is actually doable?**
+
+Or more specifically….
+
+
+#### Will DOING a dev-ask again && again
+
+Help you automate it in your dev-brain?
+
+“The best way to find out? DOING it!” – Captain Ola Vea senior.
+
+
+
+
+## IMAGE CDN
+
+### Gatsby-source-youtube-oembed Plugin Upgrade
+
+Your dev-task: the WHAT, the WHY and the How.
+
+- WHAT: you’re DOING an IMAGE CDN upgrade on 👑 Queen @raae's gatsby-source-youtube-oembed. And
+- WHY: faster builds.
+
+«Nothing piraty about WHAT and WHY.» You say? Maybe not, but look at HOW....
+
+- HOW: is guessing, not coding. (Piraty, right❓ 😺 🏴☠️ But there's more....)
+
+## How: You’ll only do the first third of your dev-task
+
+- Firstly you’ll not be coding the first few times, you’ll be **guessing**. As I mentioned, but also.
+- Secondly you’ll be guessing **again && again**, even if it feels too easy for your dev-brain.
+- Thirdly you’ll **not even TRY to finish the whole IMAGE CDN upgrade** on gatsby-source-youtube-oembed, you’ll only do the first third of the dev-task. Why so tiny a task? We'll get into that in the emails you'll get later.
+
+
+
+
+### Guess her type
+
+```js
+GraphQL node type
+GraphQL kraken type
+GraphQL octopus type
+GraphQL object type
+```
+
+
+
+
+### Puzzle the shards with arrows
+
+```js
+ interfaces: [`Node`, `RemoteFile`],
+ name: `....`,
+ fields: {
+ youTubeId: "String!",
+ },
+ schema
+ .buildObjectType({
+```
+
+
+
+
+### Guess the name of our new type of friend
+
+```js
+TobbieThumbnailKraken
+TobbieYouTubeThumbnailObject
+ThumbnailTobbieOctopus
+YouTubeThumbnailTobbie
+```
+
+
+
+
+### Guess the "actions...." to "create" Tobbie
+
+```js
+actions.createKraken(
+actions.createNode(
+actions.createNodeField(
+actions.createTypes(
+```
+
+
+
+
+## I. Is your next sub-task:
+
+### Inside which const do you put Tobbie's roof?
+
+```js
+const createYouTubeNode = async (gatsbyUtils, pluginOptions, youTubeId) => {
+const fetchEmbed = async (id) => {
+const createYouTubeThumbnailNode = (gatsbyUtils) => {
+const createYouTubeTypes = (gatsbyUtils) => {
+```
+
+### Inside which _ _ - will Tobbie live?
+
+```js
+{}
+[]
+()
+##
+```
+
+### Inside which "exports...." do we call Tobbie?
+
+```js
+'exports.pluginOptionsSchema = ({ Joi }) => {'
+exports.createSchemaCustomization = (gatsbyUtils) => {
+exports.sourceNodes = async (gatsbyUtils, pluginOptions) => {
+exports.onCreateNode = (gatsbyUtils) => {
+```
+
+
+## M. Is your last sub-task:
+
+### Must I `addRemoteFile........Interface(` to Tobbie?
+
+```js
+addRemoteFilePlayfillInterface(
+addRemoteFilePolyfillInterface(
+addRemoteFilePollyfilInterface(
+addRemoteFilePiratyInterface(
+```
+
+### Must we require ….
+
+```js
+"gatsby-plugin-tools/polyfill-remote-file"
+"gatsby-plugin-utils/polyfill-remote-file"
+"gatsby-utils/polyfill-remote-file"
+"gatsby-plugin-timeship/polyfill-remote-file"
+```
+## You DID it! 💪 😺 🏴☠️
+
+### You've done these sub-tasks:
+
+- G. A new GraphQL _ _ _ _ _ _ type
+
+- I. Inside which "exports...." do we call this code?
+
+- M. Must I `addRemoteFile........Interface(`?
+
+
+Ola Vea is a Gatsby-plugin-Dev who also helps Gatsby-Devs waste less time on unsticky dev-learning. Dev at POW! — the private menstrual cycle journal (usepow.app) 👑 @raae You can reach him at: ola@lillylabs
+
+source [docs/creating-a-source-plugin/#enabling-image-cdn-support](https://www.gatsbyjs.com/docs/how-to/plugins-and-themes/creating-a-source-plugin/#enabling-image-cdn-support)
+
diff --git a/plugins/gatsby-source-cloudinary/.gitignore b/plugins/gatsby-source-cloudinary/.gitignore
new file mode 100644
index 0000000..6da04de
--- /dev/null
+++ b/plugins/gatsby-source-cloudinary/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+config.js
\ No newline at end of file
diff --git a/plugins/gatsby-source-cloudinary/README.md b/plugins/gatsby-source-cloudinary/README.md
new file mode 100644
index 0000000..5ad3652
--- /dev/null
+++ b/plugins/gatsby-source-cloudinary/README.md
@@ -0,0 +1,182 @@
+# Gatsby-Source-Cloudinary
+
+This source plugin queries media files from a Cloudinary account into `cloudinaryMedia` nodes in your Gatsby project.
+
+[See a live demo here](https://gsc-sample.netlify.com/)
+
+[Here's a tutorial on plugin usage](https://scotch.io/tutorials/handling-images-in-gatsby-with-high-performance)
+
+## Motivation
+Gatsby offers the ability to develop high performance web pages with a rich developer experience and declarative data fetching Layer with GraphQL.
+Cloudinary provides a robust solution to manage media assets, from storage, optimized delivery, to media transformations. Extending the powers of Gatsby with the use of gatsby-source-cloudinary affords the best of both worlds, to allow users store media assets on Cloudinary,
+leveraging Cloudinary's powerful optimization and transformation capabilities in fast sites built with Gatsby.
+
+While Cloudinary images with on-the-fly transformations can be used during runtime in Gatsby, gatsby-source-cloudinary utilizes the build optimizations of Gatsby.
+
+## Features
+- Store media files on Cloudinary and deliver through a secure CDN to your Gatsby site
+- Average of over 60% image optimizations using `f_auto` and `q_auto` applied by default.
+- Query Cloudinary images in Gatsby's data layer using GraphQL.
+- Utilize Cloudinary's robust transformation suite in media files on a Gatsby site.
+- Manage media assets of an application completely on Cloudinary rather than directly in the codebase.
+
+Looking to use the features of Gatsby-Image with Cloudinary optimized storage, transformations and delivery? Checkout the [gatsby-transformer-cloudinary](https://www.npmjs.com/package/gatsby-transformer-cloudinary) plugin.
+
+## Example usage
+Here's a sample usage of the source plugin to create an image gallery from images stored on Cloudinary:
+
+```jsx harmony
+import React from 'react'
+import {useStaticQuery, graphql} from 'gatsby'
+
+const SingleImage = () => {
+ const data = useStaticQuery(graphql`
+ query CloudinaryImage {
+ cloudinaryMedia(public_id: {eq: "gatsby-source-cloudinary/11"}) {
+ secure_url
+ }
+ }
+ `
+ );
+ const clImage = data.cloudinaryMedia.secure_url;
+
+ return (
+
+
+
+
+
+ )
+ }
+```
+
+## Installation
+Install the source plugin using either `npm` or `yarm`:
+
+```bash
+npm install --save gatsby-source-cloudinary
+```
+
+### Cloudinary Credentials
+Cloudinary offers a generous free tier which is more than enough to bootstrap projects.
+Obtain your cloudname, key and secret from your cloudinary console when you signup at [Cloudinary.com](https://cloudinary.com).
+
+### Environment configuration
+Store your `cloudName`, `apiKey` and `apiSecret` as environment variables for security.
+To do this, create a file in the root of the project named `.env`. Add your environment variables in it with:
+
+```
+CLOUDINARY_API_KEY=INSERT API KEY HERE
+CLOUDINARY_API_SECRET=INSERT API SECRET HERE
+CLOUDINARY_CLOUD_NAME=INSERT CLOUDNAME HERE
+```
+
+Install `dotenv` in your project with:
+
+```
+yarn add dotenv
+```
+
+In your `gatsby-config.js` file, require and configure `dotenv` with:
+
+```
+require('dotenv').config();
+```
+
+There are several options to configuring `dotenv` to use different env files either in development or production. You can find that [here](https://www.npmjs.com/package/dotenv).
+
+Add the `.env` file to `.gitignore` so it's not committed.
+
+Ensure to configure the environment variables on deployment as well.
+
+### Plugin setup
+In your `gatsby-config.js` file, include the plugin like this:
+
+```js
+{
+ resolve:`gatsby-source-cloudinary`,
+ options: {
+ cloudName: process.env.CLOUDINARY_CLOUD_NAME,
+ apiKey: process.env.CLOUDINARY_API_KEY,
+ apiSecret: process.env.CLOUDINARY_API_SECRET,
+ resourceType: `image`,
+ type: `type Value`,
+ prefix: `abc-xyz/`
+ }
+}
+```
+
+
+
+### Plugin options
+You can find the plugin options in the table below.
+
+| option | type | required | default | description |
+|----------------|---------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `cloudName` | string | true | n/a | Cloud name of your Cloudinary account, can be obtained from your [Cloudinary console](https://cloudinary.com/console/). This should be stored and retrieved as an environment variable. |
+| `apiKey` | string | true | na/a | API Key of your Cloudinary account, can be obtained from your [Cloudinary console](https://cloudinary.com/console/). This should be stored and retrieved as an environment variable. |
+| `apiSecret` | string | true | n/a | API Secret of your Cloudinary account, can be obtained from your [Cloudinary console](https://cloudinary.com/console/). This should be stored and retrieved as an environment variable. |
+| `resourceType` | string | false | image | This is the type of file. Possible values: image, raw, video. Note: Use the video resource type for all video resources as well as for audio files, such as .mp3. |
+| `type` | string | false | all | This is the storage type: upload, private, authenticated, facebook, twitter, gplus, instagram_name, gravatar, youtube, hulu, vimeo, animoto, worldstarhiphop or dailymotion. |
+| `maxResults` | integer | false | 10 | Max number of resources to return |
+| `tags` | boolean | false | false | If true, include the list of tag names assigned to each resource |
+| `prefix` | string | false | n/a | Find all resources with a public ID that starts with the given prefix. The resources are sorted by public ID in the response. |
+| `context` | boolean | false | n/a | Specifies if the context data for the image should be returned. This is useful for retrieving `alt` text or custom metadata in key:value pairs for an image set on Cloudinary. |
+
+With `prefix`, you can source only media files from a specific folder. However, you will need to specify `type` and `resourceType` in the config options.
+
+An example `prefix` value is `gatsby-anime-videos/`. This will fetch only media files with public ids beginning with `gatsby-anime-videos/*`. Example: `gatsby-anime-videos/naruto.mp4`
+
+The `f_auto` and `q_auto` Cloudinary transformations are applied automatically to all media queries. This optimizes the delivered media quality and format.
+
+> All media files sourced from Cloudinary are done when Gatsby creates an optimized build, hence you will need to trigger a new production build whenever new media files are added directly on Cloudinary.
+
+## How to use
+Once a development server is started using `gatsby develop`, all media assets configured in the plugin are available as `cloudinaryMedia` and `allCloudinaryMedia` in graphQL.
+These can run in a Page Query or StaticQuery.
+
+```jsx harmony
+import React from 'react'
+import {useStaticQuery, graphql} from 'gatsby'
+
+
+const Images = () => {
+ const data = useStaticQuery(graphql`
+ query CloudinaryImages {
+ allCloudinaryMedia {
+ edges {
+ node {
+ secure_url
+ }
+ }
+ }
+ }
+ `
+ );
+ const clImages = data.allCloudinaryMedia.edges;
+
+ return (
+