Skip to content

Commit f779e48

Browse files
authored
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 ```
1 parent 1dc748d commit f779e48

File tree

7 files changed

+162
-44
lines changed

7 files changed

+162
-44
lines changed

docs/configuration/overview.mdx

Lines changed: 75 additions & 35 deletions
Large diffs are not rendered by default.

packages/payload/src/bin/index.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,18 @@ export const bin = async () => {
7676

7777
if (userBinScript) {
7878
try {
79-
const script: BinScript = await import(pathToFileURL(userBinScript.scriptPath).toString())
80-
await script(config)
79+
const module = await import(pathToFileURL(userBinScript.scriptPath).toString())
80+
81+
if (!module.script || typeof module.script !== 'function') {
82+
console.error(
83+
`Could not find "script" function export for script ${userBinScript.key} in ${userBinScript.scriptPath}`,
84+
)
85+
} else {
86+
await module.script(config).catch((err: unknown) => {
87+
console.log(`Script ${userBinScript.key} failed, details:`)
88+
console.error(err)
89+
})
90+
}
8191
} catch (err) {
8292
console.log(`Could not find associated bin script for the ${userBinScript.key} command`)
8393
console.error(err)

test/config/bin.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import path from 'path'
2+
import { fileURLToPath } from 'url'
3+
4+
const filename = fileURLToPath(import.meta.url)
5+
const dirname = path.dirname(filename)
6+
const { bin } = await import(path.resolve(dirname, '../../packages/payload/src/bin/index.js'))
7+
8+
await bin()

test/config/config.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ export default buildConfigWithDefaults({
8080
path: '/config',
8181
},
8282
],
83+
bin: [
84+
{
85+
scriptPath: path.resolve(dirname, 'customScript.ts'),
86+
key: 'start-server',
87+
},
88+
],
8389
globals: [
8490
{
8591
slug: 'my-global',
@@ -107,13 +113,17 @@ export default buildConfigWithDefaults({
107113
},
108114
],
109115
onInit: async (payload) => {
110-
await payload.create({
111-
collection: 'users',
112-
data: {
113-
email: devUser.email,
114-
password: devUser.password,
115-
},
116-
})
116+
const { totalDocs } = await payload.count({ collection: 'users' })
117+
118+
if (totalDocs === 0) {
119+
await payload.create({
120+
collection: 'users',
121+
data: {
122+
email: devUser.email,
123+
password: devUser.password,
124+
},
125+
})
126+
}
117127
},
118128
typescript: {
119129
outputFile: path.resolve(dirname, 'payload-types.ts'),

test/config/customScript.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { SanitizedConfig } from 'payload'
2+
3+
import { writeFileSync } from 'fs'
4+
import payload from 'payload'
5+
6+
import { testFilePath } from './testFilePath.js'
7+
8+
export const script = async (config: SanitizedConfig) => {
9+
await payload.init({ config })
10+
const data = await payload.find({ collection: 'users' })
11+
writeFileSync(testFilePath, JSON.stringify(data), 'utf-8')
12+
process.exit(0)
13+
}

test/config/int.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import type { BlockField, Payload } from 'payload'
22

3+
import { execSync } from 'child_process'
4+
import { existsSync, readFileSync, rmSync } from 'fs'
35
import path from 'path'
46
import { fileURLToPath } from 'url'
57

68
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
79

810
import { initPayloadInt } from '../helpers/initPayloadInt.js'
11+
import { testFilePath } from './testFilePath.js'
912

1013
let restClient: NextRESTClient
1114
let payload: Payload
@@ -106,4 +109,31 @@ describe('Config', () => {
106109
expect(response.headers.get('Access-Control-Allow-Headers')).toContain('x-custom-header')
107110
})
108111
})
112+
113+
describe('bin config', () => {
114+
const executeCLI = (command: string) => {
115+
execSync(`pnpm tsx "${path.resolve(dirname, 'bin.ts')}" ${command}`, {
116+
env: {
117+
...process.env,
118+
PAYLOAD_CONFIG_PATH: path.resolve(dirname, 'config.ts'),
119+
PAYLOAD_DROP_DATABASE: 'false',
120+
},
121+
stdio: 'inherit',
122+
cwd: path.resolve(dirname, '../..'), // from root
123+
})
124+
}
125+
126+
const deleteTestFile = () => {
127+
if (existsSync(testFilePath)) {
128+
rmSync(testFilePath)
129+
}
130+
}
131+
132+
it('should execute a custom script', () => {
133+
deleteTestFile()
134+
executeCLI('start-server')
135+
expect(JSON.parse(readFileSync(testFilePath, 'utf-8')).docs).toHaveLength(1)
136+
deleteTestFile()
137+
})
138+
})
109139
})

test/config/testFilePath.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import path from 'path'
2+
import { fileURLToPath } from 'url'
3+
4+
const filename = fileURLToPath(import.meta.url)
5+
const dirname = path.dirname(filename)
6+
7+
export const testFilePath = path.resolve(dirname, '_data.json')

0 commit comments

Comments
 (0)