Skip to content

Commit

Permalink
feat: plugin template (#10150)
Browse files Browse the repository at this point in the history
Updates the plugin template and adds it to the monorepo

Includes:
* Integration testing setup 
* Adding custom client / server components via a plugin
* The same building setup that we use for our plugins in the monorepo
* `create-payload-app` dynamically configures the project based on the
name:`dev/tsconfig.json`, `src/index.ts`, `dev/payload.config.ts`
For example, from project name: `payload-plugin-cool`
`src/index.ts`:
```ts
export type PayloadPluginCoolConfig = {
  /**
   * List of collections to add a custom field
   */
  collections?: Partial<Record<CollectionSlug, true>>
  disabled?: boolean
}

export const payloadPluginCool =
  (pluginOptions: PayloadPluginCoolConfig) =>
/// ...
```
`dev/tsconfig.json`:
```json
{
  "extends": "../tsconfig.json",
  "exclude": [],
  "include": [
    "**/*.ts",
    "**/*.tsx",
    "../src/**/*.ts",
    "../src/**/*.tsx",
    "next.config.mjs",
    ".next/types/**/*.ts"
  ],
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@payload-config": [
        "./payload.config.ts"
      ],
      "payload-plugin-cool": [
        "../src/index.ts"
      ],
      "payload-plugin-cool/client": [
        "../src/exports/client.ts"
      ],
      "payload-plugin-cool/rsc": [
        "../src/exports/rsc.ts"
      ]
    },
    "noEmit": true
  }
}

```

`./dev/payload.config.ts`
```
import { payloadPluginCool } from 'payload-plugin-cool'
///
 plugins: [
    payloadPluginCool({
      collections: {
        posts: true,
      },
    }),
  ],
```

Example of published plugin
https://www.npmjs.com/package/payload-plugin-cool
  • Loading branch information
r1tsuu authored Dec 27, 2024
1 parent 326b720 commit d8a62b7
Show file tree
Hide file tree
Showing 45 changed files with 1,569 additions and 17 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,8 @@ jobs:
- template: with-vercel-postgres
database: postgres

- template: plugin

# Re-enable once PG conncection is figured out
# - template: with-vercel-website
# database: postgres
Expand Down Expand Up @@ -467,6 +469,7 @@ jobs:
uses: supercharge/[email protected]
with:
mongodb-version: 6.0
if: matrix.database == 'mongodb'

- name: Build Template
run: |
Expand Down
5 changes: 2 additions & 3 deletions docs/plugins/build-your-own.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ keywords: plugins, template, config, configuration, extensions, custom, document
Building your own [Payload Plugin](./overview) is easy, and if you&apos;re already familiar with Payload then you&apos;ll have everything you need to get started. You can either start from scratch or use the [Plugin Template](#plugin-template) to get up and running quickly.

<Banner type="success">
To use the template, run `npx create-payload-app@latest -t plugin -n my-new-plugin` directly in
your terminal or [clone the template directly from
GitHub](https://github.com/payloadcms/payload-plugin-template).
To use the template, run `npx create-payload-app@latest --template plugin` directly in
your terminal.
</Banner>

Our plugin template includes everything you need to build a full life-cycle plugin:
Expand Down
46 changes: 46 additions & 0 deletions packages/create-payload-app/src/lib/configure-plugin-project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import fse from 'fs-extra'
import path from 'path'

import { toCamelCase, toPascalCase } from '../utils/casing.js'

/**
* Configures a plugin project by updating all package name placeholders to projectName
*/
export const configurePluginProject = ({
projectDirPath,
projectName,
}: {
projectDirPath: string
projectName: string
}) => {
const devPayloadConfigPath = path.resolve(projectDirPath, './dev/payload.config.ts')
const devTsConfigPath = path.resolve(projectDirPath, './dev/tsconfig.json')
const indexTsPath = path.resolve(projectDirPath, './src/index.ts')

const devPayloadConfig = fse.readFileSync(devPayloadConfigPath, 'utf8')
const devTsConfig = fse.readFileSync(devTsConfigPath, 'utf8')
const indexTs = fse.readFileSync(indexTsPath, 'utf-8')

const updatedTsConfig = devTsConfig.replaceAll('plugin-package-name-placeholder', projectName)
let updatedIndexTs = indexTs.replaceAll('plugin-package-name-placeholder', projectName)

const pluginExportVariableName = toCamelCase(projectName)

updatedIndexTs = updatedIndexTs.replace(
'export const myPlugin',
`export const ${pluginExportVariableName}`,
)

updatedIndexTs = updatedIndexTs.replaceAll('MyPluginConfig', `${toPascalCase(projectName)}Config`)

let updatedPayloadConfig = devPayloadConfig.replace(
'plugin-package-name-placeholder',
projectName,
)

updatedPayloadConfig = updatedPayloadConfig.replaceAll('myPlugin', pluginExportVariableName)

fse.writeFileSync(devPayloadConfigPath, updatedPayloadConfig)
fse.writeFileSync(devTsConfigPath, updatedTsConfig)
fse.writeFileSync(indexTsPath, updatedIndexTs)
}
5 changes: 3 additions & 2 deletions packages/create-payload-app/src/lib/create-project.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ describe('createProject', () => {
name: 'plugin',
type: 'plugin',
description: 'Template for creating a Payload plugin',
url: 'https://github.com/payloadcms/payload-plugin-template',
url: 'https://github.com/payloadcms/payload/templates/plugin',
}

await createProject({
cliArgs: args,
cliArgs: { ...args, '--local-template': 'plugin' } as CliArgs,
packageManager,
projectDir,
projectName,
Expand Down
17 changes: 12 additions & 5 deletions packages/create-payload-app/src/lib/create-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { CliArgs, DbDetails, PackageManager, ProjectTemplate } from '../typ
import { tryInitRepoAndCommit } from '../utils/git.js'
import { debug, error, info, warning } from '../utils/log.js'
import { configurePayloadConfig } from './configure-payload-config.js'
import { configurePluginProject } from './configure-plugin-project.js'
import { downloadTemplate } from './download-template.js'

const filename = fileURLToPath(import.meta.url)
Expand Down Expand Up @@ -93,11 +94,17 @@ export async function createProject(args: {
spinner.start('Checking latest Payload version...')

await updatePackageJSON({ projectDir, projectName })
spinner.message('Configuring Payload...')
await configurePayloadConfig({
dbType: dbDetails?.type,
projectDirOrConfigPath: { projectDir },
})

if (template.type === 'plugin') {
spinner.message('Configuring Plugin...')
configurePluginProject({ projectDirPath: projectDir, projectName })
} else {
spinner.message('Configuring Payload...')
await configurePayloadConfig({
dbType: dbDetails?.type,
projectDirOrConfigPath: { projectDir },
})
}

// Remove yarn.lock file. This is only desired in Payload Cloud.
const lockPath = path.resolve(projectDir, 'pnpm-lock.yaml')
Expand Down
13 changes: 6 additions & 7 deletions packages/create-payload-app/src/lib/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,11 @@ export function getValidTemplates(): ProjectTemplate[] {
description: 'Website Template',
url: `https://github.com/payloadcms/payload/templates/website#main`,
},

// {
// name: 'plugin',
// type: 'plugin',
// description: 'Template for creating a Payload plugin',
// url: 'https://github.com/payloadcms/plugin-template#beta',
// },
{
name: 'plugin',
type: 'plugin',
description: 'Template for creating a Payload plugin',
url: 'https://github.com/payloadcms/payload/templates/plugin#main',
},
]
}
14 changes: 14 additions & 0 deletions packages/create-payload-app/src/utils/casing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const toCamelCase = (str: string) => {
const s = str
.match(/[A-Z]{2,}(?=[A-Z][a-z]+\d*|\b)|[A-Z]?[a-z]+\d*|[A-Z]|\d+/g)
?.map((x) => x.slice(0, 1).toUpperCase() + x.slice(1).toLowerCase())
.join('')
return (s && s.slice(0, 1).toLowerCase() + s.slice(1)) ?? ''
}

export function toPascalCase(input: string): string {
return input
.replace(/[_-]+/g, ' ') // Replace underscores or hyphens with spaces
.replace(/(?:^|\s+)(\w)/g, (_, c) => c.toUpperCase()) // Capitalize first letter of each word
.replace(/\s+/g, '') // Remove all spaces
}
43 changes: 43 additions & 0 deletions templates/plugin/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

/.idea/*
!/.idea/runConfigurations

# testing
/coverage

# next.js
/.next/
/out/

# production
/build
/dist

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo

.env

/dev/media
6 changes: 6 additions & 0 deletions templates/plugin/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"semi": false
}
24 changes: 24 additions & 0 deletions templates/plugin/.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": true,
"jsc": {
"target": "esnext",
"parser": {
"syntax": "typescript",
"tsx": true,
"dts": true
},
"transform": {
"react": {
"runtime": "automatic",
"pragmaFrag": "React.Fragment",
"throwIfNamespace": true,
"development": false,
"useBuiltins": true
}
}
},
"module": {
"type": "es6"
}
}
3 changes: 3 additions & 0 deletions templates/plugin/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
}
24 changes: 24 additions & 0 deletions templates/plugin/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug full stack",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/next/dist/bin/next",
"runtimeArgs": ["--inspect"],
"skipFiles": ["<node_internals>/**"],
"serverReadyAction": {
"action": "debugWithChrome",
"killOnServerStop": true,
"pattern": "- Local:.+(https?://.+)",
"uriFormat": "%s",
"webRoot": "${workspaceFolder}"
},
"cwd": "${workspaceFolder}"
}
]
}
40 changes: 40 additions & 0 deletions templates/plugin/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"npm.packageManager": "pnpm",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"editor.formatOnSaveMode": "file",
"typescript.tsdk": "node_modules/typescript/lib",
"[javascript][typescript][typescriptreact]": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}
}
1 change: 1 addition & 0 deletions templates/plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Plugin
2 changes: 2 additions & 0 deletions templates/plugin/dev/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DATABASE_URI=mongodb://127.0.0.1/payload-plugin-template
PAYLOAD_SECRET=YOUR_SECRET_HERE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import type { Metadata } from 'next'

import config from '@payload-config'
import { generatePageMetadata, NotFoundPage } from '@payloadcms/next/views'

import { importMap } from '../importMap.js'

type Args = {
params: Promise<{
segments: string[]
}>
searchParams: Promise<{
[key: string]: string | string[]
}>
}

export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams })

const NotFound = ({ params, searchParams }: Args) =>
NotFoundPage({ config, importMap, params, searchParams })

export default NotFound
25 changes: 25 additions & 0 deletions templates/plugin/dev/app/(payload)/admin/[[...segments]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import type { Metadata } from 'next'

import config from '@payload-config'
import { generatePageMetadata, RootPage } from '@payloadcms/next/views'

import { importMap } from '../importMap.js'

type Args = {
params: Promise<{
segments: string[]
}>
searchParams: Promise<{
[key: string]: string | string[]
}>
}

export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams })

const Page = ({ params, searchParams }: Args) =>
RootPage({ config, importMap, params, searchParams })

export default Page
9 changes: 9 additions & 0 deletions templates/plugin/dev/app/(payload)/admin/importMap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { BeforeDashboardClient as BeforeDashboardClient_fc6e7dd366b9e2c8ce77d31252122343 } from 'plugin-package-name-placeholder/client'
import { BeforeDashboardServer as BeforeDashboardServer_c4406fcca100b2553312c5a3d7520a3f } from 'plugin-package-name-placeholder/rsc'

export const importMap = {
'plugin-package-name-placeholder/client#BeforeDashboardClient':
BeforeDashboardClient_fc6e7dd366b9e2c8ce77d31252122343,
'plugin-package-name-placeholder/rsc#BeforeDashboardServer':
BeforeDashboardServer_c4406fcca100b2553312c5a3d7520a3f,
}
19 changes: 19 additions & 0 deletions templates/plugin/dev/app/(payload)/api/[...slug]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import config from '@payload-config'
import '@payloadcms/next/css'
import {
REST_DELETE,
REST_GET,
REST_OPTIONS,
REST_PATCH,
REST_POST,
REST_PUT,
} from '@payloadcms/next/routes'

export const GET = REST_GET(config)
export const POST = REST_POST(config)
export const DELETE = REST_DELETE(config)
export const PATCH = REST_PATCH(config)
export const PUT = REST_PUT(config)
export const OPTIONS = REST_OPTIONS(config)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import config from '@payload-config'
import '@payloadcms/next/css'
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'

export const GET = GRAPHQL_PLAYGROUND_GET(config)
Loading

0 comments on commit d8a62b7

Please sign in to comment.