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
```
140 lines
4.0 KiB
TypeScript
140 lines
4.0 KiB
TypeScript
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
|
|
|
|
const filename = fileURLToPath(import.meta.url)
|
|
const dirname = path.dirname(filename)
|
|
|
|
describe('Config', () => {
|
|
beforeAll(async () => {
|
|
;({ payload, restClient } = await initPayloadInt(dirname))
|
|
})
|
|
|
|
afterAll(async () => {
|
|
if (typeof payload.db.destroy === 'function') {
|
|
await payload.db.destroy()
|
|
}
|
|
})
|
|
|
|
describe('payload config', () => {
|
|
it('allows a custom field at the config root', () => {
|
|
const { config } = payload
|
|
expect(config.custom).toEqual({
|
|
name: 'Customer portal',
|
|
})
|
|
})
|
|
|
|
it('allows a custom field in the root endpoints', () => {
|
|
const [endpoint] = payload.config.endpoints
|
|
|
|
expect(endpoint.custom).toEqual({
|
|
description: 'Get the sanitized payload config',
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('collection config', () => {
|
|
it('allows a custom field in collections', () => {
|
|
const [collection] = payload.config.collections
|
|
expect(collection.custom).toEqual({
|
|
externalLink: 'https://foo.bar',
|
|
})
|
|
})
|
|
|
|
it('allows a custom field in collection endpoints', () => {
|
|
const [collection] = payload.config.collections
|
|
const [endpoint] = collection.endpoints || []
|
|
|
|
expect(endpoint.custom).toEqual({
|
|
examples: [{ type: 'response', value: { message: 'hi' } }],
|
|
})
|
|
})
|
|
|
|
it('allows a custom field in collection fields', () => {
|
|
const [collection] = payload.config.collections
|
|
const [field] = collection.fields
|
|
|
|
expect(field.custom).toEqual({
|
|
description: 'The title of this page',
|
|
})
|
|
})
|
|
|
|
it('allows a custom field in blocks in collection fields', () => {
|
|
const [collection] = payload.config.collections
|
|
const [, blocksField] = collection.fields
|
|
|
|
expect((blocksField as BlockField).blocks[0].custom).toEqual({
|
|
description: 'The blockOne of this page',
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('global config', () => {
|
|
it('allows a custom field in globals', () => {
|
|
const [global] = payload.config.globals
|
|
expect(global.custom).toEqual({ foo: 'bar' })
|
|
})
|
|
|
|
it('allows a custom field in global endpoints', () => {
|
|
const [global] = payload.config.globals
|
|
const [endpoint] = global.endpoints || []
|
|
|
|
expect(endpoint.custom).toEqual({ params: [{ name: 'name', type: 'string', in: 'query' }] })
|
|
})
|
|
|
|
it('allows a custom field in global fields', () => {
|
|
const [global] = payload.config.globals
|
|
const [field] = global.fields
|
|
|
|
expect(field.custom).toEqual({
|
|
description: 'The title of my global',
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('cors config', () => {
|
|
it('includes a custom header in Access-Control-Allow-Headers', async () => {
|
|
const response = await restClient.GET(`/pages`)
|
|
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()
|
|
})
|
|
})
|
|
})
|