diff --git a/babel.config.js b/babel.config.js index 35587ea88a..f605871644 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,3 @@ const config = require('./src/babel.config'); -module.exports = config; +module.exports = (api) => config(api); diff --git a/config.d.ts b/config.d.ts new file mode 100644 index 0000000000..fe48d8883b --- /dev/null +++ b/config.d.ts @@ -0,0 +1 @@ +export * from './dist/config'; diff --git a/index.js b/index.js deleted file mode 100644 index 630449d6e6..0000000000 --- a/index.js +++ /dev/null @@ -1,3 +0,0 @@ -const payload = require('./src'); - -module.exports = payload; diff --git a/jest.config.js b/jest.config.js index 93b520103c..830329212f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -9,12 +9,4 @@ module.exports = { 'dist', ], testTimeout: 15000, - moduleNameMapper: { - '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/tests/mocks/fileMock.js', - '\\.(css|scss)$': '/tests/mocks/emptyModule.js', - }, - transform: { - '\\.(css|less|scss)$': './tests/stub-transformer.js', - '\\.(js|jsx|ts|tsx)$': 'babel-jest', - }, }; diff --git a/src/admin/components/providers/Config/Provider.tsx b/src/admin/components/providers/Config/Provider.tsx index ff02a7fadd..a667bf1783 100644 --- a/src/admin/components/providers/Config/Provider.tsx +++ b/src/admin/components/providers/Config/Provider.tsx @@ -1,19 +1,19 @@ import React from 'react'; import PropTypes from 'prop-types'; import unsanitizedConfig from 'payload/unsanitizedConfig'; -import sanitizeConfig from '../../../../utilities/sanitizeConfig'; +import sanitizeConfig from '../../../../config/sanitize'; import Context from './context'; const sanitizedConfig = sanitizeConfig(unsanitizedConfig); const ConfigProvider = ({ children }) => ( - - {children} - + + {children} + ); ConfigProvider.propTypes = { - children: PropTypes.node.isRequired, + children: PropTypes.node.isRequired, }; export default ConfigProvider; diff --git a/src/auth/auth.spec.ts b/src/auth/auth.spec.ts index b9771473af..e1dc515f76 100644 --- a/src/auth/auth.spec.ts +++ b/src/auth/auth.spec.ts @@ -1,5 +1,5 @@ import { MongoClient } from 'mongodb'; -import getConfig from '../utilities/getConfig'; +import getConfig from '../config/load'; import { email, password, mongo } from '../../tests/api/credentials'; require('isomorphic-fetch'); diff --git a/src/babel.config.js b/src/babel.config.js index 5df0b7f978..8bfd4822fc 100644 --- a/src/babel.config.js +++ b/src/babel.config.js @@ -1,27 +1,34 @@ -module.exports = { - presets: [ - '@babel/typescript', - [ - require.resolve('@babel/preset-env'), - { - targets: [ - 'defaults', - 'not IE 11', - 'not IE_Mob 11', - ], - }, +module.exports = (api) => { + const config = { + presets: [ + '@babel/typescript', + [ + require.resolve('@babel/preset-env'), + { + targets: [ + 'defaults', + 'not IE 11', + 'not IE_Mob 11', + ], + }, + ], + require.resolve('@babel/preset-react'), ], - require.resolve('@babel/preset-react'), - ], - plugins: [ - require.resolve('@babel/plugin-transform-runtime'), - require.resolve('@babel/plugin-proposal-class-properties'), - require.resolve('@babel/plugin-proposal-optional-chaining'), - [ + plugins: [ + require.resolve('@babel/plugin-transform-runtime'), + require.resolve('@babel/plugin-proposal-class-properties'), + require.resolve('@babel/plugin-proposal-optional-chaining'), + ], + }; + + if (api.env('test')) { + config.plugins.push([ 'babel-plugin-ignore-html-and-css-imports', { removeExtensions: ['.svg', '.css', '.scss', '.png', '.jpg'], }, - ], - ], + ]); + } + + return config; }; diff --git a/src/bin/build.js b/src/bin/build.js index 9212270990..df8e94ffcb 100755 --- a/src/bin/build.js +++ b/src/bin/build.js @@ -3,9 +3,9 @@ const webpack = require('webpack'); const getWebpackProdConfig = require('../webpack/getWebpackProdConfig'); -const findConfig = require('../utilities/findConfig'); -const getConfig = require('../utilities/getConfig'); -const sanitizeConfig = require('../utilities/sanitizeConfig'); +const findConfig = require('../config/find'); +const getConfig = require('../config/load'); +const sanitizeConfig = require('../config/sanitize'); const configPath = findConfig(); diff --git a/src/collections/config/build.ts b/src/collections/config/build.ts new file mode 100644 index 0000000000..afbf846a5a --- /dev/null +++ b/src/collections/config/build.ts @@ -0,0 +1,10 @@ +import { Collection } from './types'; +import sanitize from './sanitize'; + +const buildCollection = (collection: Collection) => { + const sanitized = sanitize(collection); + + return sanitized; +}; + +module.exports = buildCollection; diff --git a/src/collections/sanitize.ts b/src/collections/config/sanitize.ts similarity index 88% rename from src/collections/sanitize.ts rename to src/collections/config/sanitize.ts index e41e9f5fe9..e5eabd7b56 100644 --- a/src/collections/sanitize.ts +++ b/src/collections/config/sanitize.ts @@ -1,13 +1,13 @@ import merge from 'deepmerge'; -import sanitizeFields from '../fields/sanitize'; -import toKebabCase from '../utilities/toKebabCase'; -import baseAuthFields from '../fields/baseFields/baseFields'; -import baseAPIKeyFields from '../fields/baseFields/baseAPIKeyFields'; -import baseVerificationFields from '../fields/baseFields/baseVerificationFields'; -import baseAccountLockFields from '../fields/baseFields/baseAccountLockFields'; -import baseUploadFields from '../fields/baseFields/baseUploadFields'; -import baseImageUploadFields from '../fields/baseFields/baseImageUploadFields'; -import formatLabels from '../utilities/formatLabels'; +import sanitizeFields from '../../fields/config/sanitize'; +import toKebabCase from '../../utilities/toKebabCase'; +import baseAuthFields from '../../fields/baseFields/baseFields'; +import baseAPIKeyFields from '../../fields/baseFields/baseAPIKeyFields'; +import baseVerificationFields from '../../fields/baseFields/baseVerificationFields'; +import baseAccountLockFields from '../../fields/baseFields/baseAccountLockFields'; +import baseUploadFields from '../../fields/baseFields/baseUploadFields'; +import baseImageUploadFields from '../../fields/baseFields/baseImageUploadFields'; +import formatLabels from '../../utilities/formatLabels'; const mergeBaseFields = (fields, baseFields) => { const mergedFields = []; diff --git a/src/schema/collection.schema.json b/src/collections/config/schema.json similarity index 100% rename from src/schema/collection.schema.json rename to src/collections/config/schema.json diff --git a/src/collections/config/types.ts b/src/collections/config/types.ts new file mode 100644 index 0000000000..1044a44c91 --- /dev/null +++ b/src/collections/config/types.ts @@ -0,0 +1,47 @@ + +export type Collection = { + slug: string; + labels?: { + singular: string; + plural: string; + }; + admin?: { + useAsTitle?: string; + defaultColumns?: string[]; + components?: any; + }; + hooks?: { + beforeOperation?: Hook[]; + beforeValidate?: Hook[]; + beforeChange?: Hook[]; + afterChange?: Hook[]; + beforeRead?: Hook[]; + afterRead?: Hook[]; + beforeDelete?: Hook[]; + afterDelete?: Hook[]; + }; + access?: { + create?: Access; + read?: Access; + update?: Access; + delete?: Access; + admin?: Access; + }; + auth?: { + tokenExpiration?: number; + verify?: + | boolean + | { generateEmailHTML: string; generateEmailSubject: string }; + maxLoginAttempts?: number; + lockTime?: number; + useAPIKey?: boolean; + cookies?: + | { + secure?: boolean; + sameSite?: string; + domain?: string | undefined; + } + | boolean; + }; + fields: Field[]; +}; diff --git a/src/collections/graphql/resolvers/resolvers.spec.ts b/src/collections/graphql/resolvers/resolvers.spec.ts index 248cc442c4..fe9c8dd93b 100644 --- a/src/collections/graphql/resolvers/resolvers.spec.ts +++ b/src/collections/graphql/resolvers/resolvers.spec.ts @@ -2,7 +2,7 @@ * @jest-environment node */ import { request, GraphQLClient } from 'graphql-request'; -import getConfig from '../../../utilities/getConfig'; +import getConfig from '../../../config/load'; import { email, password } from '../../../../tests/api/credentials'; require('isomorphic-fetch'); diff --git a/src/collections/tests/collections.spec.ts b/src/collections/tests/collections.spec.ts index 76be086013..aaf3b8343b 100644 --- a/src/collections/tests/collections.spec.ts +++ b/src/collections/tests/collections.spec.ts @@ -1,5 +1,5 @@ import { v4 as uuid } from 'uuid'; -import getConfig from '../../utilities/getConfig'; +import getConfig from '../../config/load'; import { email, password } from '../../../tests/api/credentials'; require('isomorphic-fetch'); diff --git a/src/collections/tests/hooks.spec.ts b/src/collections/tests/hooks.spec.ts index 0d5ee07af3..2b92bf694c 100644 --- a/src/collections/tests/hooks.spec.ts +++ b/src/collections/tests/hooks.spec.ts @@ -1,4 +1,4 @@ -import getConfig from '../../utilities/getConfig'; +import getConfig from '../../config/load'; import { email, password } from '../../../tests/api/credentials'; require('isomorphic-fetch'); diff --git a/src/collections/tests/relationships.spec.ts b/src/collections/tests/relationships.spec.ts index 38d4f02554..d24c17267e 100644 --- a/src/collections/tests/relationships.spec.ts +++ b/src/collections/tests/relationships.spec.ts @@ -1,4 +1,4 @@ -import getConfig from '../../utilities/getConfig'; +import getConfig from '../../config/load'; import { email, password } from '../../../tests/api/credentials'; require('isomorphic-fetch'); diff --git a/src/collections/tests/uploads.spec.ts b/src/collections/tests/uploads.spec.ts index dce9459526..e9b3d52da8 100644 --- a/src/collections/tests/uploads.spec.ts +++ b/src/collections/tests/uploads.spec.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import path from 'path'; import FormData from 'form-data'; -import getConfig from '../../utilities/getConfig'; +import getConfig from '../../config/load'; import fileExists from '../../../tests/api/utils/fileExists'; import { email, password } from '../../../tests/api/credentials'; diff --git a/src/config/build.ts b/src/config/build.ts new file mode 100644 index 0000000000..b7af8bccde --- /dev/null +++ b/src/config/build.ts @@ -0,0 +1,12 @@ +import { PayloadConfig } from '../types'; +import sanitize from './sanitize'; +import validate from './validate'; + +const buildConfig = (config: PayloadConfig) => { + const validated = validate(config); + const sanitized = sanitize(validated); + + return sanitized; +}; + +module.exports = buildConfig; diff --git a/src/utilities/findConfig.ts b/src/config/find.ts similarity index 100% rename from src/utilities/findConfig.ts rename to src/config/find.ts diff --git a/src/utilities/getConfig.ts b/src/config/load.ts similarity index 81% rename from src/utilities/getConfig.ts rename to src/config/load.ts index 5cbbe7c46c..07bbc1a850 100644 --- a/src/utilities/getConfig.ts +++ b/src/config/load.ts @@ -1,10 +1,11 @@ /* eslint-disable import/no-dynamic-require */ /* eslint-disable global-require */ import path from 'path'; -import findConfig from './findConfig'; +import { Config } from './types'; +import findConfig from './find'; const configPath = findConfig(); -const getConfig = () => { +const getConfig = (): Config => { // eslint-disable-next-line @typescript-eslint/no-var-requires const publicConfig = require(configPath); return { diff --git a/src/utilities/sanitizeConfig.ts b/src/config/sanitize.ts similarity index 89% rename from src/utilities/sanitizeConfig.ts rename to src/config/sanitize.ts index 264c525567..b276d81292 100644 --- a/src/utilities/sanitizeConfig.ts +++ b/src/config/sanitize.ts @@ -1,13 +1,12 @@ -import { PayloadConfig } from '../types'; +import { Config } from './types'; import defaultUser from '../auth/default'; -import sanitizeCollection from '../collections/sanitize'; +import sanitizeCollection from '../collections/config/sanitize'; import { InvalidConfiguration } from '../errors'; -import sanitizeGlobals from '../globals/sanitize'; -import validateSchema from '../schema/validateSchema'; -import checkDuplicateCollections from './checkDuplicateCollections'; +import sanitizeGlobals from '../globals/config/sanitize'; +import checkDuplicateCollections from '../utilities/checkDuplicateCollections'; -const sanitizeConfig = (config: PayloadConfig) => { - const sanitizedConfig = validateSchema({ ...config }); +const sanitizeConfig = (config: Config): Config => { + const sanitizedConfig = { ...config }; // TODO: remove default values from sanitize in favor of assigning in the schema within validateSchema and use https://www.npmjs.com/package/ajv#coercing-data-types where needed if (sanitizedConfig.publicENV === undefined) sanitizedConfig.publicENV = {}; diff --git a/src/schema/payload.schema.json b/src/config/schema.json similarity index 100% rename from src/schema/payload.schema.json rename to src/config/schema.json diff --git a/src/config/types.ts b/src/config/types.ts new file mode 100644 index 0000000000..bcf5ad93c5 --- /dev/null +++ b/src/config/types.ts @@ -0,0 +1,107 @@ +import { Express } from 'express'; +import { CSSProperties } from 'react'; +import { Transporter } from 'nodemailer'; +import SMTPConnection from 'nodemailer/lib/smtp-connection'; +import { Collection } from '../collections/config/types'; + +type MockEmailTransport = { + transport?: 'mock'; + fromName?: string; + fromAddress?: string; +}; +type ValidEmailTransport = { + transport: Transporter; + transportOptions?: SMTPConnection.Options; + fromName: string; + fromAddress: string; +}; + +export type EmailOptions = ValidEmailTransport | MockEmailTransport; + +export type InitOptions = { + express?: Express; + mongoURL: string; + secret: string; + license?: string; + email?: EmailOptions; + local?: boolean; // I have no idea what this is + onInit?: () => void; +}; + +export type SendEmailOptions = { + from: string; + to: string; + subject: string; + html: string; +}; + +export type MockEmailCredentials = { + user: string; + pass: string; + web: string; +}; + +export type Hook = (...args: any[]) => any | void; +export type Access = (args?: any) => boolean; + +export type Config = { + admin?: { + user?: string + meta?: { + titleSuffix?: string + ogImage?: string + favicon?: string + } + disable?: boolean + }; + collections?: Collection[]; + globals?: Global[]; + serverURL?: string; + cookiePrefix?: string; + csrf?: string[]; + cors?: string[]; + publicENV: { [key: string]: string }; + routes?: { + api?: string; + admin?: string; + graphQL?: string; + graphQLPlayground?: string; + }; + email?: EmailOptions; + local?: boolean; + defaultDepth?: number; + maxDepth?: number; + rateLimit?: { + window?: number; + max?: number; + trustProxy?: boolean; + skip?: (req: Request) => boolean; // TODO: Type join Request w/ PayloadRequest + }; + upload?: { + limits?: { + fileSize?: number; + }; + }; + localization?: { + locales: string[]; + }; + defaultLocale?: string; + fallback?: boolean; + graphQL?: { + mutations?: { + [key: string]: unknown + }, + queries?: { + [key: string]: unknown + }, + maxComplexity?: number; + disablePlaygroundInProduction?: boolean; + }; + components: { [key: string]: JSX.Element | (() => JSX.Element) }; + paths?: { [key: string]: string }; + hooks?: { + afterError?: () => void; + }; + webpack?: (config: any) => any; + serverModules?: string[]; +}; diff --git a/src/schema/validateSchema.ts b/src/config/validate.ts similarity index 51% rename from src/schema/validateSchema.ts rename to src/config/validate.ts index 34931dfd10..9e30c4da83 100644 --- a/src/schema/validateSchema.ts +++ b/src/config/validate.ts @@ -1,15 +1,15 @@ import Ajv from 'ajv'; -import * as payloadSchema from './payload.schema.json'; -import * as collectionSchema from './collection.schema.json'; +import * as configSchema from './schema.json'; +import * as collectionSchema from '../collections/config/schema.json'; import InvalidSchema from '../errors/InvalidSchema'; -import { PayloadConfig } from '../types'; +import { PayloadConfig } from './types'; -const validateSchema = (config: PayloadConfig) => { +const validateSchema = (config: PayloadConfig): PayloadConfig => { const ajv = new Ajv({ useDefaults: true }); - const validate = ajv.addSchema(collectionSchema, '/collection.schema.json') - .compile(payloadSchema); + const validate = ajv.addSchema(collectionSchema, '../collections/config/schema.json').compile(configSchema); const valid = validate(config); + if (!valid) { throw new InvalidSchema(`Invalid payload config provided. Found ${validate.errors.length} errors`, validate.errors); } diff --git a/src/dev.js b/src/dev.js index 106767e99c..88c2de116a 100644 --- a/src/dev.js +++ b/src/dev.js @@ -1,12 +1,11 @@ -const babelConfig = require('./babel.config'); +const babelConfig = require('./babel.config')({ + env: () => false, +}); if (process.env.NODE_ENV !== 'test') { // eslint-disable-next-line global-require require('@babel/register')({ ...babelConfig, - ignore: [ - /node_modules[\\/](?!@payloadcms[\\/]payload[\\/]src[\\/]admin|@payloadcms[\\/]payload[\\/]components|@payloadcms[\\/]payload[\\/]hooks).*/, - ], extensions: ['.js', '.jsx', '.ts', '.tsx'], plugins: [ [ diff --git a/src/email/build.ts b/src/email/build.ts index b0737dc290..4c8f6ec3fa 100644 --- a/src/email/build.ts +++ b/src/email/build.ts @@ -1,11 +1,13 @@ import nodemailer, { Transporter } from 'nodemailer'; -import { PayloadEmailOptions } from '../types'; +import { EmailOptions } from '../config/types'; import { InvalidConfiguration } from '../errors'; import mockHandler from './mockHandler'; import Logger from '../utilities/logger'; +import { BuildEmailResult } from './types'; + const logger = Logger(); -export default async function buildEmail(emailConfig: PayloadEmailOptions) { +export default async function buildEmail(emailConfig: EmailOptions): BuildEmailResult { if (!emailConfig.transport || emailConfig.transport === 'mock') { const mockAccount = await mockHandler(emailConfig); // Only log mock credentials if was explicitly set in config @@ -38,8 +40,8 @@ export default async function buildEmail(emailConfig: PayloadEmailOptions) { email.transport = transport; } catch (err) { logger.error( - "There is an error with the email configuration you have provided.", - err + 'There is an error with the email configuration you have provided.', + err, ); } diff --git a/src/email/types.ts b/src/email/types.ts index e8090845d8..51177951f4 100644 --- a/src/email/types.ts +++ b/src/email/types.ts @@ -1,3 +1,12 @@ import { TestAccount, Transporter } from 'nodemailer'; +import Mail from 'nodemailer/lib/mailer'; +import SMTPConnection from 'nodemailer/lib/smtp-connection'; + +export type BuildEmailResult = Promise<{ + transport: Mail, + transportOptions?: SMTPConnection.Options, + fromName: string, + fromAddress: string, +}> export type MockEmailHandler = { account: TestAccount; transport: Transporter }; diff --git a/src/fields/sanitize.spec.ts b/src/fields/config/sanitize.spec.ts similarity index 97% rename from src/fields/sanitize.spec.ts rename to src/fields/config/sanitize.spec.ts index b1fe1f8166..acfe53e222 100644 --- a/src/fields/sanitize.spec.ts +++ b/src/fields/config/sanitize.spec.ts @@ -1,5 +1,5 @@ import sanitizeFields from './sanitize'; -import { MissingFieldType, InvalidFieldRelationship } from '../errors'; +import { MissingFieldType, InvalidFieldRelationship } from '../../errors'; describe('sanitizeFields', () => { it('should throw on missing type field', () => { diff --git a/src/fields/sanitize.ts b/src/fields/config/sanitize.ts similarity index 91% rename from src/fields/sanitize.ts rename to src/fields/config/sanitize.ts index 0f3c3390cf..83aa86f8b9 100644 --- a/src/fields/sanitize.ts +++ b/src/fields/config/sanitize.ts @@ -1,5 +1,5 @@ -import { MissingFieldType, InvalidFieldRelationship } from '../errors'; -import validations from './validations'; +import { MissingFieldType, InvalidFieldRelationship } from '../../errors'; +import validations from '../validations'; const sanitizeFields = (fields, validRelationships) => { if (!fields) return []; diff --git a/src/fields/config/types.ts b/src/fields/config/types.ts new file mode 100644 index 0000000000..9657f8256e --- /dev/null +++ b/src/fields/config/types.ts @@ -0,0 +1,30 @@ +import { CSSProperties } from 'react'; + +export type Field = { + name: string; + label: string; + type: + | 'number' + | 'text' + | 'email' + | 'textarea' + | 'richText' + | 'code' + | 'radio' + | 'checkbox' + | 'date' + | 'upload' + | 'relationship' + | 'row' + | 'array' + | 'group' + | 'select' + | 'blocks'; + localized?: boolean; + fields?: Field[]; + admin?: { + position?: string; + width?: string; + style?: CSSProperties; + }; +}; diff --git a/src/globals/config/build.ts b/src/globals/config/build.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/globals/sanitize.ts b/src/globals/config/sanitize.ts similarity index 93% rename from src/globals/sanitize.ts rename to src/globals/config/sanitize.ts index ba1c1f3819..619a68f48f 100644 --- a/src/globals/sanitize.ts +++ b/src/globals/config/sanitize.ts @@ -1,5 +1,5 @@ -import { MissingGlobalLabel } from '../errors'; -import sanitizeFields from '../fields/sanitize'; +import { MissingGlobalLabel } from '../../errors'; +import sanitizeFields from '../../fields/config/sanitize'; const sanitizeGlobals = (collections, globals) => { // ///////////////////////////////// diff --git a/src/globals/config/types.ts b/src/globals/config/types.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/globals/requestHandlers/globals.spec.js b/src/globals/requestHandlers/globals.spec.js index e293c145f6..a4e441505b 100644 --- a/src/globals/requestHandlers/globals.spec.js +++ b/src/globals/requestHandlers/globals.spec.js @@ -2,7 +2,7 @@ * @jest-environment node */ -import getConfig from '../../utilities/getConfig'; +import getConfig from '../../config/load'; import { email, password } from '../../../tests/api/credentials'; require('isomorphic-fetch'); diff --git a/src/index.ts b/src/index.ts index 3f361dc531..5575552022 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,15 @@ -import express from 'express'; +import express, { Express, Router } from 'express'; import crypto from 'crypto'; -import { Router } from 'express'; + +import { TestAccount } from 'nodemailer'; +import { + Config, + InitOptions, +} from './config/types'; +import { + Collection, +} from './collections/config/types'; import { - PayloadConfig, - PayloadCollection, - PayloadInitOptions, - PayloadLogger, CreateOptions, FindOptions, FindGlobalOptions, @@ -18,7 +22,7 @@ import Logger from './utilities/logger'; import bindOperations from './init/bindOperations'; import bindRequestHandlers from './init/bindRequestHandlers'; import bindResolvers from './init/bindResolvers'; -import getConfig from './utilities/getConfig'; +import loadConfig from './config/load'; import authenticate from './express/middleware/authenticate'; import connectMongoose from './mongoose/connect'; import expressMiddleware from './express/middleware'; @@ -29,7 +33,6 @@ import initGlobals from './globals/init'; import initGraphQLPlayground from './graphql/initPlayground'; import initStatic from './express/static'; import GraphQL from './graphql'; -import sanitizeConfig from './utilities/sanitizeConfig'; import buildEmail from './email/build'; import identifyAPI from './express/middleware/identifyAPI'; import errorHandler from './express/middleware/errorHandler'; @@ -37,26 +40,58 @@ import performFieldOperations from './fields/performFieldOperations'; import localOperations from './collections/operations/local'; import localGlobalOperations from './globals/operations/local'; import { encrypt, decrypt } from './auth/crypto'; -import { TestAccount } from 'nodemailer'; -import { MockEmailHandler } from './email/types'; +import { MockEmailHandler, BuildEmailResult } from './email/types'; -require('es6-promise').polyfill(); require('isomorphic-fetch'); class Payload { - config: PayloadConfig; - collections: PayloadCollection[] = []; - logger: PayloadLogger; - router: Router; - email: any; + config: Config; - init(options: PayloadInitOptions) { + collections: Collection[] = []; + + logger: typeof Logger; + + express: Express + + router: Router; + + emailOptions: any; + + email: BuildEmailResult; + + license: string; + + secret: string; + + mongoURL: string; + + local: boolean; + + initAuth: typeof initAuth; + + encrypt: typeof encrypt; + + decrypt: typeof decrypt; + + initCollections: typeof initCollections; + + initGlobals: typeof initGlobals; + + initGraphQLPlayground: typeof initGraphQLPlayground; + + initStatic: typeof initStatic; + + initAdmin: typeof initAdmin; + + performFieldOperations: typeof performFieldOperations; + + init(options: InitOptions) { this.logger = Logger(); this.logger.info('Starting Payload...'); if (!options.secret) { throw new Error( - 'Error: missing secret key. A secret key is needed to secure Payload.' + 'Error: missing secret key. A secret key is needed to secure Payload.', ); } @@ -64,21 +99,18 @@ class Payload { throw new Error('Error: missing MongoDB connection URL.'); } - const config = getConfig(options); - const email = { ...(config.email || {}), ...(options.email || {}) }; + this.license = options.license; + this.emailOptions = { ...(options.email || {}) }; + this.secret = crypto + .createHash('sha256') + .update(options.secret) + .digest('hex') + .slice(0, 32); - this.config = sanitizeConfig({ - ...config, - email, - license: options.license, - secret: crypto - .createHash('sha256') - .update(options.secret) - .digest('hex') - .slice(0, 32), - mongoURL: options.mongoURL, - local: options.local, - }); + this.mongoURL = options.mongoURL; + this.local = options.local; + + this.config = loadConfig(); if (typeof this.config.paths === 'undefined') this.config.paths = {}; @@ -121,14 +153,14 @@ class Payload { } // Configure email service - this.email = buildEmail(this.config.email); + this.email = buildEmail(this.emailOptions); // Initialize collections & globals this.initCollections(); this.initGlobals(); // Connect to database - connectMongoose(this.config.mongoURL); + connectMongoose(this.mongoURL); options.express.use((req, res, next) => { req.payload = this; @@ -138,8 +170,7 @@ class Payload { // If not initializing locally, set up HTTP routing if (!this.config.local) { this.express = options.express; - if (this.config.rateLimit && this.config.rateLimit.trustProxy) - this.express.set('trust proxy', 1); + if (this.config.rateLimit && this.config.rateLimit.trustProxy) { this.express.set('trust proxy', 1); } this.initAdmin(); @@ -150,7 +181,7 @@ class Payload { this.router.use( this.config.routes.graphQL, identifyAPI('GraphQL'), - (req, res) => graphQLHandler.init(req, res)(req, res) + (req, res) => graphQLHandler.init(req, res)(req, res), ); this.initGraphQLPlayground(); diff --git a/src/types/index.ts b/src/types/index.ts index e3b3b51639..4be30b8426 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,32 +1,3 @@ -// TODO: Split out init options from config types into own file - -import { Express, Request } from 'express'; -import { Transporter } from 'nodemailer'; -import SMTPConnection from 'nodemailer/lib/smtp-connection'; -import { Logger } from 'pino'; - -type MockEmailTransport = { - transport?: 'mock'; - fromName?: string; - fromAddress?: string; -}; -type ValidEmailTransport = { - transport: Transporter; - transportOptions?: SMTPConnection.Options; - fromName: string; - fromAddress: string; -}; -export type PayloadEmailOptions = ValidEmailTransport | MockEmailTransport; - -export type PayloadInitOptions = { - express?: Express; - mongoURL: string; - secret: string; - license?: string; - email?: PayloadEmailOptions; - local?: boolean; // I have no idea what this is - onInit?: () => void; -}; export type Document = { id: string; @@ -84,168 +55,3 @@ export type ForgotPasswordOptions = { expiration: Date; data: any; }; - -export type SendEmailOptions = { - from: string; - to: string; - subject: string; - html: string; -}; - -export type MockEmailCredentials = { - user: string; - pass: string; - web: string; -}; - -export type PayloadField = { - name: string; - label: string; - type: - | 'number' - | 'text' - | 'email' - | 'textarea' - | 'richText' - | 'code' - | 'radio' - | 'checkbox' - | 'date' - | 'upload' - | 'relationship' - | 'row' - | 'array' - | 'group' - | 'select' - | 'blocks'; - localized?: boolean; - fields?: PayloadField[]; - admin?: { - position?: string; - width?: string; - style?: Object; - }; -}; - -export type PayloadCollectionHook = (...args: any[]) => any | void; -export type PayloadAccess = (args?: any) => boolean; - -export type PayloadCollection = { - slug: string; - labels?: { - singular: string; - plural: string; - }; - admin?: { - useAsTitle?: string; - defaultColumns?: string[]; - components?: any; - }; - hooks?: { - beforeOperation?: PayloadCollectionHook[]; - beforeValidate?: PayloadCollectionHook[]; - beforeChange?: PayloadCollectionHook[]; - afterChange?: PayloadCollectionHook[]; - beforeRead?: PayloadCollectionHook[]; - afterRead?: PayloadCollectionHook[]; - beforeDelete?: PayloadCollectionHook[]; - afterDelete?: PayloadCollectionHook[]; - }; - access?: { - create?: PayloadAccess; - read?: PayloadAccess; - update?: PayloadAccess; - delete?: PayloadAccess; - admin?: PayloadAccess; - }; - auth?: { - tokenExpiration?: number; - verify?: - | boolean - | { generateEmailHTML: string; generateEmailSubject: string }; - maxLoginAttempts?: number; - lockTime?: number; - useAPIKey?: boolean; - cookies?: - | { - secure?: boolean; - sameSite?: string; - domain?: string | undefined; - } - | boolean; - }; - fields: PayloadField[]; -}; - -export type PayloadGlobal = { - slug: string; - label: string; - access?: { - create?: PayloadAccess; - read?: PayloadAccess; - update?: PayloadAccess; - delete?: PayloadAccess; - admin?: PayloadAccess; - }; - fields: PayloadField[]; -}; - -export type PayloadConfig = { - admin?: { - user?: string; - meta?: { - titleSuffix?: string; - ogImage?: string; - favicon?: string; - }; - disable?: boolean; - }; - collections?: PayloadCollection[]; - globals?: PayloadGlobal[]; - serverURL?: string; - cookiePrefix?: string; - csrf?: string[]; - cors?: string[]; - publicENV: { [key: string]: string }; - routes?: { - api?: string; - admin?: string; - graphQL?: string; - graphQLPlayground?: string; - }; - email?: PayloadEmailOptions; - local?: boolean; - defaultDepth?: number; - maxDepth?: number; - rateLimit?: { - window?: number; - max?: number; - trustProxy?: boolean; - skip?: (req: Request) => boolean; // TODO: Type join Request w/ PayloadRequest - }; - upload?: { - limits?: { - fileSize?: number; - }; - }; - localization?: { - locales: string[]; - }; - defaultLocale?: string; - fallback?: boolean; - graphQL?: { - mutations?: Object; - queries?: Object; - maxComplexity?: number; - disablePlaygroundInProduction?: boolean; - }; - components: { [key: string]: JSX.Element | (() => JSX.Element) }; - paths?: { [key: string]: string }; - hooks?: { - afterError?: () => void; - }; - webpack?: (config: any) => any; - serverModules?: string[]; -}; - -export type PayloadLogger = Logger; diff --git a/src/webpack/components.config.ts b/src/webpack/components.config.ts index ba0b34d630..d7b9bc2644 100644 --- a/src/webpack/components.config.ts +++ b/src/webpack/components.config.ts @@ -2,7 +2,6 @@ import path from 'path'; import MiniCSSExtractPlugin from 'mini-css-extract-plugin'; import TerserJSPlugin from 'terser-webpack-plugin'; import OptimizeCSSAssetsPlugin from 'optimize-css-assets-webpack-plugin'; -import babelConfig from '../babel.config'; export default { entry: { @@ -31,7 +30,6 @@ export default { exclude: /node_modules/, use: { loader: 'babel-loader', - options: babelConfig, }, }, { diff --git a/src/webpack/getStyleLoaders.ts b/src/webpack/getStyleLoaders.ts deleted file mode 100644 index 5da616fddb..0000000000 --- a/src/webpack/getStyleLoaders.ts +++ /dev/null @@ -1,28 +0,0 @@ -export default (cssOptions, preProcessor) => { - const loaders = [ - 'isomorphic-style-loader', - { - loader: require.resolve('css-loader'), - options: cssOptions, - }, - { - // Options for PostCSS as we reference these options twice - // Adds vendor prefixing based on your specified browser support in - // package.json - loader: require.resolve('postcss-loader'), - options: { - // Necessary for external CSS imports to work - // https://github.com/facebook/create-react-app/issues/2677 - ident: 'postcss', - plugins: () => [ - require('postcss-flexbugs-fixes'), - require('postcss-preset-env'), - ], - }, - }, - ]; - if (preProcessor) { - loaders.push(require.resolve(preProcessor)); - } - return loaders; -}; diff --git a/src/webpack/getWebpackDevConfig.ts b/src/webpack/getWebpackDevConfig.ts index 460f158e18..e5a36d2735 100644 --- a/src/webpack/getWebpackDevConfig.ts +++ b/src/webpack/getWebpackDevConfig.ts @@ -2,7 +2,6 @@ import HtmlWebpackPlugin from 'html-webpack-plugin'; import path from 'path'; import webpack from 'webpack'; import MiniCSSExtractPlugin from 'mini-css-extract-plugin'; -import babelConfig from '../babel.config'; const mockModulePath = path.resolve(__dirname, '../mocks/emptyModule.js'); @@ -10,19 +9,19 @@ export default (config) => { let webpackConfig = { entry: { main: [ - "webpack-hot-middleware/client", - path.resolve(__dirname, "../admin/index.tsx"), + 'webpack-hot-middleware/client', + path.resolve(__dirname, '../admin/index.tsx'), ], }, output: { - path: "/", + path: '/', publicPath: config.routes.admin, - filename: "[name].js", + filename: '[name].js', }, - devtool: "inline-cheap-source-map", - mode: "development", + devtool: 'inline-cheap-source-map', + mode: 'development', resolveLoader: { - modules: ["node_modules", path.join(__dirname, "../../node_modules")], + modules: ['node_modules', path.join(__dirname, '../../node_modules')], }, module: { rules: [ @@ -31,8 +30,7 @@ export default (config) => { exclude: /node_modules[\\/](?!(@payloadcms[\\/]payload)[\\/]).*/, use: [ { - loader: "babel-loader", - options: babelConfig, + loader: 'babel-loader', }, ], }, @@ -40,33 +38,33 @@ export default (config) => { oneOf: [ { test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], - loader: require.resolve("url-loader"), + loader: require.resolve('url-loader'), options: { limit: 10000, - name: "static/media/[name].[hash:8].[ext]", + name: 'static/media/[name].[hash:8].[ext]', }, }, { test: /\.(sa|sc|c)ss$/, use: [ MiniCSSExtractPlugin.loader, - "css-loader", + 'css-loader', { - loader: "postcss-loader", + loader: 'postcss-loader', options: { postcssOptions: { - plugins: ["postcss-preset-env"], + plugins: ['postcss-preset-env'], }, }, }, - "sass-loader", + 'sass-loader', ], }, { exclude: [/\.((t|j)s|(t|j)sx|mjs)$/, /\.html$/, /\.json$/], - loader: require.resolve("file-loader"), + loader: require.resolve('file-loader'), options: { - name: "static/media/[name].[hash:8].[ext]", + name: 'static/media/[name].[hash:8].[ext]', }, }, ], @@ -74,12 +72,12 @@ export default (config) => { ], }, resolve: { - modules: ["node_modules", path.resolve(__dirname, "../../node_modules")], + modules: ['node_modules', path.resolve(__dirname, '../../node_modules')], alias: { - "payload/unsanitizedConfig": config.paths.config, - "@payloadcms/payload$": mockModulePath, + 'payload/unsanitizedConfig': config.paths.config, + '@payloadcms/payload$': mockModulePath, }, - extensions: [".ts", ".tsx", ".js", ".json"], + extensions: ['.ts', '.tsx', '.js', '.json'], }, plugins: [ new webpack.DefinePlugin( @@ -88,15 +86,15 @@ export default (config) => { ...values, [`process.env.${key}`]: `'${val}'`, }), - {} - ) + {}, + ), ), new HtmlWebpackPlugin({ template: config.admin && config.admin.indexHTML ? path.join(config.paths.configDir, config.admin.indexHTML) - : path.resolve(__dirname, "../admin/index.html"), - filename: "./index.html", + : path.resolve(__dirname, '../admin/index.html'), + filename: './index.html', }), new webpack.HotModuleReplacementPlugin(), new MiniCSSExtractPlugin({ diff --git a/src/webpack/getWebpackProdConfig.ts b/src/webpack/getWebpackProdConfig.ts index 63213c32d0..a940833458 100644 --- a/src/webpack/getWebpackProdConfig.ts +++ b/src/webpack/getWebpackProdConfig.ts @@ -5,7 +5,6 @@ import OptimizeCSSAssetsPlugin from 'optimize-css-assets-webpack-plugin'; import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; import path from 'path'; import webpack from 'webpack'; -import babelConfig from '../babel.config'; const mockModulePath = path.resolve(__dirname, '../mocks/emptyModule.js'); @@ -44,7 +43,6 @@ export default (config) => { exclude: /node_modules[\\/](?!(@payloadcms[\\/]payload)[\\/]).*/, use: { loader: 'babel-loader', - options: babelConfig, }, }, {