Skip to content

Commit d8a62b7

Browse files
authored
feat: plugin template (#10150)
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
1 parent 326b720 commit d8a62b7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1569
-17
lines changed

.github/workflows/main.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,8 @@ jobs:
414414
- template: with-vercel-postgres
415415
database: postgres
416416

417+
- template: plugin
418+
417419
# Re-enable once PG conncection is figured out
418420
# - template: with-vercel-website
419421
# database: postgres
@@ -467,6 +469,7 @@ jobs:
467469
uses: supercharge/mongodb-github-action@1.11.0
468470
with:
469471
mongodb-version: 6.0
472+
if: matrix.database == 'mongodb'
470473

471474
- name: Build Template
472475
run: |

docs/plugins/build-your-own.mdx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ keywords: plugins, template, config, configuration, extensions, custom, document
99
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.
1010

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

1716
Our plugin template includes everything you need to build a full life-cycle plugin:
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import fse from 'fs-extra'
2+
import path from 'path'
3+
4+
import { toCamelCase, toPascalCase } from '../utils/casing.js'
5+
6+
/**
7+
* Configures a plugin project by updating all package name placeholders to projectName
8+
*/
9+
export const configurePluginProject = ({
10+
projectDirPath,
11+
projectName,
12+
}: {
13+
projectDirPath: string
14+
projectName: string
15+
}) => {
16+
const devPayloadConfigPath = path.resolve(projectDirPath, './dev/payload.config.ts')
17+
const devTsConfigPath = path.resolve(projectDirPath, './dev/tsconfig.json')
18+
const indexTsPath = path.resolve(projectDirPath, './src/index.ts')
19+
20+
const devPayloadConfig = fse.readFileSync(devPayloadConfigPath, 'utf8')
21+
const devTsConfig = fse.readFileSync(devTsConfigPath, 'utf8')
22+
const indexTs = fse.readFileSync(indexTsPath, 'utf-8')
23+
24+
const updatedTsConfig = devTsConfig.replaceAll('plugin-package-name-placeholder', projectName)
25+
let updatedIndexTs = indexTs.replaceAll('plugin-package-name-placeholder', projectName)
26+
27+
const pluginExportVariableName = toCamelCase(projectName)
28+
29+
updatedIndexTs = updatedIndexTs.replace(
30+
'export const myPlugin',
31+
`export const ${pluginExportVariableName}`,
32+
)
33+
34+
updatedIndexTs = updatedIndexTs.replaceAll('MyPluginConfig', `${toPascalCase(projectName)}Config`)
35+
36+
let updatedPayloadConfig = devPayloadConfig.replace(
37+
'plugin-package-name-placeholder',
38+
projectName,
39+
)
40+
41+
updatedPayloadConfig = updatedPayloadConfig.replaceAll('myPlugin', pluginExportVariableName)
42+
43+
fse.writeFileSync(devPayloadConfigPath, updatedPayloadConfig)
44+
fse.writeFileSync(devTsConfigPath, updatedTsConfig)
45+
fse.writeFileSync(indexTsPath, updatedIndexTs)
46+
}

packages/create-payload-app/src/lib/create-project.spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@ describe('createProject', () => {
4444
name: 'plugin',
4545
type: 'plugin',
4646
description: 'Template for creating a Payload plugin',
47-
url: 'https://github.com/payloadcms/payload-plugin-template',
47+
url: 'https://github.com/payloadcms/payload/templates/plugin',
4848
}
49+
4950
await createProject({
50-
cliArgs: args,
51+
cliArgs: { ...args, '--local-template': 'plugin' } as CliArgs,
5152
packageManager,
5253
projectDir,
5354
projectName,

packages/create-payload-app/src/lib/create-project.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { CliArgs, DbDetails, PackageManager, ProjectTemplate } from '../typ
1010
import { tryInitRepoAndCommit } from '../utils/git.js'
1111
import { debug, error, info, warning } from '../utils/log.js'
1212
import { configurePayloadConfig } from './configure-payload-config.js'
13+
import { configurePluginProject } from './configure-plugin-project.js'
1314
import { downloadTemplate } from './download-template.js'
1415

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

9596
await updatePackageJSON({ projectDir, projectName })
96-
spinner.message('Configuring Payload...')
97-
await configurePayloadConfig({
98-
dbType: dbDetails?.type,
99-
projectDirOrConfigPath: { projectDir },
100-
})
97+
98+
if (template.type === 'plugin') {
99+
spinner.message('Configuring Plugin...')
100+
configurePluginProject({ projectDirPath: projectDir, projectName })
101+
} else {
102+
spinner.message('Configuring Payload...')
103+
await configurePayloadConfig({
104+
dbType: dbDetails?.type,
105+
projectDirOrConfigPath: { projectDir },
106+
})
107+
}
101108

102109
// Remove yarn.lock file. This is only desired in Payload Cloud.
103110
const lockPath = path.resolve(projectDir, 'pnpm-lock.yaml')

packages/create-payload-app/src/lib/templates.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,11 @@ export function getValidTemplates(): ProjectTemplate[] {
2727
description: 'Website Template',
2828
url: `https://github.com/payloadcms/payload/templates/website#main`,
2929
},
30-
31-
// {
32-
// name: 'plugin',
33-
// type: 'plugin',
34-
// description: 'Template for creating a Payload plugin',
35-
// url: 'https://github.com/payloadcms/plugin-template#beta',
36-
// },
30+
{
31+
name: 'plugin',
32+
type: 'plugin',
33+
description: 'Template for creating a Payload plugin',
34+
url: 'https://github.com/payloadcms/payload/templates/plugin#main',
35+
},
3736
]
3837
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export const toCamelCase = (str: string) => {
2+
const s = str
3+
.match(/[A-Z]{2,}(?=[A-Z][a-z]+\d*|\b)|[A-Z]?[a-z]+\d*|[A-Z]|\d+/g)
4+
?.map((x) => x.slice(0, 1).toUpperCase() + x.slice(1).toLowerCase())
5+
.join('')
6+
return (s && s.slice(0, 1).toLowerCase() + s.slice(1)) ?? ''
7+
}
8+
9+
export function toPascalCase(input: string): string {
10+
return input
11+
.replace(/[_-]+/g, ' ') // Replace underscores or hyphens with spaces
12+
.replace(/(?:^|\s+)(\w)/g, (_, c) => c.toUpperCase()) // Capitalize first letter of each word
13+
.replace(/\s+/g, '') // Remove all spaces
14+
}

templates/plugin/.gitignore

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
.yarn/install-state.gz
8+
9+
/.idea/*
10+
!/.idea/runConfigurations
11+
12+
# testing
13+
/coverage
14+
15+
# next.js
16+
/.next/
17+
/out/
18+
19+
# production
20+
/build
21+
/dist
22+
23+
# misc
24+
.DS_Store
25+
*.pem
26+
27+
# debug
28+
npm-debug.log*
29+
yarn-debug.log*
30+
yarn-error.log*
31+
32+
# local env files
33+
.env*.local
34+
35+
# vercel
36+
.vercel
37+
38+
# typescript
39+
*.tsbuildinfo
40+
41+
.env
42+
43+
/dev/media

templates/plugin/.prettierrc.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"singleQuote": true,
3+
"trailingComma": "all",
4+
"printWidth": 100,
5+
"semi": false
6+
}

templates/plugin/.swcrc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"$schema": "https://json.schemastore.org/swcrc",
3+
"sourceMaps": true,
4+
"jsc": {
5+
"target": "esnext",
6+
"parser": {
7+
"syntax": "typescript",
8+
"tsx": true,
9+
"dts": true
10+
},
11+
"transform": {
12+
"react": {
13+
"runtime": "automatic",
14+
"pragmaFrag": "React.Fragment",
15+
"throwIfNamespace": true,
16+
"development": false,
17+
"useBuiltins": true
18+
}
19+
}
20+
},
21+
"module": {
22+
"type": "es6"
23+
}
24+
}

0 commit comments

Comments
 (0)