feat(plugin-sentry): update plugin to 3.0 (#8613)
Updates the plugin to 3.0
Test:
```sh
NEXT_PUBLIC_SENTRY_DSN=<DSN here> pnpm dev plugin-sentry
```
Example:
```ts
sentryPlugin({
options: {
captureErrors: [400, 403],
context: ({ defaultContext, req }) => {
return {
...defaultContext,
tags: {
locale: req.locale,
},
}
},
debug: true,
},
Sentry,
})
```
This commit is contained in:
27
app/global-error.tsx
Normal file
27
app/global-error.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/* eslint-disable no-restricted-exports */
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import * as Sentry from '@sentry/nextjs'
|
||||||
|
import NextError from 'next/error.js'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
export default function GlobalError({ error }: { error: { digest?: string } & Error }) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
|
||||||
|
Sentry.captureException(error)
|
||||||
|
}
|
||||||
|
}, [error])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang="en-US">
|
||||||
|
<body>
|
||||||
|
{/* `NextError` is the default Next.js error page component. Its type
|
||||||
|
definition requires a `statusCode` prop. However, since the App Router
|
||||||
|
does not expose status codes for errors, we simply pass 0 to render a
|
||||||
|
generic error message. */}
|
||||||
|
{/* @ts-expect-error types repo */}
|
||||||
|
<NextError statusCode={0} />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ This multi-faceted software offers a range of features that will help you manage
|
|||||||
- **Integrations**: Connects with various tools and services for enhanced workflow and issue management
|
- **Integrations**: Connects with various tools and services for enhanced workflow and issue management
|
||||||
|
|
||||||
<Banner type="info">
|
<Banner type="info">
|
||||||
This plugin is completely open-source and the [source code can be found here](https://github.com/payloadcms/payload/tree/main/packages/plugin-sentry). If you need help, check out our [Community Help](https://payloadcms.com/community-help). If you think you've found a bug, please [open a new issue](https://github.com/payloadcms/payload/issues/new?assignees=&labels=plugin%3A%20seo&template=bug_report.md&title=plugin-seo%3A) with as much detail as possible.
|
This plugin is completely open-source and the [source code can be found here](https://github.com/payloadcms/payload/tree/beta/packages/plugin-sentry). If you need help, check out our [Community Help](https://payloadcms.com/community-help). If you think you've found a bug, please [open a new issue](https://github.com/payloadcms/payload/issues/new?assignees=&labels=plugin%3A%20seo&template=bug_report.md&title=plugin-sentry%3A) with as much detail as possible.
|
||||||
</Banner>
|
</Banner>
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
@@ -42,6 +42,15 @@ Install the plugin using any JavaScript package manager like [Yarn](https://yarn
|
|||||||
pnpm add @payloadcms/plugin-sentry
|
pnpm add @payloadcms/plugin-sentry
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Sentry for Next.js setup
|
||||||
|
This plugin requires to complete the [Sentry + Next.js setup](https://docs.sentry.io/platforms/javascript/guides/nextjs/) before.
|
||||||
|
|
||||||
|
You can use either the [automatic setup](https://docs.sentry.io/platforms/javascript/guides/nextjs/#install) with the installation wizard:
|
||||||
|
```sh
|
||||||
|
npx @sentry/wizard@latest -i nextjs
|
||||||
|
```
|
||||||
|
Or the [Manual Setup](https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/)
|
||||||
|
|
||||||
## Basic Usage
|
## Basic Usage
|
||||||
|
|
||||||
In the `plugins` array of your [Payload Config](https://payloadcms.com/docs/configuration/overview), call the plugin and pass in your Sentry DSN as an option.
|
In the `plugins` array of your [Payload Config](https://payloadcms.com/docs/configuration/overview), call the plugin and pass in your Sentry DSN as an option.
|
||||||
@@ -51,11 +60,13 @@ import { buildConfig } from 'payload'
|
|||||||
import { sentryPlugin } from '@payloadcms/plugin-sentry'
|
import { sentryPlugin } from '@payloadcms/plugin-sentry'
|
||||||
import { Pages, Media } from './collections'
|
import { Pages, Media } from './collections'
|
||||||
|
|
||||||
|
import * as Sentry from '@sentry/nextjs'
|
||||||
|
|
||||||
const config = buildConfig({
|
const config = buildConfig({
|
||||||
collections: [Pages, Media],
|
collections: [Pages, Media],
|
||||||
plugins: [
|
plugins: [
|
||||||
sentryPlugin({
|
sentryPlugin({
|
||||||
dsn: 'https://61edebas776889984d323d777@o4505289711681536.ingest.sentry.io/4505357433352176',
|
Sentry,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@@ -65,58 +76,55 @@ export default config
|
|||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
- `dsn` : string | **required**
|
- `Sentry` : Sentry | **required**
|
||||||
|
|
||||||
Sentry automatically assigns a DSN when you create a project, the unique DSN informs Sentry where to send events so they are associated with the correct project.
|
The `Sentry` instance
|
||||||
|
|
||||||
<Banner type="warning">
|
<Banner type="warning">
|
||||||
You can find your project DSN (Data Source Name) by visiting [sentry.io](sentry.io) and navigating to your [Project] > Settings > Client Keys (DSN).
|
Make sure to complete the [Sentry for Next.js Setup](#sentry-for-nextjs-setup) before.
|
||||||
</Banner>
|
</Banner>
|
||||||
|
|
||||||
- `enabled`: boolean | optional
|
- `enabled`: boolean | optional
|
||||||
|
|
||||||
Set to false to disable the plugin. Defaults to true.
|
Set to false to disable the plugin. Defaults to `true`.
|
||||||
|
|
||||||
- `init` : ClientOptions | optional
|
- `context`: `(args: ContextArgs) => Partial<ScopeContext> | Promise<Partial<ScopeContext>>`
|
||||||
|
|
||||||
Sentry allows a variety of options to be passed into the Sentry.init() function, see the full list of options [here](https://docs.sentry.io/platforms/node/guides/express/configuration/options).
|
Pass additional [contextual data](https://docs.sentry.io/platforms/javascript/enriching-events/context/#passing-context-directly) to Sentry
|
||||||
|
|
||||||
- `requestHandler` : RequestHandlerOptions | optional
|
|
||||||
|
|
||||||
Accepts options that let you decide what data should be included in the event sent to Sentry, checkout the options [here](https://docs.sentry.io/platforms/node/guides/express/configuration/options).
|
|
||||||
|
|
||||||
- `captureErrors`: number[] | optional
|
- `captureErrors`: number[] | optional
|
||||||
|
|
||||||
By default, `Sentry.errorHandler` will capture only errors with a status code of 500 or higher. To capture additional error codes, pass the values as numbers in an array.
|
By default, `Sentry.errorHandler` will capture only errors with a status code of 500 or higher. To capture additional error codes, pass the values as numbers in an array.
|
||||||
|
|
||||||
To see all options available, visit the [Sentry Docs](https://docs.sentry.io/platforms/node/guides/express/configuration/options).
|
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
Configure any of these options by passing them to the plugin:
|
Configure any of these options by passing them to the plugin:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { buildConfig } from 'payload'
|
import { buildConfig } from 'payload'
|
||||||
import { sentry } from '@payloadcms/plugin-sentry'
|
import { sentryPlugin } from '@payloadcms/plugin-sentry'
|
||||||
|
|
||||||
|
import * as Sentry from '@sentry/nextjs'
|
||||||
|
|
||||||
import { Pages, Media } from './collections'
|
import { Pages, Media } from './collections'
|
||||||
|
|
||||||
const config = buildConfig({
|
const config = buildConfig({
|
||||||
collections: [Pages, Media],
|
collections: [Pages, Media],
|
||||||
plugins: [
|
plugins: [
|
||||||
sentry({
|
sentryPlugin({
|
||||||
dsn: 'https://61edebas777689984d323d777@o4505289711681536.ingest.sentry.io/4505357433352176',
|
|
||||||
options: {
|
options: {
|
||||||
init: {
|
captureErrors: [400, 403],
|
||||||
debug: true,
|
context: ({ defaultContext, req }) => {
|
||||||
environment: 'development',
|
return {
|
||||||
tracesSampleRate: 1.0,
|
...defaultContext,
|
||||||
|
tags: {
|
||||||
|
locale: req.locale,
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
requestHandler: {
|
debug: true,
|
||||||
serverName: false,
|
|
||||||
user: ['email'],
|
|
||||||
},
|
|
||||||
captureErrors: [400, 403, 404],
|
|
||||||
},
|
},
|
||||||
|
Sentry,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
5
instrumentation.ts
Normal file
5
instrumentation.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export async function register() {
|
||||||
|
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
||||||
|
await import('./sentry.server.config.js')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import bundleAnalyzer from '@next/bundle-analyzer'
|
import bundleAnalyzer from '@next/bundle-analyzer'
|
||||||
|
import { withSentryConfig } from '@sentry/nextjs'
|
||||||
import withPayload from './packages/next/src/withPayload.js'
|
import { withPayload } from './packages/next/src/withPayload.js'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
@@ -11,8 +11,7 @@ const withBundleAnalyzer = bundleAnalyzer({
|
|||||||
enabled: process.env.ANALYZE === 'true',
|
enabled: process.env.ANALYZE === 'true',
|
||||||
})
|
})
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-exports
|
const config = withBundleAnalyzer(
|
||||||
export default withBundleAnalyzer(
|
|
||||||
withPayload({
|
withPayload({
|
||||||
eslint: {
|
eslint: {
|
||||||
ignoreDuringBuilds: true,
|
ignoreDuringBuilds: true,
|
||||||
@@ -23,7 +22,6 @@ export default withBundleAnalyzer(
|
|||||||
env: {
|
env: {
|
||||||
PAYLOAD_CORE_DEV: 'true',
|
PAYLOAD_CORE_DEV: 'true',
|
||||||
ROOT_DIR: path.resolve(dirname),
|
ROOT_DIR: path.resolve(dirname),
|
||||||
PAYLOAD_CI_DEPENDENCY_CHECKER: 'true',
|
|
||||||
},
|
},
|
||||||
async redirects() {
|
async redirects() {
|
||||||
return [
|
return [
|
||||||
@@ -48,3 +46,8 @@ export default withBundleAnalyzer(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export default withSentryConfig(config, {
|
||||||
|
telemetry: false,
|
||||||
|
tunnelRoute: '/monitoring-tunnel',
|
||||||
|
})
|
||||||
|
|||||||
@@ -111,6 +111,8 @@
|
|||||||
"@payloadcms/eslint-plugin": "workspace:*",
|
"@payloadcms/eslint-plugin": "workspace:*",
|
||||||
"@payloadcms/live-preview-react": "workspace:*",
|
"@payloadcms/live-preview-react": "workspace:*",
|
||||||
"@playwright/test": "1.46.0",
|
"@playwright/test": "1.46.0",
|
||||||
|
"@sentry/nextjs": "^8.33.1",
|
||||||
|
"@sentry/node": "^8.33.1",
|
||||||
"@swc-node/register": "1.10.9",
|
"@swc-node/register": "1.10.9",
|
||||||
"@swc/cli": "0.4.0",
|
"@swc/cli": "0.4.0",
|
||||||
"@swc/jest": "0.2.36",
|
"@swc/jest": "0.2.36",
|
||||||
|
|||||||
@@ -60,7 +60,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@payloadcms/eslint-config": "workspace:*",
|
"@payloadcms/eslint-config": "workspace:*",
|
||||||
"@types/escape-html": "^1.0.4",
|
"@types/escape-html": "^1.0.4",
|
||||||
"@types/express": "^4.17.21",
|
|
||||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
|
|||||||
@@ -50,9 +50,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@payloadcms/eslint-config": "workspace:*",
|
"@payloadcms/eslint-config": "workspace:*",
|
||||||
"@types/express": "^4.17.9",
|
|
||||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
|
||||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
|
||||||
"payload": "workspace:*"
|
"payload": "workspace:*"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -74,9 +71,5 @@
|
|||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts"
|
"types": "./dist/index.d.ts"
|
||||||
},
|
},
|
||||||
"homepage:": "https://payloadcms.com",
|
"homepage:": "https://payloadcms.com"
|
||||||
"overrides": {
|
|
||||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
|
||||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@payloadcms/eslint-config": "workspace:*",
|
"@payloadcms/eslint-config": "workspace:*",
|
||||||
"@types/express": "^4.17.9",
|
|
||||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
||||||
"payload": "workspace:*"
|
"payload": "workspace:*"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@payloadcms/plugin-sentry",
|
"name": "@payloadcms/plugin-sentry",
|
||||||
"version": "0.0.6",
|
"version": "3.0.0-beta.111",
|
||||||
"description": "Sentry plugin for Payload",
|
"description": "Sentry plugin for Payload",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"payload",
|
"payload",
|
||||||
@@ -23,6 +23,11 @@
|
|||||||
"import": "./src/index.ts",
|
"import": "./src/index.ts",
|
||||||
"types": "./src/index.ts",
|
"types": "./src/index.ts",
|
||||||
"default": "./src/index.ts"
|
"default": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"./client": {
|
||||||
|
"import": "./src/exports/client.ts",
|
||||||
|
"types": "./src/exports/client.ts",
|
||||||
|
"default": "./src/exports/client.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
@@ -31,32 +36,24 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "echo \"Build temporarily disabled.\" && exit 0",
|
"build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
|
||||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc-build --strip-leading-paths",
|
"build:swc": "swc ./src -d ./dist --config-file .swcrc-build --strip-leading-paths",
|
||||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||||
"clean": "rimraf {dist,*.tsbuildinfo}",
|
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||||
|
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
"prepublishOnly": "pnpm clean && pnpm turbo build"
|
"prepublishOnly": "pnpm clean && pnpm turbo build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/node": "^7.55.2",
|
"@sentry/nextjs": "^8.33.1",
|
||||||
"@sentry/types": "^7.54.0",
|
"@sentry/types": "^8.33.1"
|
||||||
"express": "^4.18.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@payloadcms/eslint-config": "workspace:*",
|
"@payloadcms/eslint-config": "workspace:*",
|
||||||
"@types/express": "^4.17.9",
|
|
||||||
"@types/jest": "29.5.12",
|
|
||||||
"@types/node": "22.5.4",
|
|
||||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
||||||
"copyfiles": "^2.4.1",
|
"payload": "workspace:*"
|
||||||
"cross-env": "^7.0.3",
|
|
||||||
"jest": "^29.7.0",
|
|
||||||
"nodemon": "3.0.3",
|
|
||||||
"payload": "workspace:*",
|
|
||||||
"ts-jest": "^29.1.0"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"payload": "workspace:*",
|
"payload": "workspace:*",
|
||||||
@@ -69,6 +66,11 @@
|
|||||||
"import": "./dist/index.js",
|
"import": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"default": "./dist/index.js"
|
"default": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"./client": {
|
||||||
|
"import": "./dist/exports/client.js",
|
||||||
|
"types": "./dist/exports/client.d.ts",
|
||||||
|
"default": "./dist/exports/client.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import * as Sentry from '@sentry/node'
|
|
||||||
|
|
||||||
export const captureException = (err: Error): void => {
|
|
||||||
Sentry.captureException(err)
|
|
||||||
}
|
|
||||||
1
packages/plugin-sentry/src/exports/client.ts
Normal file
1
packages/plugin-sentry/src/exports/client.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { AdminErrorBoundary } from '../providers/AdminErrorBoundary.js'
|
||||||
@@ -1,2 +1,94 @@
|
|||||||
export { sentryPlugin } from './plugin.js'
|
import type { ScopeContext } from '@sentry/types'
|
||||||
export type { PluginOptions } from './types.js'
|
import type { APIError, Config } from 'payload'
|
||||||
|
|
||||||
|
import type { PluginOptions } from './types.js'
|
||||||
|
|
||||||
|
export { PluginOptions }
|
||||||
|
/**
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* import * as Sentry from '@sentry/nextjs'
|
||||||
|
*
|
||||||
|
* sentryPlugin({
|
||||||
|
* options: {
|
||||||
|
* captureErrors: [400, 403],
|
||||||
|
* context: ({ defaultContext, req }) => {
|
||||||
|
* return {
|
||||||
|
* ...defaultContext,
|
||||||
|
* tags: {
|
||||||
|
* locale: req.locale,
|
||||||
|
* },
|
||||||
|
* }
|
||||||
|
* },
|
||||||
|
* debug: true,
|
||||||
|
* },
|
||||||
|
* Sentry,
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const sentryPlugin =
|
||||||
|
(pluginOptions: PluginOptions) =>
|
||||||
|
(config: Config): Config => {
|
||||||
|
const { enabled = true, options = {}, Sentry } = pluginOptions
|
||||||
|
|
||||||
|
if (!enabled || !Sentry) {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
const { captureErrors = [], debug = false } = options
|
||||||
|
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
admin: {
|
||||||
|
...config.admin,
|
||||||
|
components: {
|
||||||
|
...config.admin?.components,
|
||||||
|
providers: [
|
||||||
|
...(config.admin?.components?.providers ?? []),
|
||||||
|
'@payloadcms/plugin-sentry/client#AdminErrorBoundary',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
afterError: [
|
||||||
|
...(config.hooks?.afterError ?? []),
|
||||||
|
async (args) => {
|
||||||
|
if ('status' in args.error) {
|
||||||
|
const apiError = args.error as APIError
|
||||||
|
if (apiError.status >= 500 || captureErrors.includes(apiError.status)) {
|
||||||
|
let context: Partial<ScopeContext> = {
|
||||||
|
extra: {
|
||||||
|
errorCollectionSlug: args.collection?.slug,
|
||||||
|
},
|
||||||
|
...(args.req.user && {
|
||||||
|
user: {
|
||||||
|
id: args.req.user.id,
|
||||||
|
collection: args.req.user.collection,
|
||||||
|
email: args.req.user.email,
|
||||||
|
ip_address: args.req.headers?.get('X-Forwarded-For') ?? undefined,
|
||||||
|
username: args.req.user.username,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.context) {
|
||||||
|
context = await options.context({
|
||||||
|
...args,
|
||||||
|
defaultContext: context,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = Sentry.captureException(args.error, context)
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
args.req.payload.logger.info(
|
||||||
|
`Captured exception ${id} to Sentry, error msg: ${args.error.message}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
captureException: () => {},
|
|
||||||
startSentry: () => {},
|
|
||||||
}
|
|
||||||
@@ -1,47 +1,96 @@
|
|||||||
import type { Config } from 'payload'
|
import type { AfterErrorHook, AfterErrorHookArgs, Config, PayloadRequest } from 'payload'
|
||||||
|
|
||||||
import { defaults } from 'payload'
|
import { APIError, defaults } from 'payload'
|
||||||
|
|
||||||
import { sentryPlugin } from './plugin'
|
import { sentryPlugin } from './index'
|
||||||
|
import { randomUUID } from 'crypto'
|
||||||
|
|
||||||
describe('plugin', () => {
|
const mockExceptionID = randomUUID()
|
||||||
|
|
||||||
|
const mockSentry = {
|
||||||
|
captureException() {
|
||||||
|
return mockExceptionID
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('@payloadcms/plugin-sentry - unit', () => {
|
||||||
it('should run the plugin', () => {
|
it('should run the plugin', () => {
|
||||||
const plugin = sentryPlugin({ dsn: 'asdf', enabled: true })
|
const plugin = sentryPlugin({ Sentry: mockSentry, enabled: true })
|
||||||
const config = plugin(createConfig())
|
const config = plugin(createConfig())
|
||||||
|
|
||||||
assertPluginRan(config)
|
assertPluginRan(config)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should default enable: true', () => {
|
it('should default enabled: true', () => {
|
||||||
const plugin = sentryPlugin({ dsn: 'asdf' })
|
const plugin = sentryPlugin({ Sentry: mockSentry })
|
||||||
const config = plugin(createConfig())
|
const config = plugin(createConfig())
|
||||||
|
|
||||||
assertPluginRan(config)
|
assertPluginRan(config)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not run if dsn is not provided', () => {
|
it('should not run if Sentry is not provided', () => {
|
||||||
const plugin = sentryPlugin({ dsn: null, enabled: true })
|
const plugin = sentryPlugin({ enabled: true })
|
||||||
const config = plugin(createConfig())
|
const config = plugin(createConfig())
|
||||||
|
|
||||||
assertPluginDidNotRun(config)
|
assertPluginDidNotRun(config)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respect enabled: false', () => {
|
it('should respect enabled: false', () => {
|
||||||
const plugin = sentryPlugin({ dsn: null, enabled: false })
|
const plugin = sentryPlugin({ Sentry: mockSentry, enabled: false })
|
||||||
const config = plugin(createConfig())
|
const config = plugin(createConfig())
|
||||||
|
|
||||||
assertPluginDidNotRun(config)
|
assertPluginDidNotRun(config)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should execute Sentry.captureException with correct errors / args', async () => {
|
||||||
|
const hintTimestamp = Date.now()
|
||||||
|
|
||||||
|
const plugin = sentryPlugin({
|
||||||
|
Sentry: mockSentry,
|
||||||
|
options: {
|
||||||
|
context: ({ defaultContext }) => ({
|
||||||
|
...defaultContext,
|
||||||
|
extra: {
|
||||||
|
...defaultContext.extra,
|
||||||
|
hintTimestamp,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const config = plugin(createConfig())
|
||||||
|
|
||||||
|
const hook = config.hooks?.afterError?.[0] as AfterErrorHook
|
||||||
|
|
||||||
|
const error = new APIError('ApiError', 500)
|
||||||
|
|
||||||
|
const afterErrorHookArgs: AfterErrorHookArgs = {
|
||||||
|
req: {} as PayloadRequest,
|
||||||
|
context: {},
|
||||||
|
error,
|
||||||
|
collection: { slug: 'mock-slug' } as any,
|
||||||
|
}
|
||||||
|
|
||||||
|
const captureExceptionSpy = jest.spyOn(mockSentry, 'captureException')
|
||||||
|
|
||||||
|
await hook(afterErrorHookArgs)
|
||||||
|
|
||||||
|
expect(captureExceptionSpy).toHaveBeenCalledTimes(1)
|
||||||
|
expect(captureExceptionSpy).toHaveBeenCalledWith(error, {
|
||||||
|
extra: {
|
||||||
|
errorCollectionSlug: 'mock-slug',
|
||||||
|
hintTimestamp,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(captureExceptionSpy).toHaveReturnedWith(mockExceptionID)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
function assertPluginRan(config: Config) {
|
function assertPluginRan(config: Config) {
|
||||||
expect(config.hooks?.afterError).toBeDefined()
|
expect(config.hooks?.afterError?.[0]).toBeDefined()
|
||||||
expect(config.onInit).toBeDefined()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertPluginDidNotRun(config: Config) {
|
function assertPluginDidNotRun(config: Config) {
|
||||||
expect(config.hooks?.afterError).toBeUndefined()
|
expect(config.hooks?.afterError?.[0]).toBeUndefined()
|
||||||
expect(config.onInit).toBeUndefined()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createConfig(overrides?: Partial<Config>): Config {
|
function createConfig(overrides?: Partial<Config>): Config {
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import type { Config } from 'payload'
|
|
||||||
|
|
||||||
import type { PluginOptions } from './types.js'
|
|
||||||
|
|
||||||
import { captureException } from './captureException.js'
|
|
||||||
import { startSentry } from './startSentry.js'
|
|
||||||
|
|
||||||
export const sentryPlugin =
|
|
||||||
(pluginOptions: PluginOptions) =>
|
|
||||||
(incomingConfig: Config): Config => {
|
|
||||||
const config = { ...incomingConfig }
|
|
||||||
|
|
||||||
if (pluginOptions.enabled === false || !pluginOptions.dsn) {
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
config.hooks = {
|
|
||||||
...(incomingConfig.hooks || {}),
|
|
||||||
|
|
||||||
afterError: [
|
|
||||||
({ error }) => {
|
|
||||||
captureException(error)
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
config.onInit = async (payload) => {
|
|
||||||
if (incomingConfig.onInit) {
|
|
||||||
await incomingConfig.onInit(payload)
|
|
||||||
}
|
|
||||||
startSentry(pluginOptions, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
12
packages/plugin-sentry/src/providers/AdminErrorBoundary.tsx
Normal file
12
packages/plugin-sentry/src/providers/AdminErrorBoundary.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
|
|
||||||
|
import { ErrorBoundary } from '@sentry/nextjs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Captures errored components to Sentry
|
||||||
|
*/
|
||||||
|
export const AdminErrorBoundary = ({ children }: { children: ReactNode }) => {
|
||||||
|
return <ErrorBoundary>{children}</ErrorBoundary>
|
||||||
|
}
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import type { NextFunction, Request, Response } from 'express'
|
|
||||||
import type express from 'express'
|
|
||||||
import type { Payload } from 'payload'
|
|
||||||
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
import * as Sentry from '@sentry/node'
|
|
||||||
|
|
||||||
import type { PluginOptions } from './types'
|
|
||||||
|
|
||||||
export const startSentry = (pluginOptions: PluginOptions, payload: Payload): void => {
|
|
||||||
const { dsn, options } = pluginOptions
|
|
||||||
const { express: app } = payload
|
|
||||||
|
|
||||||
if (!dsn || !app) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Sentry.init({
|
|
||||||
...options?.init,
|
|
||||||
dsn,
|
|
||||||
integrations: [
|
|
||||||
...(options?.init?.integrations || []),
|
|
||||||
new Sentry.Integrations.Http({ tracing: true }),
|
|
||||||
new Sentry.Integrations.Express({ app }),
|
|
||||||
...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations(),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
app.use(Sentry.Handlers.requestHandler(options?.requestHandler || {}) as express.RequestHandler)
|
|
||||||
app.use(Sentry.Handlers.tracingHandler())
|
|
||||||
|
|
||||||
app.use(
|
|
||||||
Sentry.Handlers.errorHandler({
|
|
||||||
shouldHandleError(error) {
|
|
||||||
if (error.status === 500) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
options?.captureErrors &&
|
|
||||||
typeof error.status === 'number' &&
|
|
||||||
options.captureErrors.includes(error.status)
|
|
||||||
) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
}) as express.ErrorRequestHandler,
|
|
||||||
)
|
|
||||||
|
|
||||||
app.use(function onError(
|
|
||||||
_err: unknown,
|
|
||||||
_req: Request,
|
|
||||||
res: { sentry?: string } & Response,
|
|
||||||
_next: NextFunction,
|
|
||||||
) {
|
|
||||||
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')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +1,46 @@
|
|||||||
import type { RequestHandlerOptions } from '@sentry/node/types/handlers'
|
import type { ScopeContext } from '@sentry/types'
|
||||||
import type { ClientOptions } from '@sentry/types'
|
import type { AfterErrorHookArgs } from 'payload'
|
||||||
|
|
||||||
|
type SentryInstance = {
|
||||||
|
captureException: (err: Error, hint: any) => string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContextArgs = {
|
||||||
|
defaultContext: Partial<ScopeContext>
|
||||||
|
} & AfterErrorHookArgs
|
||||||
|
|
||||||
export interface PluginOptions {
|
export interface PluginOptions {
|
||||||
/**
|
|
||||||
* Sentry DSN (Data Source Name)
|
|
||||||
* This is required unless enabled is set to false.
|
|
||||||
* Sentry automatically assigns a DSN when you create a project.
|
|
||||||
* If you don't have a DSN yet, you can create a new project here: https://sentry.io
|
|
||||||
*/
|
|
||||||
dsn: null | string
|
|
||||||
/**
|
/**
|
||||||
* Enable or disable Sentry plugin
|
* Enable or disable Sentry plugin
|
||||||
* @default false
|
* @default true
|
||||||
*/
|
*/
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
/**
|
/**
|
||||||
* Options passed directly to Sentry
|
* Options passed directly to Sentry
|
||||||
* @default false
|
|
||||||
*/
|
*/
|
||||||
options?: {
|
options?: {
|
||||||
/**
|
/**
|
||||||
* Sentry will only capture 500 errors by default.
|
* Sentry will only capture 500 errors by default.
|
||||||
* If you want to capture other errors, you can add them as an array here.
|
* If you want to capture other errors, you can add them as an array here.
|
||||||
|
* @default []
|
||||||
*/
|
*/
|
||||||
captureErrors?: number[]
|
captureErrors?: number[]
|
||||||
/**
|
/**
|
||||||
* Passes any valid options to Sentry.init()
|
* Set `ScopeContext` for `Sentry.captureException` which includes `user` and other info.
|
||||||
*/
|
*/
|
||||||
init?: Partial<ClientOptions>
|
context?: (args: ContextArgs) => Partial<ScopeContext> | Promise<Partial<ScopeContext>>
|
||||||
/**
|
/**
|
||||||
* Passes any valid options to Sentry.Handlers.requestHandler()
|
* Log captured exceptions,
|
||||||
|
* @default false
|
||||||
*/
|
*/
|
||||||
requestHandler?: RequestHandlerOptions
|
debug?: boolean
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Instance of Sentry from
|
||||||
|
* ```ts
|
||||||
|
* import * as Sentry from '@sentry/nextjs'
|
||||||
|
* ```
|
||||||
|
* This is required unless enabled is set to false.
|
||||||
|
*/
|
||||||
|
Sentry?: SentryInstance
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
"noEmit": false /* Do not emit outputs. */,
|
"noEmit": false /* Do not emit outputs. */,
|
||||||
"emitDeclarationOnly": true,
|
"emitDeclarationOnly": true,
|
||||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||||
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
"rootDir": "./src", /* Specify the root folder within your source files. */
|
||||||
"strict": true
|
"jsx": "react-jsx"
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -63,7 +63,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@payloadcms/eslint-config": "workspace:*",
|
"@payloadcms/eslint-config": "workspace:*",
|
||||||
"@payloadcms/next": "workspace:*",
|
"@payloadcms/next": "workspace:*",
|
||||||
"@types/express": "^4.17.9",
|
|
||||||
"@types/lodash.get": "^4.4.7",
|
"@types/lodash.get": "^4.4.7",
|
||||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
||||||
|
|||||||
1707
pnpm-lock.yaml
generated
1707
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
26
sentry.client.config.ts
Normal file
26
sentry.client.config.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import * as Sentry from '@sentry/nextjs'
|
||||||
|
|
||||||
|
const dsn = process.env.NEXT_PUBLIC_SENTRY_DSN
|
||||||
|
|
||||||
|
Sentry.init({
|
||||||
|
dsn,
|
||||||
|
// Replay may only be enabled for the client-side
|
||||||
|
integrations: [Sentry.replayIntegration()],
|
||||||
|
|
||||||
|
// Set tracesSampleRate to 1.0 to capture 100%
|
||||||
|
// of transactions for tracing.
|
||||||
|
// We recommend adjusting this value in production
|
||||||
|
tracesSampleRate: 1.0,
|
||||||
|
|
||||||
|
// Capture Replay for 10% of all sessions,
|
||||||
|
// plus for 100% of sessions with an error
|
||||||
|
enabled: !!dsn,
|
||||||
|
replaysOnErrorSampleRate: 1.0,
|
||||||
|
replaysSessionSampleRate: 0.1,
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Note: if you want to override the automatic release value, do not set a
|
||||||
|
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
|
||||||
|
// that it will also get attached to your source maps
|
||||||
|
})
|
||||||
18
sentry.server.config.ts
Normal file
18
sentry.server.config.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import * as Sentry from '@sentry/nextjs'
|
||||||
|
const dsn = process.env.NEXT_PUBLIC_SENTRY_DSN
|
||||||
|
|
||||||
|
const enabled = !!dsn
|
||||||
|
|
||||||
|
Sentry.init({
|
||||||
|
dsn,
|
||||||
|
enabled,
|
||||||
|
skipOpenTelemetrySetup: true,
|
||||||
|
tracesSampleRate: 1.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Sentry inited')
|
||||||
|
}
|
||||||
|
|
||||||
|
export {}
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
"@payloadcms/storage-vercel-blob": "workspace:*",
|
"@payloadcms/storage-vercel-blob": "workspace:*",
|
||||||
"@payloadcms/translations": "workspace:*",
|
"@payloadcms/translations": "workspace:*",
|
||||||
"@payloadcms/ui": "workspace:*",
|
"@payloadcms/ui": "workspace:*",
|
||||||
|
"@sentry/nextjs": "^8.33.1",
|
||||||
"@sentry/react": "^7.77.0",
|
"@sentry/react": "^7.77.0",
|
||||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import * as Sentry from '@sentry/react'
|
|
||||||
import React from 'react'
|
import { useState } from 'react'
|
||||||
export const testErrors = () => {
|
|
||||||
|
export const TestErrors = () => {
|
||||||
|
const [throwClientSide, setThrowClientSide] = useState(false)
|
||||||
|
|
||||||
const notFound = async () => {
|
const notFound = async () => {
|
||||||
const req = await fetch('http://localhost:3000/api/users/notFound', {
|
const req = await fetch('http://localhost:3000/api/users/notFound', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -60,8 +63,12 @@ export const testErrors = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ThrowClientSide = () => {
|
||||||
|
throw new Error('client side error')
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sentry.ErrorBoundary>
|
<>
|
||||||
<h4>Test Errors</h4>
|
<h4>Test Errors</h4>
|
||||||
<div style={{ display: 'flex', gap: '10px' }}>
|
<div style={{ display: 'flex', gap: '10px' }}>
|
||||||
<button onClick={() => notFound()} style={{ marginBottom: '20px' }} type="button">
|
<button onClick={() => notFound()} style={{ marginBottom: '20px' }} type="button">
|
||||||
@@ -87,7 +94,15 @@ export const testErrors = () => {
|
|||||||
<button onClick={() => badVerify()} style={{ marginBottom: '20px' }} type="button">
|
<button onClick={() => badVerify()} style={{ marginBottom: '20px' }} type="button">
|
||||||
Bad Verify
|
Bad Verify
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setThrowClientSide(true)}
|
||||||
|
style={{ marginBottom: '20px' }}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Throw client side error
|
||||||
|
</button>
|
||||||
|
{throwClientSide && <ThrowClientSide />}
|
||||||
</div>
|
</div>
|
||||||
</Sentry.ErrorBoundary>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,8 @@ import path from 'path'
|
|||||||
const filename = fileURLToPath(import.meta.url)
|
const filename = fileURLToPath(import.meta.url)
|
||||||
const dirname = path.dirname(filename)
|
const dirname = path.dirname(filename)
|
||||||
import { sentryPlugin } from '@payloadcms/plugin-sentry'
|
import { sentryPlugin } from '@payloadcms/plugin-sentry'
|
||||||
|
import * as Sentry from '@sentry/nextjs'
|
||||||
|
import { APIError } from 'payload'
|
||||||
|
|
||||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||||
import { devUser } from '../credentials.js'
|
import { devUser } from '../credentials.js'
|
||||||
@@ -12,7 +14,7 @@ import { Users } from './collections/Users.js'
|
|||||||
export default buildConfigWithDefaults({
|
export default buildConfigWithDefaults({
|
||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
beforeDashboard: ['/components.js#testErrors'],
|
beforeDashboard: ['/TestErrors.js#TestErrors'],
|
||||||
},
|
},
|
||||||
importMap: {
|
importMap: {
|
||||||
baseDir: path.resolve(dirname),
|
baseDir: path.resolve(dirname),
|
||||||
@@ -29,17 +31,21 @@ export default buildConfigWithDefaults({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
endpoints: [
|
||||||
|
{
|
||||||
|
path: '/exception',
|
||||||
|
handler: () => {
|
||||||
|
throw new APIError('Test Plugin-Sentry Exception', 500)
|
||||||
|
},
|
||||||
|
method: 'get',
|
||||||
|
},
|
||||||
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
sentryPlugin({
|
sentryPlugin({
|
||||||
dsn: 'https://61edebe5ee6d4d38a9d6459c7323d777@o4505289711681536.ingest.sentry.io/4505357688242176',
|
Sentry,
|
||||||
options: {
|
options: {
|
||||||
|
debug: true,
|
||||||
captureErrors: [400, 403, 404],
|
captureErrors: [400, 403, 404],
|
||||||
init: {
|
|
||||||
debug: true,
|
|
||||||
},
|
|
||||||
requestHandler: {
|
|
||||||
serverName: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export interface Config {
|
|||||||
collections: {
|
collections: {
|
||||||
posts: Post;
|
posts: Post;
|
||||||
users: User;
|
users: User;
|
||||||
|
'payload-locked-documents': PayloadLockedDocument;
|
||||||
'payload-preferences': PayloadPreference;
|
'payload-preferences': PayloadPreference;
|
||||||
'payload-migrations': PayloadMigration;
|
'payload-migrations': PayloadMigration;
|
||||||
};
|
};
|
||||||
@@ -70,6 +71,29 @@ export interface User {
|
|||||||
lockUntil?: string | null;
|
lockUntil?: string | null;
|
||||||
password?: string | null;
|
password?: string | null;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "payload-locked-documents".
|
||||||
|
*/
|
||||||
|
export interface PayloadLockedDocument {
|
||||||
|
id: string;
|
||||||
|
document?:
|
||||||
|
| ({
|
||||||
|
relationTo: 'posts';
|
||||||
|
value: string | Post;
|
||||||
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'users';
|
||||||
|
value: string | User;
|
||||||
|
} | null);
|
||||||
|
globalSlug?: string | null;
|
||||||
|
user: {
|
||||||
|
relationTo: 'users';
|
||||||
|
value: string | User;
|
||||||
|
};
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "payload-preferences".
|
* via the `definition` "payload-preferences".
|
||||||
|
|||||||
@@ -78,6 +78,9 @@
|
|||||||
"@payloadcms/plugin-seo/client": [
|
"@payloadcms/plugin-seo/client": [
|
||||||
"./packages/plugin-seo/src/exports/client.ts"
|
"./packages/plugin-seo/src/exports/client.ts"
|
||||||
],
|
],
|
||||||
|
"@payloadcms/plugin-sentry/client": [
|
||||||
|
"./packages/plugin-sentry/src/exports/client.ts"
|
||||||
|
],
|
||||||
"@payloadcms/plugin-stripe/client": [
|
"@payloadcms/plugin-stripe/client": [
|
||||||
"./packages/plugin-stripe/src/exports/client.ts"
|
"./packages/plugin-stripe/src/exports/client.ts"
|
||||||
],
|
],
|
||||||
@@ -171,6 +174,9 @@
|
|||||||
"app",
|
"app",
|
||||||
"next-env.d.ts",
|
"next-env.d.ts",
|
||||||
".next/types/**/*.ts",
|
".next/types/**/*.ts",
|
||||||
"scripts/**/*.ts"
|
"scripts/**/*.ts",
|
||||||
|
"instrumentation.ts",
|
||||||
|
"sentry.server.config.ts",
|
||||||
|
"sentry.client.config.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user