Skip to content

Commit

Permalink
feat(gatsby): release plugin option validation (#27437)
Browse files Browse the repository at this point in the history
* chore(gatsby): add @gatsbyVersion pragma to pluginOptionsSchema Node API

This will make Gatsby prompt users on old versions of gatsby that don't support the API to upgrade to the new version.

* 2.25.0!

* Remove GATSBY_EXPERIMENTAL_PLUGIN_OPTION_VALIDATION feature flag

* Fix incorrect option in gatsby-admin;

* Fix tests

* Remove obsolete snapshots

* Trigger Build

* Remove flag from remark-autolink-headers

* Fix gatsby-plugin-mdx default
  • Loading branch information
mxstbr committed Nov 2, 2020
1 parent a33e7fb commit 41ae1c0
Show file tree
Hide file tree
Showing 24 changed files with 505 additions and 744 deletions.
1 change: 0 additions & 1 deletion .jestSetup.js
@@ -1,2 +1 @@
process.env.GATSBY_RECIPES_NO_COLOR = "true"
process.env.GATSBY_EXPERIMENTAL_PLUGIN_OPTION_VALIDATION = "true"
7 changes: 1 addition & 6 deletions packages/gatsby-admin/gatsby-config.js
@@ -1,11 +1,6 @@
module.exports = {
plugins: [
{
resolve: "gatsby-plugin-react-helmet",
options: {
test: false,
},
},
"gatsby-plugin-react-helmet",
{
resolve: "gatsby-plugin-webfonts",
options: {
Expand Down
Expand Up @@ -5,7 +5,3 @@ exports[`Test plugin feed custom properties work properly 1`] = `"<?xml version=
exports[`Test plugin feed custom query runs 1`] = `"<?xml version=\\"1.0\\" encoding=\\"UTF-8\\"?><rss xmlns:dc=\\"http://purl.org/dc/elements/1.1/\\" xmlns:content=\\"http://purl.org/rss/1.0/modules/content/\\" xmlns:atom=\\"http://www.w3.org/2005/Atom\\" version=\\"2.0\\"><channel><title><![CDATA[my feed]]></title><description><![CDATA[a description]]></description><link>http://github.com/dylang/node-rss</link><generator>GatsbyJS</generator><lastBuildDate>Mon, 01 Jan 2018 00:00:00 GMT</lastBuildDate><item><title><![CDATA[No title]]></title><description><![CDATA[post description]]></description><link>http://dummy.url/a-custom-path</link><guid isPermaLink=\\"true\\">http://dummy.url/a-custom-path</guid></item><item><title><![CDATA[No title]]></title><description><![CDATA[post description]]></description><link>http://dummy.url/another-custom-path</link><guid isPermaLink=\\"true\\">http://dummy.url/another-custom-path</guid></item></channel></rss>"`;
exports[`Test plugin feed default settings work properly 1`] = `"<?xml version=\\"1.0\\" encoding=\\"UTF-8\\"?><rss xmlns:dc=\\"http://purl.org/dc/elements/1.1/\\" xmlns:content=\\"http://purl.org/rss/1.0/modules/content/\\" xmlns:atom=\\"http://www.w3.org/2005/Atom\\" version=\\"2.0\\"><channel><title><![CDATA[a sample title]]></title><description><![CDATA[a description]]></description><link>http://github.com/dylang/node-rss</link><generator>GatsbyJS</generator><lastBuildDate>Mon, 01 Jan 2018 00:00:00 GMT</lastBuildDate><item><title><![CDATA[No title]]></title><description><![CDATA[post description]]></description><link>http://dummy.url/a-slug</link><guid isPermaLink=\\"false\\">http://dummy.url/a-slug</guid><content:encoded></content:encoded></item></channel></rss>"`;
exports[`Test plugin feed options validation throws when invalid plugin options 1`] = `[Error: [Config Validation]: "feeds[0].output" is required]`;
exports[`Test plugin feed options validation throws when invalid plugin options 2`] = `[Error: [Config Validation]: "feeds[0].query" is required]`;
104 changes: 1 addition & 103 deletions packages/gatsby-plugin-feed/src/__tests__/gatsby-node.js
@@ -1,7 +1,7 @@
jest.mock(`fs-extra`)
const fs = require(`fs-extra`)
const path = require(`path`)
const { onPreBootstrap, onPostBuild } = require(`../gatsby-node`)
const { onPostBuild } = require(`../gatsby-node`)
const DATE_TO_USE = new Date(`2018`)
const _Date = Date
global.Date = jest.fn(() => DATE_TO_USE)
Expand All @@ -15,108 +15,6 @@ describe(`Test plugin feed`, () => {
fs.mkdirp = jest.fn().mockResolvedValue()
})

describe(`options validation`, () => {
const setup = async options => {
const reporter = {
stripIndent: jest.fn(value => value.trim()),
warn: jest.fn(),
}
await onPreBootstrap({ reporter }, options)

return [reporter, options]
}

const deprecationNotice = `This behavior will be removed in the next major release of gatsby-plugin-feed`

it(`removes plugins`, async () => {
const options = { plugins: [] }

await setup(options)

expect(options.plugins).toBeUndefined()
})

it(`warns when feeds is not supplied`, async () => {
const options = {}

const [reporter] = await setup(options)

expect(reporter.warn).toHaveBeenCalledTimes(1)
expect(reporter.warn).toHaveBeenCalledWith(
expect.stringContaining(deprecationNotice)
)
})

it(`warns when individual feed does not have title`, async () => {
const options = {
feeds: [
{
output: `rss.xml`,
query: `{}`,
serialize: () => {},
},
],
}

const [reporter] = await setup(options)

expect(reporter.warn).toHaveBeenCalledTimes(1)
expect(reporter.warn).toHaveBeenCalledWith(
expect.stringContaining(`title`)
)
})

it(`warns when individual feed does not have serialize function`, async () => {
const options = {
feeds: [
{
output: `rss.xml`,
query: `{}`,
title: `my feed`,
},
],
}

const [reporter] = await setup(options)

expect(reporter.warn).toHaveBeenCalledTimes(1)
expect(reporter.warn).toHaveBeenCalledWith(
expect.stringContaining(deprecationNotice)
)
})

it(`throws when invalid plugin options`, async () => {
const invalidOptions = [
{
feeds: [
{
// output is missing
query: `{}`,
},
],
},
{
feeds: [
{
output: `rss.xml`,
// query is missing
},
],
},
]

for (let options of invalidOptions) {
try {
await setup(options)
} catch (e) {
expect(e).toMatchSnapshot()
}
}

expect.assertions(invalidOptions.length)
})
})

it(`default settings work properly`, async () => {
fs.writeFile = jest.fn()
fs.writeFile.mockResolvedValue(true)
Expand Down
61 changes: 1 addition & 60 deletions packages/gatsby-plugin-feed/src/gatsby-node.js
Expand Up @@ -2,23 +2,13 @@ import fs from "fs-extra"
import path from "path"
import RSS from "rss"
import merge from "lodash.merge"
import { Joi } from "gatsby-plugin-utils"

import { defaultOptions, runQuery } from "./internals"
import pluginOptionsSchema from "./plugin-options"

const publicPath = `./public`

const warnMessage = (error, behavior) => `
gatsby-plugin-feed was initialized in gatsby-config.js without a ${error}.
This means that the plugin will use ${behavior}, which may not match your use case.
This behavior will be removed in the next major release of gatsby-plugin-feed.
For more info, check out: https://gatsby.dev/adding-rss-feed
`

if (process.env.GATSBY_EXPERIMENTAL_PLUGIN_OPTION_VALIDATION) {
exports.pluginOptionsSchema = pluginOptionsSchema
}
exports.pluginOptionsSchema = pluginOptionsSchema

// TODO: remove in the next major release
// A default function to transform query data into feed entries.
Expand All @@ -33,55 +23,6 @@ const serialize = ({ query: { site, allMarkdownRemark } }) =>
}
})

exports.onPreBootstrap = async function onPreBootstrap(
{ reporter },
pluginOptions
) {
delete pluginOptions.plugins

try {
// TODO: remove this once pluginOptionsSchema is stable
const { value: normalized, error } = await pluginOptionsSchema({
Joi,
}).validate(pluginOptions, {
externals: false,
})

if (error) throw error

if (!normalized.feeds) {
reporter.warn(
reporter.stripIndent(
warnMessage(`feeds option`, `the internal RSS feed creation`)
)
)
} else if (normalized.feeds.some(feed => typeof feed.title !== `string`)) {
reporter.warn(
reporter.stripIndent(
warnMessage(`title in a feed`, `the default feed title`)
)
)
} else if (
normalized.feeds.some(feed => typeof feed.serialize !== `function`)
) {
reporter.warn(
reporter.stripIndent(
warnMessage(
`serialize function in a feed`,
`the internal serialize function`
)
)
)
}
} catch (e) {
throw new Error(
e.details
.map(detail => `[Config Validation]: ${detail.message}`)
.join(`\n`)
)
}
}

exports.onPostBuild = async ({ graphql }, pluginOptions) => {
/*
* Run the site settings query to gather context, then
Expand Down
82 changes: 36 additions & 46 deletions packages/gatsby-plugin-google-analytics/src/gatsby-node.js
@@ -1,48 +1,38 @@
if (process.env.GATSBY_EXPERIMENTAL_PLUGIN_OPTION_VALIDATION) {
exports.pluginOptionsSchema = ({ Joi }) =>
// TODO: make sure that trackingId gets required() when releasing a major version
Joi.object({
trackingId: Joi.string().description(
`The property ID; the tracking code won't be generated without it`
exports.pluginOptionsSchema = ({ Joi }) =>
// TODO: make sure that trackingId gets required() when releasing a major version
Joi.object({
trackingId: Joi.string().description(
`The property ID; the tracking code won't be generated without it`
),
head: Joi.boolean()
.default(false)
.description(
`Defines where to place the tracking script - \`true\` in the head and \`false\` in the body`
),
head: Joi.boolean()
.default(false)
.description(
`Defines where to place the tracking script - \`true\` in the head and \`false\` in the body`
),
anonymize: Joi.boolean().default(false),
respectDNT: Joi.boolean().default(false),
exclude: Joi.array()
.items(Joi.string())
.default([])
.description(`Avoids sending pageview hits from custom paths`),
pageTransitionDelay: Joi.number()
.default(0)
.description(
`Delays sending pageview hits on route update (in milliseconds)`
),
optimizeId: Joi.string().description(
`Enables Google Optimize using your container Id`
anonymize: Joi.boolean().default(false),
respectDNT: Joi.boolean().default(false),
exclude: Joi.array()
.items(Joi.string())
.default([])
.description(`Avoids sending pageview hits from custom paths`),
pageTransitionDelay: Joi.number()
.default(0)
.description(
`Delays sending pageview hits on route update (in milliseconds)`
),
experimentId: Joi.string().description(
`Enables Google Optimize Experiment ID`
),
variationId: Joi.string().description(
`Set Variation ID. 0 for original 1,2,3....`
),
defer: Joi.boolean().description(
`Defers execution of google analytics script after page load`
),
sampleRate: Joi.number(),
siteSpeedSampleRate: Joi.number(),
cookieDomain: Joi.string(),
})
} else {
exports.onPreInit = ({ reporter }, { trackingId } = {}) => {
if (!trackingId) {
reporter.warn(
`The Google Analytics plugin requires a tracking ID. Did you mean to add it?`
)
}
}
}
optimizeId: Joi.string().description(
`Enables Google Optimize using your container Id`
),
experimentId: Joi.string().description(
`Enables Google Optimize Experiment ID`
),
variationId: Joi.string().description(
`Set Variation ID. 0 for original 1,2,3....`
),
defer: Joi.boolean().description(
`Defers execution of google analytics script after page load`
),
sampleRate: Joi.number(),
siteSpeedSampleRate: Joi.number(),
cookieDomain: Joi.string(),
})
52 changes: 25 additions & 27 deletions packages/gatsby-plugin-google-tagmanager/src/gatsby-node.js
Expand Up @@ -12,33 +12,31 @@ exports.onPreInit = (args, options) => {
}
}

if (process.env.GATSBY_EXPERIMENTAL_PLUGIN_OPTION_VALIDATION) {
exports.pluginOptionsSchema = ({ Joi }) =>
Joi.object({
id: Joi.string().description(
`Google Tag Manager ID that can be found in your Tag Manager dashboard.`
exports.pluginOptionsSchema = ({ Joi }) =>
Joi.object({
id: Joi.string().description(
`Google Tag Manager ID that can be found in your Tag Manager dashboard.`
),
includeInDevelopment: Joi.boolean()
.default(false)
.description(
`Include Google Tag Manager when running in development mode.`
),
includeInDevelopment: Joi.boolean()
.default(false)
.description(
`Include Google Tag Manager when running in development mode.`
),
defaultDataLayer: Joi.object()
.default(null)
.description(
`Data layer to be set before Google Tag Manager is loaded. Should be an object or a function.`
),
gtmAuth: Joi.string().description(
`Google Tag Manager environment auth string.`
defaultDataLayer: Joi.object()
.default(null)
.description(
`Data layer to be set before Google Tag Manager is loaded. Should be an object or a function.`
),
gtmPreview: Joi.string().description(
`Google Tag Manager environment preview name.`
gtmAuth: Joi.string().description(
`Google Tag Manager environment auth string.`
),
gtmPreview: Joi.string().description(
`Google Tag Manager environment preview name.`
),
dataLayerName: Joi.string().description(`Data layer name.`),
routeChangeEventName: Joi.string()
.default(`gatsby-route-change`)
.description(
`Name of the event that is triggered on every Gatsby route change.`
),
dataLayerName: Joi.string().description(`Data layer name.`),
routeChangeEventName: Joi.string()
.default(`gatsby-route-change`)
.description(
`Name of the event that is triggered on every Gatsby route change.`
),
})
}
})

0 comments on commit 41ae1c0

Please sign in to comment.