From 90e9dd7f47ac78e22352081aceed939c694ab29d Mon Sep 17 00:00:00 2001 From: Jessica Boezwinkle Date: Fri, 16 Jun 2023 11:58:47 +0100 Subject: [PATCH] setup: builds plugin and demo --- packages/plugin-sentry/.editorconfig | 10 ++++ packages/plugin-sentry/.eslintrc.js | 3 + packages/plugin-sentry/.gitignore | 6 ++ packages/plugin-sentry/.prettierrc.js | 8 +++ packages/plugin-sentry/dev/.gitignore | 6 ++ packages/plugin-sentry/dev/README.md | 7 +++ packages/plugin-sentry/dev/nodemon.json | 4 ++ packages/plugin-sentry/dev/package.json | 33 ++++++++++ .../dev/src/collections/Users.ts | 18 ++++++ .../plugin-sentry/dev/src/payload.config.ts | 25 ++++++++ packages/plugin-sentry/dev/src/server.ts | 27 +++++++++ packages/plugin-sentry/dev/tsconfig.json | 20 +++++++ packages/plugin-sentry/package.json | 60 +++++++++++++++++++ .../plugin-sentry/src/captureException.ts | 5 ++ packages/plugin-sentry/src/index.ts | 38 ++++++++++++ packages/plugin-sentry/src/mocks/mockFile.js | 4 ++ packages/plugin-sentry/src/startSentry.ts | 48 +++++++++++++++ packages/plugin-sentry/src/types.ts | 6 ++ packages/plugin-sentry/src/webpack.ts | 28 +++++++++ packages/plugin-sentry/tsconfig.json | 18 ++++++ 20 files changed, 374 insertions(+) create mode 100644 packages/plugin-sentry/.editorconfig create mode 100644 packages/plugin-sentry/.eslintrc.js create mode 100644 packages/plugin-sentry/.gitignore create mode 100644 packages/plugin-sentry/.prettierrc.js create mode 100644 packages/plugin-sentry/dev/.gitignore create mode 100644 packages/plugin-sentry/dev/README.md create mode 100644 packages/plugin-sentry/dev/nodemon.json create mode 100644 packages/plugin-sentry/dev/package.json create mode 100644 packages/plugin-sentry/dev/src/collections/Users.ts create mode 100644 packages/plugin-sentry/dev/src/payload.config.ts create mode 100644 packages/plugin-sentry/dev/src/server.ts create mode 100644 packages/plugin-sentry/dev/tsconfig.json create mode 100644 packages/plugin-sentry/package.json create mode 100644 packages/plugin-sentry/src/captureException.ts create mode 100644 packages/plugin-sentry/src/index.ts create mode 100644 packages/plugin-sentry/src/mocks/mockFile.js create mode 100644 packages/plugin-sentry/src/startSentry.ts create mode 100644 packages/plugin-sentry/src/types.ts create mode 100644 packages/plugin-sentry/src/webpack.ts create mode 100644 packages/plugin-sentry/tsconfig.json diff --git a/packages/plugin-sentry/.editorconfig b/packages/plugin-sentry/.editorconfig new file mode 100644 index 000000000..d8e085abc --- /dev/null +++ b/packages/plugin-sentry/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +end_of_line = lf +max_line_length = null diff --git a/packages/plugin-sentry/.eslintrc.js b/packages/plugin-sentry/.eslintrc.js new file mode 100644 index 000000000..8713f59e0 --- /dev/null +++ b/packages/plugin-sentry/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['@payloadcms'], +} diff --git a/packages/plugin-sentry/.gitignore b/packages/plugin-sentry/.gitignore new file mode 100644 index 000000000..479ccff27 --- /dev/null +++ b/packages/plugin-sentry/.gitignore @@ -0,0 +1,6 @@ +node_modules +.env +dist +build +.DS_Store +package-lock.json diff --git a/packages/plugin-sentry/.prettierrc.js b/packages/plugin-sentry/.prettierrc.js new file mode 100644 index 000000000..70c17c995 --- /dev/null +++ b/packages/plugin-sentry/.prettierrc.js @@ -0,0 +1,8 @@ +module.exports = { + printWidth: 100, + parser: "typescript", + semi: false, + singleQuote: true, + trailingComma: "all", + arrowParens: "avoid", +}; diff --git a/packages/plugin-sentry/dev/.gitignore b/packages/plugin-sentry/dev/.gitignore new file mode 100644 index 000000000..479ccff27 --- /dev/null +++ b/packages/plugin-sentry/dev/.gitignore @@ -0,0 +1,6 @@ +node_modules +.env +dist +build +.DS_Store +package-lock.json diff --git a/packages/plugin-sentry/dev/README.md b/packages/plugin-sentry/dev/README.md new file mode 100644 index 000000000..27723a982 --- /dev/null +++ b/packages/plugin-sentry/dev/README.md @@ -0,0 +1,7 @@ +# Sentry Plugin Demo + +This project was created using create-payload-app using the blank template. + +## How to Use + +`yarn dev` will start up your application and reload on any changes. diff --git a/packages/plugin-sentry/dev/nodemon.json b/packages/plugin-sentry/dev/nodemon.json new file mode 100644 index 000000000..ed1a1850d --- /dev/null +++ b/packages/plugin-sentry/dev/nodemon.json @@ -0,0 +1,4 @@ +{ + "ext": "ts", + "exec": "ts-node src/server.ts" +} diff --git a/packages/plugin-sentry/dev/package.json b/packages/plugin-sentry/dev/package.json new file mode 100644 index 000000000..08b20a075 --- /dev/null +++ b/packages/plugin-sentry/dev/package.json @@ -0,0 +1,33 @@ +{ + "name": "plugin-sentry-demo", + "description": "Payload project to demonstrate how to use the sentry plugin", + "version": "1.0.0", + "main": "dist/server.js", + "license": "MIT", + "scripts": { + "dev": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon", + "build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload build", + "build:server": "tsc", + "build": "yarn copyfiles && yarn build:payload && yarn build:server", + "serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js", + "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/", + "generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types", + "generate:graphQLSchema": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema" + }, + "dependencies": { + "@sentry/node": "^7.55.2", + "@sentry/react": "^7.55.2", + "dotenv": "^8.2.0", + "eslint": "^8.42.0", + "express": "^4.17.1", + "payload": "^1.9.2" + }, + "devDependencies": { + "@types/express": "^4.17.9", + "copyfiles": "^2.4.1", + "cross-env": "^7.0.3", + "nodemon": "^2.0.6", + "ts-node": "^9.1.1", + "typescript": "^4.8.4" + } +} diff --git a/packages/plugin-sentry/dev/src/collections/Users.ts b/packages/plugin-sentry/dev/src/collections/Users.ts new file mode 100644 index 000000000..3a635d139 --- /dev/null +++ b/packages/plugin-sentry/dev/src/collections/Users.ts @@ -0,0 +1,18 @@ +import type { CollectionConfig } from 'payload/types' + +const Users: CollectionConfig = { + slug: 'users', + auth: true, + admin: { + useAsTitle: 'email', + }, + access: { + read: () => true, + }, + fields: [ + // Email added by default + // Add more fields as needed + ], +} + +export default Users diff --git a/packages/plugin-sentry/dev/src/payload.config.ts b/packages/plugin-sentry/dev/src/payload.config.ts new file mode 100644 index 000000000..5272c2f15 --- /dev/null +++ b/packages/plugin-sentry/dev/src/payload.config.ts @@ -0,0 +1,25 @@ +/* eslint-disable import/no-relative-packages */ +import path from 'path' +import { buildConfig } from 'payload/config' + +import { sentry } from '../../src/index' +import Users from './collections/Users' + +export default buildConfig({ + serverURL: 'http://localhost:3000', + admin: { + user: Users.slug, + }, + collections: [Users], + typescript: { + outputFile: path.resolve(__dirname, 'payload-types.ts'), + }, + graphQL: { + schemaOutputFile: path.resolve(__dirname, 'generated-schema.graphql'), + }, + plugins: [ + sentry({ + dsn: 'https://61edebe5ee6d4d38a9d6459c7323d777@o4505289711681536.ingest.sentry.io/4505357688242176', + }), + ], +}) diff --git a/packages/plugin-sentry/dev/src/server.ts b/packages/plugin-sentry/dev/src/server.ts new file mode 100644 index 000000000..c245eeef2 --- /dev/null +++ b/packages/plugin-sentry/dev/src/server.ts @@ -0,0 +1,27 @@ +import dotenv from 'dotenv' +import express from 'express' +import payload from 'payload' + +dotenv.config() +const app = express() + +// Redirect root to Admin panel +app.get('/', (_, res) => { + res.redirect('/admin') +}) + +// Initialize Payload +const start = async (): Promise => { + await payload.init({ + secret: process.env.PAYLOAD_SECRET, + mongoURL: process.env.MONGODB_URI, + express: app, + onInit: () => { + payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`) + }, + }) + + app.listen(3000) +} + +start() diff --git a/packages/plugin-sentry/dev/tsconfig.json b/packages/plugin-sentry/dev/tsconfig.json new file mode 100644 index 000000000..196397b33 --- /dev/null +++ b/packages/plugin-sentry/dev/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "strict": false, + "esModuleInterop": true, + "skipLibCheck": true, + "outDir": "./dist", + "rootDir": "../", + "jsx": "react", + "sourceMap": true + }, + "ts-node": { + "transpileOnly": true + } +} diff --git a/packages/plugin-sentry/package.json b/packages/plugin-sentry/package.json new file mode 100644 index 000000000..0775d281f --- /dev/null +++ b/packages/plugin-sentry/package.json @@ -0,0 +1,60 @@ +{ + "name": "@payloadcms/plugin-sentry", + "version": "0.0.1", + "homepage:": "https://payloadcms.com", + "repository": "git@github.com:payloadcms/plugin-sentry.git", + "description": "Sentry plugin for Payload", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "test": "echo \"Error: no test specified\" && exit 1", + "lint": "eslint src", + "lint:fix": "eslint --fix --ext .ts,.tsx src" + }, + "keywords": [ + "payload", + "cms", + "plugin", + "typescript", + "sentry", + "error handling" + ], + "author": "dev@payloadcms.com", + "license": "MIT", + "files": [ + "dist" + ], + "peerDependencies": { + "payload": "^1.9.2", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "dependencies": { + "@sentry/node": "^7.55.2", + "@sentry/types": "^7.54.0", + "express": "^4.18.2" + }, + "devDependencies": { + "@payloadcms/eslint-config": "^0.0.1", + "@types/express": "^4.17.9", + "@types/node": "18.11.3", + "@types/react": "18.0.21", + "@typescript-eslint/eslint-plugin": "^5.51.0", + "@typescript-eslint/parser": "^5.51.0", + "copyfiles": "^2.4.1", + "cross-env": "^7.0.3", + "dotenv": "^8.2.0", + "eslint": "^8.19.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-filenames": "^1.3.2", + "eslint-plugin-import": "2.25.4", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-simple-import-sort": "^10.0.0", + "nodemon": "^2.0.6", + "payload": "^1.9.2", + "prettier": "^2.7.1", + "ts-node": "^10.9.1", + "typescript": "^4.1.3" + } +} \ No newline at end of file diff --git a/packages/plugin-sentry/src/captureException.ts b/packages/plugin-sentry/src/captureException.ts new file mode 100644 index 000000000..1882c3654 --- /dev/null +++ b/packages/plugin-sentry/src/captureException.ts @@ -0,0 +1,5 @@ +import * as Sentry from '@sentry/node' + +export const captureException = (err: Error): void => { + Sentry.captureException(err) +} diff --git a/packages/plugin-sentry/src/index.ts b/packages/plugin-sentry/src/index.ts new file mode 100644 index 000000000..431d20ad8 --- /dev/null +++ b/packages/plugin-sentry/src/index.ts @@ -0,0 +1,38 @@ +/* eslint-disable no-console */ +import type { Config } from 'payload/config' + +import { captureException } from './captureException' +import { startSentry } from './startSentry' +import type { PluginOptions } from './types' +import { extendWebpackConfig } from './webpack' + +export const sentry = + (pluginOptions: PluginOptions) => + (incomingConfig: Config): Config => { + if (!pluginOptions.dsn) { + console.log('Sentry plugin is disabled because no DSN was provided') + return incomingConfig + } + + let config = { ...incomingConfig } + const webpack = extendWebpackConfig(incomingConfig) + + config.admin = { + ...(config.admin || {}), + webpack, + } + + config.hooks = { + ...(incomingConfig.hooks || {}), + afterError: (err: any) => { + captureException(err) + }, + } + + startSentry({ + dsn: pluginOptions.dsn, + options: pluginOptions?.options, + }) + + return config + } diff --git a/packages/plugin-sentry/src/mocks/mockFile.js b/packages/plugin-sentry/src/mocks/mockFile.js new file mode 100644 index 000000000..644d71ec5 --- /dev/null +++ b/packages/plugin-sentry/src/mocks/mockFile.js @@ -0,0 +1,4 @@ +module.exports = { + startSentry: () => {}, + captureException: () => {}, +} diff --git a/packages/plugin-sentry/src/startSentry.ts b/packages/plugin-sentry/src/startSentry.ts new file mode 100644 index 000000000..91c666bf6 --- /dev/null +++ b/packages/plugin-sentry/src/startSentry.ts @@ -0,0 +1,48 @@ +/* eslint-disable no-console */ +import * as Sentry from '@sentry/node' +import express from 'express' + +import type { PluginOptions } from './types' + +export const startSentry = (pluginOptions: PluginOptions): any => { + const { dsn, options } = pluginOptions + + try { + const app = express() + + Sentry.init({ + dsn: dsn, + tracesSampleRate: 1.0, + integrations: [ + new Sentry.Integrations.Http({ tracing: true }), + new Sentry.Integrations.Express({ app }), + ...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations(), + ], + ...options, + }) + + app.use(Sentry.Handlers.requestHandler()) + app.use(Sentry.Handlers.tracingHandler()) + app.use( + Sentry.Handlers.errorHandler({ + shouldHandleError(error) { + if (error.status === 404 || error.status === 500) { + return true + } + return false + }, + }), + ) + app.use(Sentry.Handlers.requestHandler()) + app.get('/', function rootHandler(req, res) { + res.end('Sentry running') + }) + + app.use(function onError(err, req, res, next) { + res.statusCode = 500 + res.end(res.sentry + '\n') + }) + } catch (err: unknown) { + console.log('There was an error initializing Sentry, please ensure you entered a valid DSN') + } +} diff --git a/packages/plugin-sentry/src/types.ts b/packages/plugin-sentry/src/types.ts new file mode 100644 index 000000000..04ec42d35 --- /dev/null +++ b/packages/plugin-sentry/src/types.ts @@ -0,0 +1,6 @@ +import type { ClientOptions } from '@sentry/types' + +export interface PluginOptions { + dsn: string + options?: ClientOptions +} diff --git a/packages/plugin-sentry/src/webpack.ts b/packages/plugin-sentry/src/webpack.ts new file mode 100644 index 000000000..eeb318fe7 --- /dev/null +++ b/packages/plugin-sentry/src/webpack.ts @@ -0,0 +1,28 @@ +import path from 'path' +import type { Config } from 'payload/config' +import type { Configuration as WebpackConfig } from 'webpack' + +export const extendWebpackConfig = + (config: Config): ((webpackConfig: WebpackConfig) => WebpackConfig) => + webpackConfig => { + const existingWebpackConfig = + typeof config.admin?.webpack === 'function' + ? config.admin.webpack(webpackConfig) + : webpackConfig + + const mockModulePath = path.resolve(__dirname, './mocks/mockFile.js') + + const newWebpack = { + ...existingWebpackConfig, + resolve: { + ...(existingWebpackConfig.resolve || {}), + alias: { + ...(existingWebpackConfig.resolve?.alias ? existingWebpackConfig.resolve.alias : {}), + [path.resolve(__dirname, './startSentry')]: mockModulePath, + [path.resolve(__dirname, './captureException')]: mockModulePath, + }, + }, + } + + return newWebpack + } diff --git a/packages/plugin-sentry/tsconfig.json b/packages/plugin-sentry/tsconfig.json new file mode 100644 index 000000000..fd92f4c93 --- /dev/null +++ b/packages/plugin-sentry/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es5", + "outDir": "./dist", + "allowJs": true, + "module": "commonjs", + "sourceMap": true, + "jsx": "react", + "esModuleInterop": true, + "declaration": true, + "declarationDir": "./dist", + "skipLibCheck": true, + "strict": true, + }, + "include": [ + "src/**/*" + ], +} \ No newline at end of file