Skip to content

Commit

Permalink
fix(gatsby): validate local plugin options schema (#29787)
Browse files Browse the repository at this point in the history
Co-authored-by: gatsbybot <[email protected]>
Co-authored-by: LekoArts <[email protected]>
  • Loading branch information
3 people authored Mar 24, 2021
1 parent 54d4721 commit 096eb38
Show file tree
Hide file tree
Showing 14 changed files with 267 additions and 6 deletions.
6 changes: 6 additions & 0 deletions integration-tests/structured-logging/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Structured Logging

Tests to ensure a couple of functionalities from our structured logging, including (local) plugins.

- Verifies IPC, logs, panic, status
- Verifies plugin errors with an errorMap
- Verifies plugin options. The tests will verify local plugin options schema validation by dropping file markers into the build folder with a flag that flips if the `pluginOptionsSchema` export is invoked.

## Problems

- Concurrent tests will need to generate a unique build folder name
142 changes: 142 additions & 0 deletions integration-tests/structured-logging/__tests__/validate-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
const { spawn } = require(`child_process`)
const path = require(`path`)

jest.setTimeout(100000)

const gatsbyBin = path.join(
`node_modules`,
`gatsby`,
`dist`,
`bin`,
`gatsby.js`
)

describe(`Validate Plugin Options`, () => {
let gatsbyProcess
let events = []

beforeEach(async () => {
gatsbyProcess = spawn(process.execPath, [gatsbyBin, `build`], {
// inherit lets us see logs in console
// stdio: [`inherit`, `inherit`, `inherit`, `ipc`],
stdio: [`ignore`, `ignore`, `ignore`, `ipc`],
env: {
...process.env,
NODE_ENV: `production`,
ENABLE_GATSBY_REFRESH_ENDPOINT: true,
VALIDATE_PLUGIN_OPTIONS: true,
},
})

await new Promise(resolve => {
gatsbyProcess.on(`message`, msg => {
events.push(msg)
})

gatsbyProcess.on(`exit`, exitCode => {
resolve()
})
})
})

it(`Errors on local plugins`, () => {
expect(events).toEqual(
expect.arrayContaining([
expect.objectContaining({
type: `LOG_ACTION`,
action: expect.objectContaining({
type: `SET_STATUS`,
payload: `FAILED`,
}),
}),
])
)

expect(events).toEqual(
expect.arrayContaining([
// Local plugin with require.resolve
expect.objectContaining({
type: `LOG_ACTION`,
action: expect.objectContaining({
type: `LOG`,
payload: expect.objectContaining({
level: `ERROR`,
category: `USER`,
context: expect.objectContaining({
pluginName: expect.stringContaining("integration-tests/structured-logging/local-plugin-with-path/index.js"),
validationErrors: expect.arrayContaining([
{
context: {
key: "required",
label: "required"
},
message: "\"required\" is required",
path: [
"required"
],
type: "any.required"
},
{
context: {
key: "optionalString",
label: "optionalString",
value: 1234
},
message: "\"optionalString\" must be a string",
path: [
"optionalString"
],
type: "string.base"
}
])
}),
code: `11331`,
type: `PLUGIN`,
})
})
}),
// Local plugin with name in gatsby-config
expect.objectContaining({
type: `LOG_ACTION`,
action: expect.objectContaining({
type: `LOG`,
payload: expect.objectContaining({
level: `ERROR`,
category: `USER`,
context: expect.objectContaining({
pluginName: "local-plugin",
validationErrors: expect.arrayContaining([
{
context: {
key: "required",
label: "required"
},
message: "\"required\" is required",
path: [
"required"
],
type: "any.required"
},
{
context: {
key: "optionalString",
label: "optionalString",
value: 1234
},
message: "\"optionalString\" must be a string",
path: [
"optionalString"
],
type: "string.base"
}
])
}),
code: `11331`,
type: `PLUGIN`,
})
})
})
])
)
})
})
24 changes: 23 additions & 1 deletion integration-tests/structured-logging/gatsby-config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
const dynamicPlugins = []

if (process.env.VALIDATE_PLUGIN_OPTIONS) {
dynamicPlugins.push(
{
resolve: "local-plugin",
options: {
optionalString: 1234,
},
},
{
resolve: require.resolve("./local-plugin-with-path"),
options: {
optionalString: 1234,
},
},
)
}

module.exports = {
plugins: ["structured-plugin-errors"],
plugins: [
"structured-plugin-errors",
...dynamicPlugins
],
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
exports.pluginOptionsSchema = ({ Joi }) => {
return Joi.object({
required: Joi.boolean().required(),
optionalString: Joi.string(),
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// no-op
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "local-plugin-with-path",
"private": true,
"version": "0.1.0",
"main": "index.js"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
exports.pluginOptionsSchema = ({ Joi }) => {
return Joi.object({
required: Joi.boolean().required(),
optionalString: Joi.string(),
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// no-op
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "local-plugin",
"private": true,
"version": "0.1.0",
"main": "index.js"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
exports.pluginOptionsSchema = ({ Joi }) => Joi.object({
required: Joi.boolean().required(),
optionalString: Joi.string()
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// no-op
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "local-plugin",
"private": true,
"version": "0.1.0",
"main": "index.js"
}
Original file line number Diff line number Diff line change
Expand Up @@ -401,5 +401,58 @@ describe(`Load plugins`, () => {
`)
expect(mockProcessExit).toHaveBeenCalledWith(1)
})

it(`validates local plugin schemas using require.resolve`, async () => {
await loadPlugins({
plugins: [
{
resolve: require.resolve(`./fixtures/local-plugin`),
options: {
optionalString: 1234,
},
},
],
})

expect(reporter.error as jest.Mock).toHaveBeenCalledTimes(1)
expect((reporter.error as jest.Mock).mock.calls[0])
.toMatchInlineSnapshot(`
Array [
Object {
"context": Object {
"configDir": null,
"pluginName": "<PROJECT_ROOT>/packages/gatsby/src/bootstrap/load-plugins/__tests__/fixtures/local-plugin/index.js",
"validationErrors": Array [
Object {
"context": Object {
"key": "required",
"label": "required",
},
"message": "\\"required\\" is required",
"path": Array [
"required",
],
"type": "any.required",
},
Object {
"context": Object {
"key": "optionalString",
"label": "optionalString",
"value": 1234,
},
"message": "\\"optionalString\\" must be a string",
"path": Array [
"optionalString",
],
"type": "string.base",
},
],
},
"id": "11331",
},
]
`)
expect(mockProcessExit).toHaveBeenCalledWith(1)
})
})
})
11 changes: 6 additions & 5 deletions packages/gatsby/src/bootstrap/load-plugins/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import * as stringSimilarity from "string-similarity"
import { version as gatsbyVersion } from "gatsby/package.json"
import reporter from "gatsby-cli/lib/reporter"
import { validateOptionsSchema, Joi } from "gatsby-plugin-utils"
import { IPluginRefObject } from "gatsby-plugin-utils/dist/types"
import { stripIndent } from "common-tags"
import { trackCli } from "gatsby-telemetry"
import { resolveModuleExports } from "../resolve-module-exports"
import { getLatestAPIs } from "../../utils/get-latest-apis"
import { GatsbyNode, PackageJson } from "../../../"
Expand All @@ -14,9 +17,7 @@ import {
IPluginInfoOptions,
ISiteConfig,
} from "./types"
import { IPluginRefObject } from "gatsby-plugin-utils/dist/types"
import { stripIndent } from "common-tags"
import { trackCli } from "gatsby-telemetry"
import { resolvePlugin } from "./load"

interface IApi {
version?: string
Expand Down Expand Up @@ -187,9 +188,9 @@ async function validatePluginsOptions(
const newPlugins = await Promise.all(
plugins.map(async plugin => {
let gatsbyNode

try {
gatsbyNode = require(`${plugin.resolve}/gatsby-node`)
const resolvedPlugin = resolvePlugin(plugin.resolve, rootDir)
gatsbyNode = require(`${resolvedPlugin.resolve}/gatsby-node`)
} catch (err) {
gatsbyNode = {}
}
Expand Down

0 comments on commit 096eb38

Please sign in to comment.