Files
payloadcms/test/config/int.spec.ts
Sasha f779e48a58 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
```
2025-02-21 18:15:27 +02:00

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()
})
})
})