chore(examples): adds nested docs example (#4452)

This commit is contained in:
Jacob Fletcher
2023-12-11 17:40:10 -05:00
committed by GitHub
parent 63000373e6
commit 2a65717792
93 changed files with 16372 additions and 0 deletions

View File

@@ -0,0 +1 @@
NEXT_PUBLIC_PAYLOAD_URL=http://localhost:3000

View File

@@ -0,0 +1,7 @@
module.exports = {
root: true,
extends: ['plugin:@next/next/recommended', '@payloadcms'],
rules: {
'import/extensions': 'off',
},
}

View File

@@ -0,0 +1,6 @@
.next
dist
build
node_modules
.env
package-lock.json

View File

@@ -0,0 +1,8 @@
module.exports = {
printWidth: 100,
parser: 'typescript',
semi: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
}

View File

@@ -0,0 +1,37 @@
# Payload Nested Docs Example Front-End
This is a [Next.js](https://nextjs.org) app using the [App Router](https://nextjs.org/docs/app). It was made explicitly for Payload's [Nested Docs Example](https://github.com/payloadcms/payload/tree/main/examples/nested-docs/payload).
> This example uses the App Router, the latest API of Next.js. If your app is using the legacy [Pages Router](https://nextjs.org/docs/pages), check out the official [Pages Router Example](https://github.com/payloadcms/payload/tree/main/examples/nested-docs/next-pages).
## Getting Started
### Payload
First you'll need a running Payload app. There is one made explicitly for this example and [can be found here](https://github.com/payloadcms/payload/tree/main/examples/nested-docs/payload). If you have not done so already, clone it down and follow the setup instructions there. This will provide all the necessary APIs that your Next.js app requires for authentication.
### Next.js
1. Clone this repo
2. `cd` into this directory and run `yarn` or `npm install`
3. `cp .env.example .env` to copy the example environment variables
4. `yarn dev` or `npm run dev` to start the server
5. `open http://localhost:3001` to see the result
Once running you will find a couple seeded pages on your local environment with some basic instructions. You can also start editing the pages by modifying the documents within Payload. See the [Nested Docs Example](https://github.com/payloadcms/payload/tree/main/examples/nested-docs/payload) for full details.
## Learn More
To learn more about Payload and Next.js, take a look at the following resources:
- [Payload Documentation](https://payloadcms.com/docs) - learn about Payload features and API.
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Payload GitHub repository](https://github.com/payloadcms/payload) as well as [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deployment
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new) from the creators of Next.js. You could also combine this app into a [single Express server](https://github.com/payloadcms/payload/tree/main/examples/custom-server) and deploy in to [Payload Cloud](https://payloadcms.com/new/import).
Check out our [Payload deployment documentation](https://payloadcms.com/docs/production/deployment) or the [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

View File

@@ -0,0 +1,3 @@
.page {
margin-top: calc(var(--base) * 2);
}

View File

@@ -0,0 +1,84 @@
import React from 'react'
import { notFound } from 'next/navigation'
import { Page } from '../../payload-types'
import { Gutter } from '../_components/Gutter'
import RichText from '../_components/RichText'
import classes from './index.module.scss'
interface PageParams {
params: { slug: string[] }
}
export const PageTemplate: React.FC<{ page: Page | null | undefined }> = ({ page }) => (
<main className={classes.page}>
<Gutter>
<h1>{page?.title}</h1>
<RichText content={page?.richText} />
</Gutter>
</main>
)
export default async function Page({ params }: PageParams) {
let { slug } = params || {}
if (!slug) slug = ['home']
const lastSlug = slug[slug.length - 1]
const page: Page = await fetch(
`${
process.env.NEXT_PUBLIC_PAYLOAD_URL
}/api/pages?where[slug][equals]=${lastSlug.toLowerCase()}&depth=1`,
)?.then(res => res.json()?.then(data => data.docs[0]))
if (!page) {
return notFound()
}
return <PageTemplate page={page} />
}
type Path = {
slug: string[]
}
type Paths = Path[]
export async function generateStaticParams() {
let paths: Paths = []
const pages: Page[] = await fetch(
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/pages?depth=0&limit=300`,
)?.then(res => res.json()?.then(data => data.docs))
if (pages && Array.isArray(pages) && pages.length > 0) {
paths = pages.map(page => {
const { slug, breadcrumbs } = page
let slugs = [slug]
const hasBreadcrumbs = breadcrumbs && Array.isArray(breadcrumbs) && breadcrumbs.length > 0
if (hasBreadcrumbs) {
slugs = breadcrumbs
.map(crumb => {
const { url } = crumb
let slug: string = ''
if (url) {
const split = url.split('/')
slug = split[split.length - 1]
}
return slug
})
?.filter(Boolean)
}
return { slug: slugs }
})
}
return paths
}

View File

@@ -0,0 +1,43 @@
'use client'
import React, { useState } from 'react'
import { PayloadAdminBar, PayloadAdminBarProps, PayloadMeUser } from 'payload-admin-bar'
import { Gutter } from '../Gutter'
import classes from './index.module.scss'
const Title: React.FC = () => <span>Dashboard</span>
export const AdminBarClient: React.FC<PayloadAdminBarProps> = props => {
const [user, setUser] = useState<PayloadMeUser>()
return (
<div className={[classes.adminBar, user && classes.show].filter(Boolean).join(' ')}>
<Gutter className={classes.container}>
<PayloadAdminBar
{...props}
logo={<Title />}
cmsURL={process.env.NEXT_PUBLIC_PAYLOAD_URL}
onPreviewExit={async () => {
await fetch(`/api/exit-preview`)
window.location.reload()
}}
onAuthChange={setUser}
className={classes.payloadAdminBar}
classNames={{
user: classes.user,
logo: classes.logo,
controls: classes.controls,
}}
style={{
position: 'relative',
zIndex: 'unset',
padding: 0,
backgroundColor: 'transparent',
}}
/>
</Gutter>
</div>
)
}

View File

@@ -0,0 +1,51 @@
.adminBar {
z-index: 10;
width: 100%;
background-color: rgba(var(--foreground-rgb), 0.075);
padding: calc(var(--base) * 0.5) 0;
display: none;
visibility: hidden;
opacity: 0;
transition: opacity 150ms linear;
}
.payloadAdminBar {
color: rgb(var(--foreground-rgb)) !important;
}
.show {
display: block;
visibility: visible;
opacity: 1;
}
.controls {
& > *:not(:last-child) {
margin-right: calc(var(--base) * 0.5) !important;
}
}
.user {
margin-right: calc(var(--base) * 0.5) !important;
}
.logo {
margin-right: calc(var(--base) * 0.5) !important;
}
.innerLogo {
width: 100%;
}
.container {
position: relative;
}
.hr {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background-color: rbg(var(--background-rgb));
height: 2px;
}

View File

@@ -0,0 +1,16 @@
import React from 'react'
import { draftMode } from 'next/headers'
import { AdminBarClient } from './index.client'
export function AdminBar() {
const { isEnabled: isPreviewMode } = draftMode()
return (
<AdminBarClient
preview={isPreviewMode}
// id={page?.id} // TODO: is there any way to do this?!
collection="pages"
/>
)
}

View File

@@ -0,0 +1,55 @@
.button {
border: none;
cursor: pointer;
display: inline-flex;
justify-content: center;
background-color: transparent;
}
.content {
display: flex;
align-items: center;
justify-content: space-around;
svg {
margin-right: calc(var(--base) / 2);
width: var(--base);
height: var(--base);
}
}
.label {
text-align: center;
display: flex;
align-items: center;
}
.button {
text-decoration: none;
display: inline-flex;
padding: 12px 24px;
}
.primary--white {
background-color: black;
color: white;
}
.primary--black {
background-color: white;
color: black;
}
.secondary--white {
background-color: white;
box-shadow: inset 0 0 0 1px black;
}
.secondary--black {
background-color: black;
box-shadow: inset 0 0 0 1px white;
}
.appearance--default {
padding: 0;
}

View File

@@ -0,0 +1,71 @@
import React, { ElementType } from 'react'
import Link from 'next/link'
import classes from './index.module.scss'
export type Props = {
label?: string
appearance?: 'default' | 'primary' | 'secondary'
el?: 'button' | 'link' | 'a'
onClick?: () => void
href?: string | null
newTab?: boolean | null
className?: string
type?: 'submit' | 'button'
disabled?: boolean
}
export const Button: React.FC<Props> = ({
el: elFromProps = 'link',
label,
newTab,
href,
appearance,
className: classNameFromProps,
onClick,
type = 'button',
disabled,
}) => {
let el = elFromProps
const newTabProps = newTab ? { target: '_blank', rel: 'noopener noreferrer' } : {}
const className = [
classes.button,
classNameFromProps,
classes[`appearance--${appearance}`],
classes.button,
]
.filter(Boolean)
.join(' ')
const content = (
<div className={classes.content}>
{/* <Chevron /> */}
<span className={classes.label}>{label}</span>
</div>
)
if (onClick || type === 'submit') el = 'button'
if (el === 'link') {
return (
<Link href={href || ''} className={className} {...newTabProps} onClick={onClick}>
{content}
</Link>
)
}
const Element: ElementType = el
return (
<Element
href={href || ''}
className={className}
type={type}
{...newTabProps}
onClick={onClick}
disabled={disabled}
>
{content}
</Element>
)
}

View File

@@ -0,0 +1,71 @@
import React from 'react'
import Link from 'next/link'
import { Page } from '../../../payload-types'
import { Button } from '../Button'
export type CMSLinkType = {
type?: 'custom' | 'reference' | null
url?: string | null
newTab?: boolean | null
reference?: {
value: string | Page
relationTo: 'pages'
} | null
label?: string
appearance?: 'default' | 'primary' | 'secondary'
children?: React.ReactNode
className?: string
}
export const CMSLink: React.FC<CMSLinkType> = ({
type,
url,
newTab,
reference,
label,
appearance,
children,
className,
}) => {
let href = url
if (type === 'reference' && reference && reference.value && typeof reference.value === 'object') {
if ('breadcrumbs' in reference.value) {
href = reference.value.breadcrumbs?.[reference.value.breadcrumbs.length - 1]?.url || ''
} else {
href = `/${reference.value.slug === 'home' ? '' : reference.value.slug}`
}
}
if (!appearance) {
const newTabProps = newTab ? { target: '_blank', rel: 'noopener noreferrer' } : {}
if (type === 'custom') {
return (
<a href={url || ''} {...newTabProps} className={className}>
{label && label}
{children && children}
</a>
)
}
if (href) {
return (
<Link href={href} {...newTabProps} className={className} prefetch={false}>
{label && label}
{children && children}
</Link>
)
}
}
const buttonProps = {
newTab,
href,
appearance,
label,
}
return <Button className={className} {...buttonProps} el="link" />
}

View File

@@ -0,0 +1,13 @@
.gutter {
max-width: var(--max-width);
width: 100%;
margin: auto;
}
.gutterLeft {
padding-left: var(--gutter-h);
}
.gutterRight {
padding-right: var(--gutter-h);
}

View File

@@ -0,0 +1,33 @@
import React, { forwardRef, Ref } from 'react'
import classes from './index.module.scss'
type Props = {
left?: boolean
right?: boolean
className?: string
children: React.ReactNode
ref?: Ref<HTMLDivElement>
}
export const Gutter: React.FC<Props> = forwardRef<HTMLDivElement, Props>((props, ref) => {
const { left = true, right = true, className, children } = props
return (
<div
ref={ref}
className={[
classes.gutter,
left && classes.gutterLeft,
right && classes.gutterRight,
className,
]
.filter(Boolean)
.join(' ')}
>
{children}
</div>
)
})
Gutter.displayName = 'Gutter'

View File

@@ -0,0 +1,32 @@
.header {
padding: var(--base) 0;
}
.wrap {
display: flex;
justify-content: space-between;
gap: calc(var(--base) / 2);
flex-wrap: wrap;
}
.logo {
flex-shrink: 0;
}
.nav {
display: flex;
align-items: center;
gap: var(--base);
white-space: nowrap;
overflow: hidden;
flex-wrap: wrap;
a {
display: block;
text-decoration: none;
}
@media (max-width: 1000px) {
gap: 0 calc(var(--base) / 2);
}
}

View File

@@ -0,0 +1,49 @@
import React from 'react'
import Image from 'next/image'
import Link from 'next/link'
import { MainMenu } from '../../../payload-types'
import { CMSLink } from '../CMSLink'
import { Gutter } from '../Gutter'
import classes from './index.module.scss'
export async function Header() {
const mainMenu: MainMenu = await fetch(
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/globals/main-menu`,
).then(res => res.json())
const { navItems } = mainMenu
const hasNavItems = navItems && Array.isArray(navItems) && navItems.length > 0
return (
<header className={classes.header}>
<Gutter className={classes.wrap}>
<Link href="/" className={classes.logo}>
<picture>
<source
srcSet="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/payload-logo-light.svg"
media="(prefers-color-scheme: dark)"
/>
<Image
width={150}
height={30}
alt="Payload Logo"
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/payload-logo-dark.svg"
/>
</picture>
</Link>
{hasNavItems && (
<nav className={classes.nav}>
{navItems.map(({ link }, i) => {
return <CMSLink key={i} {...link} />
})}
</nav>
)}
</Gutter>
</header>
)
}
export default Header

View File

@@ -0,0 +1,9 @@
.richText {
:first-child {
margin-top: 0;
}
a {
text-decoration: underline;
}
}

View File

@@ -0,0 +1,19 @@
import React from 'react'
import serialize from './serialize'
import classes from './index.module.scss'
const RichText: React.FC<{ className?: string; content: any }> = ({ className, content }) => {
if (!content) {
return null
}
return (
<div className={[classes.richText, className].filter(Boolean).join(' ')}>
{serialize(content)}
</div>
)
}
export default RichText

View File

@@ -0,0 +1,102 @@
import React, { Fragment } from 'react'
import escapeHTML from 'escape-html'
import { Text } from 'slate'
import { CMSLink } from '../CMSLink'
// eslint-disable-next-line no-use-before-define
type Children = Leaf[]
type Leaf = {
type: string
value?: {
url: string
alt: string
}
children: Children
url?: string
[key: string]: unknown
}
const serialize = (children: Children): React.ReactNode[] =>
children.map((node, i) => {
if (Text.isText(node)) {
let text = <span dangerouslySetInnerHTML={{ __html: escapeHTML(node.text) }} />
if (node.bold) {
text = <strong key={i}>{text}</strong>
}
if (node.code) {
text = <code key={i}>{text}</code>
}
if (node.italic) {
text = <em key={i}>{text}</em>
}
if (node.underline) {
text = (
<span style={{ textDecoration: 'underline' }} key={i}>
{text}
</span>
)
}
if (node.strikethrough) {
text = (
<span style={{ textDecoration: 'line-through' }} key={i}>
{text}
</span>
)
}
return <Fragment key={i}>{text}</Fragment>
}
if (!node) {
return null
}
switch (node.type) {
case 'h1':
return <h1 key={i}>{serialize(node.children)}</h1>
case 'h2':
return <h2 key={i}>{serialize(node.children)}</h2>
case 'h3':
return <h3 key={i}>{serialize(node.children)}</h3>
case 'h4':
return <h4 key={i}>{serialize(node.children)}</h4>
case 'h5':
return <h5 key={i}>{serialize(node.children)}</h5>
case 'h6':
return <h6 key={i}>{serialize(node.children)}</h6>
case 'blockquote':
return <blockquote key={i}>{serialize(node.children)}</blockquote>
case 'ul':
return <ul key={i}>{serialize(node.children)}</ul>
case 'ol':
return <ol key={i}>{serialize(node.children)}</ol>
case 'li':
return <li key={i}>{serialize(node.children)}</li>
case 'link':
return (
<CMSLink
url={escapeHTML(node.url)}
key={i}
reference={node.doc as any}
type={node.linkType as any}
label={node.label as any}
newTab={node.newTab as any}
appearance={node.appearance as any}
>
{serialize(node.children)}
</CMSLink>
)
default:
return <p key={i}>{serialize(node.children)}</p>
}
})
export default serialize

View File

@@ -0,0 +1,107 @@
$breakpoint: 1000px;
:root {
--max-width: 1600px;
--foreground-rgb: 0, 0, 0;
--background-rgb: 255, 255, 255;
--block-spacing: 2rem;
--gutter-h: 4rem;
--base: 1rem;
@media (max-width: $breakpoint) {
--block-spacing: 1rem;
--gutter-h: 2rem;
--base: 0.75rem;
}
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-rgb: 7, 7, 7;
}
}
* {
box-sizing: border-box;
}
html {
font-size: 20px;
line-height: 1.5;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
@media (max-width: $breakpoint) {
font-size: 16px;
}
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
}
body {
margin: 0;
color: rgb(var(--foreground-rgb));
background: rgb(var(--background-rgb));
}
img {
height: auto;
max-width: 100%;
display: block;
}
h1 {
font-size: 4.5rem;
line-height: 1.2;
margin: 0 0 2.5rem 0;
@media (max-width: $breakpoint) {
font-size: 3rem;
margin: 0 0 1.5rem 0;
}
}
h2 {
font-size: 3.5rem;
line-height: 1.2;
margin: 0 0 2.5rem 0;
}
h3 {
font-size: 2.5rem;
line-height: 1.2;
margin: 0 0 2rem 0;
}
h4 {
font-size: 1.5rem;
line-height: 1.2;
margin: 0 0 1rem 0;
}
h5 {
font-size: 1.25rem;
line-height: 1.2;
margin: 0 0 1rem 0;
}
h6 {
font-size: 1rem;
line-height: 1.2;
margin: 0 0 1rem 0;
}
a {
color: inherit;
text-decoration: none;
}
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
}

View File

@@ -0,0 +1,32 @@
import { AdminBar } from './_components/AdminBar'
import { Header } from './_components/Header'
import './app.scss'
export const metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default async function RootLayout(props: { children: React.ReactNode }) {
const { children } = props
return (
<html lang="en">
<body>
<AdminBar />
{/* The error ignored here is related to `@types/react` and `typescript` not
aligning with their implementations of Promise-based server components.
This can be removed once these dependencies are resolved in their respective modules.
- https://github.com/vercel/next.js/issues/42292
- https://github.com/vercel/next.js/issues/43537
Update: this is fixed in `@types/react` v18.2.14 but still requires `@ts-expect-error` to build :shrug:
See my comment here: https://github.com/vercel/next.js/issues/42292#issuecomment-1622979777
*/}
{/* @ts-expect-error */}
<Header />
{children}
</body>
</html>
)
}

View File

@@ -0,0 +1,3 @@
import Page from './[...slug]/page'
export default Page

View File

@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {}
module.exports = nextConfig

View File

@@ -0,0 +1,40 @@
{
"name": "payload-nested-docs-next-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev -p 3001",
"build": "next build",
"start": "next start -p 3001",
"lint": "next lint"
},
"dependencies": {
"escape-html": "^1.0.3",
"next": "^13.5.0",
"payload-admin-bar": "^1.0.6",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@next/eslint-plugin-next": "^13.4.8",
"@payloadcms/eslint-config": "^0.0.2",
"@types/escape-html": "^1.0.2",
"@types/node": "18.11.3",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@typescript-eslint/eslint-plugin": "^5.51.0",
"@typescript-eslint/parser": "^5.51.0",
"eslint": "8.41.0",
"eslint-config-next": "13.4.3",
"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",
"prettier": "^2.7.1",
"sass": "^1.62.1",
"slate": "^0.82.0",
"typescript": "^4.8.4"
}
}

View File

@@ -0,0 +1,98 @@
/* tslint:disable */
/* eslint-disable */
/**
* This file was automatically generated by Payload.
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
* and re-run `payload generate:types` to regenerate this file.
*/
export interface Config {
collections: {
pages: Page
users: User
'payload-preferences': PayloadPreference
'payload-migrations': PayloadMigration
}
globals: {
'main-menu': MainMenu
}
}
export interface Page {
id: string
title: string
fullTitle?: string | null
richText: {
[k: string]: unknown
}[]
slug: string
parent?: (string | null) | Page
breadcrumbs?:
| {
doc?: (string | null) | Page
url?: string | null
label?: string | null
id?: string | null
}[]
| null
updatedAt: string
createdAt: string
}
export interface User {
id: string
updatedAt: string
createdAt: string
email: string
resetPasswordToken?: string | null
resetPasswordExpiration?: string | null
salt?: string | null
hash?: string | null
loginAttempts?: number | null
lockUntil?: string | null
password: string | null
}
export interface PayloadPreference {
id: string
user: {
relationTo: 'users'
value: string | User
}
key?: string | null
value?:
| {
[k: string]: unknown
}
| unknown[]
| string
| number
| boolean
| null
updatedAt: string
createdAt: string
}
export interface PayloadMigration {
id: string
name?: string | null
batch?: number | null
updatedAt: string
createdAt: string
}
export interface MainMenu {
id: string
navItems?:
| {
link: {
type?: ('reference' | 'custom') | null
newTab?: boolean | null
reference?: {
relationTo: 'pages'
value: string | Page
} | null
url?: string | null
label: string
}
id?: string | null
}[]
| null
updatedAt?: string | null
createdAt?: string | null
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,15 @@
<svg width="260" height="260" viewBox="0 0 260 260" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
path {
fill: #0F0F0F;
}
@media (prefers-color-scheme: dark) {
path {
fill: white;
}
}
</style>
<path d="M120.59 8.5824L231.788 75.6142V202.829L148.039 251.418V124.203L36.7866 57.2249L120.59 8.5824Z" />
<path d="M112.123 244.353V145.073L28.2114 193.769L112.123 244.353Z" />
</svg>

After

Width:  |  Height:  |  Size: 437 B

View File

@@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

File diff suppressed because it is too large Load Diff