fix: working bin configuration for custom scripts (#11294)
Previously, the `bin` configuration wasn't working at all.
Possibly because in an ESM environment this cannot work, because
`import` always returns an object with a default export under the
`module` key.
```ts
const script: BinScript = await import(pathToFileURL(userBinScript.scriptPath).toString())
await script(config)
```
Now, this works, but you must define a `script` export from your file.
Attached an integration test that asserts that it actually works. Added
documentation on how to use it, as previously it was missing.
This can be also helpful for plugins.
### Documentation
Using the `bin` configuration property, you can inject your own scripts
to `npx payload`.
Example for `pnpm payload seed`:
Step 1: create `seed.ts` file in the same folder with
`payload.config.ts` with:
```ts
import type { SanitizedConfig } from 'payload'
import payload from 'payload'
// Script must define a "script" function export that accepts the sanitized config
export const script = async (config: SanitizedConfig) => {
await payload.init({ config })
await payload.create({ collection: 'pages', data: { title: 'my title' } })
payload.logger.info('Succesffully seeded!')
process.exit(0)
}
```
Step 2: add the `seed` script to `bin`:
```ts
export default buildConfig({
bin: [
{
scriptPath: path.resolve(dirname, 'seed.ts'),
key: 'seed',
},
],
})
```
Now you can run the script using:
```sh
pnpm payload seed
```
This commit is contained in:
@@ -64,9 +64,9 @@ export default buildConfig({
|
||||
The following options are available:
|
||||
|
||||
| Option | Description |
|
||||
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`admin`** | The configuration options for the Admin Panel, including Custom Components, Live Preview, etc. [More details](../admin/overview#admin-options). |
|
||||
| **`bin`** | Register custom bin scripts for Payload to execute. |
|
||||
| **`bin`** | Register custom bin scripts for Payload to execute. [More Details](#custom-bin-scripts). |
|
||||
| **`editor`** | The Rich Text Editor which will be used by `richText` fields. [More details](../rich-text/overview). |
|
||||
| **`db`** * | The Database Adapter which will be used by Payload. [More details](../database/overview). |
|
||||
| **`serverURL`** | A string used to define the absolute URL of your app. This includes the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port. |
|
||||
@@ -265,3 +265,43 @@ The Payload Config can accept compatibility flags for running the newest version
|
||||
Payload localization works on a field-by-field basis. As you can nest fields within other fields, you could potentially nest a localized field within a localized field—but this would be redundant and unnecessary. There would be no reason to define a localized field within a localized parent field, given that the entire data structure from the parent field onward would be localized.
|
||||
|
||||
By default, Payload will remove the `localized: true` property from sub-fields if a parent field is localized. Set this compatibility flag to `true` only if you have an existing Payload MongoDB database from pre-3.0, and you have nested localized fields that you would like to maintain without migrating.
|
||||
|
||||
|
||||
## Custom bin scripts
|
||||
|
||||
Using the `bin` configuration property, you can inject your own scripts to `npx payload`.
|
||||
Example for `pnpm payload seed`:
|
||||
|
||||
|
||||
Step 1: create `seed.ts` file in the same folder with `payload.config.ts` with:
|
||||
|
||||
```ts
|
||||
import type { SanitizedConfig } from 'payload'
|
||||
|
||||
import payload from 'payload'
|
||||
|
||||
// Script must define a "script" function export that accepts the sanitized config
|
||||
export const script = async (config: SanitizedConfig) => {
|
||||
await payload.init({ config })
|
||||
await payload.create({ collection: 'pages', data: { title: 'my title' } })
|
||||
payload.logger.info('Succesffully seeded!')
|
||||
process.exit(0)
|
||||
}
|
||||
```
|
||||
|
||||
Step 2: add the `seed` script to `bin`:
|
||||
```ts
|
||||
export default buildConfig({
|
||||
bin: [
|
||||
{
|
||||
scriptPath: path.resolve(dirname, 'seed.ts'),
|
||||
key: 'seed',
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
Now you can run the command using:
|
||||
```sh
|
||||
pnpm payload seed
|
||||
```
|
||||
@@ -76,8 +76,18 @@ export const bin = async () => {
|
||||
|
||||
if (userBinScript) {
|
||||
try {
|
||||
const script: BinScript = await import(pathToFileURL(userBinScript.scriptPath).toString())
|
||||
await script(config)
|
||||
const module = await import(pathToFileURL(userBinScript.scriptPath).toString())
|
||||
|
||||
if (!module.script || typeof module.script !== 'function') {
|
||||
console.error(
|
||||
`Could not find "script" function export for script ${userBinScript.key} in ${userBinScript.scriptPath}`,
|
||||
)
|
||||
} else {
|
||||
await module.script(config).catch((err: unknown) => {
|
||||
console.log(`Script ${userBinScript.key} failed, details:`)
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`Could not find associated bin script for the ${userBinScript.key} command`)
|
||||
console.error(err)
|
||||
|
||||
8
test/config/bin.ts
Normal file
8
test/config/bin.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
const { bin } = await import(path.resolve(dirname, '../../packages/payload/src/bin/index.js'))
|
||||
|
||||
await bin()
|
||||
@@ -80,6 +80,12 @@ export default buildConfigWithDefaults({
|
||||
path: '/config',
|
||||
},
|
||||
],
|
||||
bin: [
|
||||
{
|
||||
scriptPath: path.resolve(dirname, 'customScript.ts'),
|
||||
key: 'start-server',
|
||||
},
|
||||
],
|
||||
globals: [
|
||||
{
|
||||
slug: 'my-global',
|
||||
@@ -107,6 +113,9 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
],
|
||||
onInit: async (payload) => {
|
||||
const { totalDocs } = await payload.count({ collection: 'users' })
|
||||
|
||||
if (totalDocs === 0) {
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
@@ -114,6 +123,7 @@ export default buildConfigWithDefaults({
|
||||
password: devUser.password,
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
|
||||
13
test/config/customScript.ts
Normal file
13
test/config/customScript.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { SanitizedConfig } from 'payload'
|
||||
|
||||
import { writeFileSync } from 'fs'
|
||||
import payload from 'payload'
|
||||
|
||||
import { testFilePath } from './testFilePath.js'
|
||||
|
||||
export const script = async (config: SanitizedConfig) => {
|
||||
await payload.init({ config })
|
||||
const data = await payload.find({ collection: 'users' })
|
||||
writeFileSync(testFilePath, JSON.stringify(data), 'utf-8')
|
||||
process.exit(0)
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
import type { BlockField, Payload } from 'payload'
|
||||
|
||||
import { execSync } from 'child_process'
|
||||
import { existsSync, readFileSync, rmSync } from 'fs'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
||||
|
||||
import { initPayloadInt } from '../helpers/initPayloadInt.js'
|
||||
import { testFilePath } from './testFilePath.js'
|
||||
|
||||
let restClient: NextRESTClient
|
||||
let payload: Payload
|
||||
@@ -106,4 +109,31 @@ describe('Config', () => {
|
||||
expect(response.headers.get('Access-Control-Allow-Headers')).toContain('x-custom-header')
|
||||
})
|
||||
})
|
||||
|
||||
describe('bin config', () => {
|
||||
const executeCLI = (command: string) => {
|
||||
execSync(`pnpm tsx "${path.resolve(dirname, 'bin.ts')}" ${command}`, {
|
||||
env: {
|
||||
...process.env,
|
||||
PAYLOAD_CONFIG_PATH: path.resolve(dirname, 'config.ts'),
|
||||
PAYLOAD_DROP_DATABASE: 'false',
|
||||
},
|
||||
stdio: 'inherit',
|
||||
cwd: path.resolve(dirname, '../..'), // from root
|
||||
})
|
||||
}
|
||||
|
||||
const deleteTestFile = () => {
|
||||
if (existsSync(testFilePath)) {
|
||||
rmSync(testFilePath)
|
||||
}
|
||||
}
|
||||
|
||||
it('should execute a custom script', () => {
|
||||
deleteTestFile()
|
||||
executeCLI('start-server')
|
||||
expect(JSON.parse(readFileSync(testFilePath, 'utf-8')).docs).toHaveLength(1)
|
||||
deleteTestFile()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
7
test/config/testFilePath.ts
Normal file
7
test/config/testFilePath.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export const testFilePath = path.resolve(dirname, '_data.json')
|
||||
Reference in New Issue
Block a user