diff --git a/packages/gatsby-cli/src/structured-errors/__tests__/error-schema.ts b/packages/gatsby-cli/src/structured-errors/__tests__/error-schema.ts index d49787a1dc45a..a01e785f294b5 100644 --- a/packages/gatsby-cli/src/structured-errors/__tests__/error-schema.ts +++ b/packages/gatsby-cli/src/structured-errors/__tests__/error-schema.ts @@ -1,12 +1,12 @@ -import schema from "../error-schema" +import { errorSchema } from "../error-schema" test(`throws invalid on an invalid error`, () => { - expect(schema.validate({ lol: `true` })).rejects.toBeDefined() + expect(errorSchema.validate({ lol: `true` })).rejects.toBeDefined() }) test(`does not throw on a valid schema`, () => { expect( - schema.validate({ + errorSchema.validate({ context: {}, }) ).resolves.toEqual(expect.any(Object)) diff --git a/packages/gatsby-cli/src/structured-errors/construct-error.ts b/packages/gatsby-cli/src/structured-errors/construct-error.ts index 20f1db865b959..a53249b47c5fc 100644 --- a/packages/gatsby-cli/src/structured-errors/construct-error.ts +++ b/packages/gatsby-cli/src/structured-errors/construct-error.ts @@ -1,44 +1,8 @@ -import Joi from "@hapi/joi" import stackTrace from "stack-trace" -import errorSchema from "./error-schema" -import { errorMap, defaultError, IErrorMapEntry, ErrorId } from "./error-map" +import { errorSchema } from "./error-schema" +import { errorMap, defaultError, IErrorMapEntry } from "./error-map" import { sanitizeStructuredStackTrace } from "../reporter/errors" - -interface IConstructError { - details: { - id?: ErrorId - context?: Record - error?: Error - [key: string]: unknown - } -} - -interface ILocationPosition { - line: number - column: number -} - -interface IStructuredError { - code?: string - text: string - stack: { - fileName: string - functionName?: string - lineNumber?: number - columnNumber?: number - }[] - filePath?: string - location?: { - start: ILocationPosition - end?: ILocationPosition - } - error?: unknown - group?: string - level: IErrorMapEntry["level"] - type?: IErrorMapEntry["type"] - docsUrl?: string -} - +import { IConstructError, IStructuredError } from "./types" // Merge partial error details with information from the errorMap // Validate the constructed object against an error schema const constructError = ({ @@ -63,7 +27,7 @@ const constructError = ({ } // validate - const { error } = Joi.validate(structuredError, errorSchema) + const { error } = errorSchema.validate(structuredError) if (error !== null) { console.log(`Failed to validate error`, error) process.exit(1) diff --git a/packages/gatsby-cli/src/structured-errors/error-map.ts b/packages/gatsby-cli/src/structured-errors/error-map.ts index bc6c917ee7555..a3a56cc07a934 100644 --- a/packages/gatsby-cli/src/structured-errors/error-map.ts +++ b/packages/gatsby-cli/src/structured-errors/error-map.ts @@ -1,25 +1,5 @@ import { stripIndent, stripIndents } from "common-tags" - -interface IOptionalGraphQLInfoContext { - codeFrame?: string - filePath?: string - urlPath?: string - plugin?: string -} - -enum Level { - ERROR = `ERROR`, - WARNING = `WARNING`, - INFO = `INFO`, - DEBUG = `DEBUG`, -} - -enum Type { - GRAPHQL = `GRAPHQL`, - CONFIG = `CONFIG`, - WEBPACK = `WEBPACK`, - PLUGIN = `PLUGIN`, -} +import { IOptionalGraphQLInfoContext, Level, Type } from "./types" const optionalGraphQLInfo = (context: IOptionalGraphQLInfoContext): string => `${context.codeFrame ? `\n\n${context.codeFrame}` : ``}${ diff --git a/packages/gatsby-cli/src/structured-errors/error-schema.ts b/packages/gatsby-cli/src/structured-errors/error-schema.ts index a5de534971e91..ec14cfd6f65be 100644 --- a/packages/gatsby-cli/src/structured-errors/error-schema.ts +++ b/packages/gatsby-cli/src/structured-errors/error-schema.ts @@ -1,38 +1,39 @@ import Joi from "@hapi/joi" +import { ILocationPosition, IStructuredError } from "./types" -const Position = Joi.object().keys({ +export const Position: Joi.ObjectSchema = Joi.object().keys({ line: Joi.number(), column: Joi.number(), }) -const errorSchema = Joi.object().keys({ - code: Joi.string(), - text: Joi.string(), - stack: Joi.array() - .items( - Joi.object().keys({ - fileName: Joi.string(), - functionName: Joi.string().allow(null), - lineNumber: Joi.number().allow(null), - columnNumber: Joi.number().allow(null), - }) - ) - .allow(null), - level: Joi.string().valid([`ERROR`, `WARNING`, `INFO`, `DEBUG`]), - type: Joi.string().valid([`GRAPHQL`, `CONFIG`, `WEBPACK`, `PLUGIN`]), - filePath: Joi.string(), - location: Joi.object({ - start: Position.required(), - end: Position, - }), - docsUrl: Joi.string().uri({ - allowRelative: false, - relativeOnly: false, - }), - error: Joi.object({}).unknown(), - context: Joi.object({}).unknown(), - group: Joi.string(), - panicOnBuild: Joi.boolean(), -}) - -export default errorSchema +export const errorSchema: Joi.ObjectSchema = Joi.object().keys( + { + code: Joi.string(), + text: Joi.string(), + stack: Joi.array() + .items( + Joi.object().keys({ + fileName: Joi.string(), + functionName: Joi.string().allow(null), + lineNumber: Joi.number().allow(null), + columnNumber: Joi.number().allow(null), + }) + ) + .allow(null), + level: Joi.string().valid([`ERROR`, `WARNING`, `INFO`, `DEBUG`]), + type: Joi.string().valid([`GRAPHQL`, `CONFIG`, `WEBPACK`, `PLUGIN`]), + filePath: Joi.string(), + location: Joi.object({ + start: Position.required(), + end: Position, + }), + docsUrl: Joi.string().uri({ + allowRelative: false, + relativeOnly: false, + }), + error: Joi.object({}).unknown(), + context: Joi.object({}).unknown(), + group: Joi.string(), + panicOnBuild: Joi.boolean(), + } +) diff --git a/packages/gatsby-cli/src/structured-errors/types.ts b/packages/gatsby-cli/src/structured-errors/types.ts new file mode 100644 index 0000000000000..428620550433d --- /dev/null +++ b/packages/gatsby-cli/src/structured-errors/types.ts @@ -0,0 +1,57 @@ +import { IErrorMapEntry, ErrorId } from "./error-map" + +export interface IConstructError { + details: { + id?: ErrorId + context?: Record + error?: Error + [key: string]: unknown + } +} + +export interface ILocationPosition { + line: number + column: number +} + +export interface IStructuredError { + code?: string + text: string + stack: { + fileName: string + functionName?: string + lineNumber?: number + columnNumber?: number + }[] + filePath?: string + location?: { + start: ILocationPosition + end?: ILocationPosition + } + error?: unknown + group?: string + level: IErrorMapEntry["level"] + type?: IErrorMapEntry["type"] + docsUrl?: string +} + +export interface IOptionalGraphQLInfoContext { + codeFrame?: string + filePath?: string + urlPath?: string + plugin?: string +} + +export enum Level { + ERROR = `ERROR`, + WARNING = `WARNING`, + INFO = `INFO`, + DEBUG = `DEBUG`, +} + +export enum Type { + GRAPHQL = `GRAPHQL`, + CONFIG = `CONFIG`, + WEBPACK = `WEBPACK`, + PLUGIN = `PLUGIN`, +} diff --git a/packages/gatsby/index.d.ts b/packages/gatsby/index.d.ts index ffcdbdde7478b..020f0c8fa9a50 100644 --- a/packages/gatsby/index.d.ts +++ b/packages/gatsby/index.d.ts @@ -176,6 +176,8 @@ export interface GatsbyConfig { > /** It’s common for sites to be hosted somewhere other than the root of their domain. Say we have a Gatsby site at `example.com/blog/`. In this case, we would need a prefix (`/blog`) added to all paths on the site. */ pathPrefix?: string + /** In some circumstances you may want to deploy assets (non-HTML resources such as JavaScript, CSS, etc.) to a separate domain. `assetPrefix` allows you to use Gatsby with assets hosted from a separate domain */ + assetPrefix?: string /** Gatsby uses the ES6 Promise API. Because some browsers don't support this, Gatsby includes a Promise polyfill by default. If you'd like to provide your own Promise polyfill, you can set `polyfill` to false.*/ polyfill?: boolean mapping?: Record diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index 5f7df108d504e..28365f1f23c85 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -150,6 +150,7 @@ "devDependencies": { "@babel/cli": "^7.8.4", "@babel/runtime": "^7.8.7", + "@types/hapi__joi": "^16.0.12", "@types/socket.io": "^2.1.4", "babel-preset-gatsby-package": "^0.3.1", "cross-env": "^5.2.1", diff --git a/packages/gatsby/src/joi-schemas/__tests__/__snapshots__/joi.js.snap b/packages/gatsby/src/joi-schemas/__tests__/__snapshots__/joi.ts.snap similarity index 100% rename from packages/gatsby/src/joi-schemas/__tests__/__snapshots__/joi.js.snap rename to packages/gatsby/src/joi-schemas/__tests__/__snapshots__/joi.ts.snap diff --git a/packages/gatsby/src/joi-schemas/__tests__/joi.js b/packages/gatsby/src/joi-schemas/__tests__/joi.ts similarity index 92% rename from packages/gatsby/src/joi-schemas/__tests__/joi.js rename to packages/gatsby/src/joi-schemas/__tests__/joi.ts index c4588d6502f67..a2480b7c1e009 100644 --- a/packages/gatsby/src/joi-schemas/__tests__/joi.js +++ b/packages/gatsby/src/joi-schemas/__tests__/joi.ts @@ -1,4 +1,4 @@ -const { gatsbyConfigSchema, nodeSchema } = require(`../joi`) +import { gatsbyConfigSchema, nodeSchema } from "../joi" describe(`gatsby config`, () => { it(`returns empty pathPrefix when not set`, async () => { @@ -133,7 +133,8 @@ describe(`node schema`, () => { const { error } = nodeSchema.validate(node) expect(error).toBeTruthy() - expect(error.message).toMatchSnapshot() + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expect(error!.message).toMatchSnapshot() }) it(`doesn't allow unknown internal fields`, async () => { @@ -152,6 +153,7 @@ describe(`node schema`, () => { const { error } = nodeSchema.validate(node) expect(error).toBeTruthy() - expect(error.message).toMatchSnapshot() + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expect(error!.message).toMatchSnapshot() }) }) diff --git a/packages/gatsby/src/joi-schemas/joi.js b/packages/gatsby/src/joi-schemas/joi.ts similarity index 83% rename from packages/gatsby/src/joi-schemas/joi.js rename to packages/gatsby/src/joi-schemas/joi.ts index d211184251ee8..9f626f19dfb42 100644 --- a/packages/gatsby/src/joi-schemas/joi.js +++ b/packages/gatsby/src/joi-schemas/joi.ts @@ -1,13 +1,16 @@ -const Joi = require(`@hapi/joi`) +import Joi from "@hapi/joi" +import { IGatsbyConfig, IGatsbyPage, IGatsbyNode } from "../redux/types" + +const stripTrailingSlash = (chain: Joi.StringSchema): Joi.StringSchema => + chain.replace(/(\w)\/+$/, `$1`) -const stripTrailingSlash = chain => chain.replace(/(\w)\/+$/, `$1`) // only add leading slash on relative urls -const addLeadingSlash = chain => +const addLeadingSlash = (chain: Joi.StringSchema): Joi.StringSchema => chain.when(Joi.string().uri({ relativeOnly: true }), { then: chain.replace(/^([^/])/, `/$1`), }) -export const gatsbyConfigSchema = Joi.object() +export const gatsbyConfigSchema: Joi.ObjectSchema = Joi.object() .keys({ __experimentalThemes: Joi.array(), polyfill: Joi.boolean().default(true), @@ -73,7 +76,7 @@ export const gatsbyConfigSchema = Joi.object() } ) -export const pageSchema = Joi.object() +export const pageSchema: Joi.ObjectSchema = Joi.object() .keys({ path: Joi.string().required(), matchPath: Joi.string(), @@ -85,7 +88,7 @@ export const pageSchema = Joi.object() }) .unknown() -export const nodeSchema = Joi.object() +export const nodeSchema: Joi.ObjectSchema = Joi.object() .keys({ id: Joi.string().required(), children: Joi.array().items(Joi.string(), Joi.object().forbidden()), diff --git a/packages/gatsby/src/redux/actions/public.js b/packages/gatsby/src/redux/actions/public.js index 5a6db72d66266..ec5061d2170bb 100644 --- a/packages/gatsby/src/redux/actions/public.js +++ b/packages/gatsby/src/redux/actions/public.js @@ -14,7 +14,7 @@ const { hasNodeChanged, getNode } = require(`../../db/nodes`) const sanitizeNode = require(`../../db/sanitize-node`) const { store } = require(`..`) const fileExistsSync = require(`fs-exists-cached`).sync -const joiSchemas = require(`../../joi-schemas/joi`) +import { nodeSchema } from "../../joi-schemas/joi" const { generateComponentChunkName } = require(`../../utils/js-chunk-names`) const { getCommonDir, @@ -744,7 +744,7 @@ const createNode = ( trackCli(`CREATE_NODE`, trackParams, { debounce: true }) - const result = Joi.validate(node, joiSchemas.nodeSchema) + const result = Joi.validate(node, nodeSchema) if (result.error) { if (!hasErroredBecauseOfNodeValidation.has(result.error.message)) { const errorObj = { diff --git a/yarn.lock b/yarn.lock index 15423b1e043c8..2409b681cd75c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3927,6 +3927,11 @@ "@types/tough-cookie" "*" form-data "^2.5.0" +"@types/hapi__joi@^16.0.12": + version "16.0.12" + resolved "https://registry.yarnpkg.com/@types/hapi__joi/-/hapi__joi-16.0.12.tgz#fb9113f17cf5764d6b3586ae9817d1606cc7c90c" + integrity sha512-xJYifuz59jXdWY5JMS15uvA3ycS3nQYOGqoIIE0+fwQ0qI3/4CxBc6RHsOTp6wk9M0NWEdpcTl02lOQOKMifbQ== + "@types/history@*": version "4.7.0" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.0.tgz#2fac51050c68f7d6f96c5aafc631132522f4aa3f"