feat: plugin-import-export initial work (#10795)

Adds new plugin-import-export initial version.

Allows for direct download and creation of downloadable collection data
stored to a json or csv uses the access control of the user creating the
request to make the file.

config options:
```ts
  /**
   * Collections to include the Import/Export controls in
   * Defaults to all collections
   */
  collections?: string[]
  /**
   * Enable to force the export to run synchronously
   */
  disableJobsQueue?: boolean
  /**
   * This function takes the default export collection configured in the plugin and allows you to override it by modifying and returning it
   * @param collection
   * @returns collection
   */
  overrideExportCollection?: (collection: CollectionOverride) => CollectionOverride

// payload.config.ts:
plugins: [
    importExportPlugin({
      collections: ['pages', 'users'],
      overrideExportCollection: (collection) => {
        collection.admin.group = 'System'
        collection.upload.staticDir = path.resolve(dirname, 'uploads')
        return collection
      },
      disableJobsQueue: true,
    }),
 ],
```

---------

Co-authored-by: Jessica Chowdhury <jessica@trbl.design>
Co-authored-by: Kendell Joseph <kendelljoseph@gmail.com>
This commit is contained in:
Dan Ribbens
2025-03-04 20:06:43 -05:00
committed by GitHub
parent cc05937633
commit 4f822a439b
61 changed files with 3345 additions and 8 deletions

View File

@@ -1232,6 +1232,7 @@ export interface Auth {
declare module 'payload' {
// @ts-ignore
// @ts-ignore
export interface GeneratedTypes extends Config {}
}
}

View File

@@ -41,6 +41,7 @@
"@payloadcms/payload-cloud": "workspace:*",
"@payloadcms/plugin-cloud-storage": "workspace:*",
"@payloadcms/plugin-form-builder": "workspace:*",
"@payloadcms/plugin-import-export": "workspace:*",
"@payloadcms/plugin-multi-tenant": "workspace:*",
"@payloadcms/plugin-nested-docs": "workspace:*",
"@payloadcms/plugin-redirects": "workspace:*",
@@ -65,6 +66,7 @@
"babel-plugin-react-compiler": "19.0.0-beta-714736e-20250131",
"comment-json": "^4.2.3",
"create-payload-app": "workspace:*",
"csv-parse": "^5.6.0",
"dequal": "2.0.3",
"dotenv": "16.4.7",
"drizzle-kit": "0.28.0",

1
test/plugin-import-export/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
uploads

View File

@@ -0,0 +1,117 @@
import type { CollectionConfig } from 'payload'
import { pagesSlug } from '../shared.js'
export const Pages: CollectionConfig = {
slug: pagesSlug,
labels: {
singular: 'Page',
plural: 'Pages',
},
admin: {
useAsTitle: 'title',
},
versions: {
drafts: true,
},
fields: [
{
name: 'title',
label: 'Title',
type: 'text',
required: true,
},
{
name: 'localized',
type: 'text',
localized: true,
},
{
name: 'group',
type: 'group',
fields: [
{
name: 'value',
type: 'text',
defaultValue: 'group value',
},
{
name: 'ignore',
type: 'text',
},
{
name: 'array',
type: 'array',
fields: [
{
name: 'field1',
type: 'text',
},
{
name: 'field2',
type: 'text',
},
],
},
],
},
{
name: 'array',
type: 'array',
fields: [
{
name: 'field1',
type: 'text',
},
{
name: 'field2',
type: 'text',
},
],
},
{
name: 'blocks',
type: 'blocks',
blocks: [
{
slug: 'hero',
fields: [
{
name: 'title',
type: 'text',
},
],
},
{
slug: 'content',
fields: [
{
name: 'richText',
type: 'richText',
},
],
},
],
},
{
name: 'author',
type: 'relationship',
relationTo: 'users',
},
{
name: 'hasManyNumber',
type: 'number',
hasMany: true,
},
{
name: 'relationship',
type: 'relationship',
relationTo: 'users',
},
{
name: 'excerpt',
label: 'Excerpt',
type: 'text',
},
],
}

View File

@@ -0,0 +1,16 @@
import type { CollectionConfig } from 'payload'
export const Users: CollectionConfig = {
slug: 'users',
auth: true,
admin: {
useAsTitle: 'email',
},
access: {
read: () => true,
},
fields: [
// Email added by default
// Add more fields as needed
],
}

View File

@@ -0,0 +1,51 @@
import { fileURLToPath } from 'node:url'
import path from 'path'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
import { importExportPlugin } from '@payloadcms/plugin-import-export'
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { Pages } from './collections/Pages.js'
import { Users } from './collections/Users.js'
import { seed } from './seed/index.js'
export default buildConfigWithDefaults({
admin: {
importMap: {
baseDir: path.resolve(dirname),
},
},
collections: [Users, Pages],
localization: {
defaultLocale: 'en',
fallback: true,
locales: ['en', 'es', 'de'],
},
onInit: async (payload) => {
await seed(payload)
},
plugins: [
importExportPlugin({
overrideExportCollection: (collection) => {
collection.admin.group = 'System'
collection.upload.staticDir = path.resolve(dirname, 'uploads')
return collection
},
disableJobsQueue: true,
}),
importExportPlugin({
collections: ['pages'],
overrideExportCollection: (collection) => {
collection.slug = 'exports-tasks'
if (collection.admin) {
collection.admin.group = 'System'
}
collection.upload.staticDir = path.resolve(dirname, 'uploads')
return collection
},
}),
],
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
})

View File

@@ -0,0 +1,50 @@
import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import * as path from 'path'
import { fileURLToPath } from 'url'
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
import type { Config } from './payload-types.js'
import { ensureCompilationIsDone, initPageConsoleErrorCatch } from '../helpers.js'
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
test.describe('Import Export', () => {
let page: Page
let pagesURL: AdminUrlUtil
let payload: PayloadTestSDK<Config>
test.beforeAll(async ({ browser }, testInfo) => {
testInfo.setTimeout(TEST_TIMEOUT_LONG)
const { payload: payloadFromInit, serverURL } = await initPayloadE2ENoConfig<Config>({
dirname,
})
pagesURL = new AdminUrlUtil(serverURL, 'pages')
payload = payloadFromInit
const context = await browser.newContext()
page = await context.newPage()
initPageConsoleErrorCatch(page)
await ensureCompilationIsDone({ page, serverURL })
})
test.describe('Import', () => {
test('works', async () => {
// TODO: write e2e tests
})
})
test.describe('Export', () => {
test('works', async () => {
// TODO: write e2e tests
})
})
})

View File

@@ -0,0 +1,19 @@
import { rootParserOptions } from '../../eslint.config.js'
import testEslintConfig from '../eslint.config.js'
/** @typedef {import('eslint').Linter.Config} Config */
/** @type {Config[]} */
export const index = [
...testEslintConfig,
{
languageOptions: {
parserOptions: {
tsconfigRootDir: import.meta.dirname,
...rootParserOptions,
},
},
},
]
export default index

View File

@@ -0,0 +1,51 @@
import { parse } from 'csv-parse'
import fs from 'fs'
export const readCSV = async (path: string): Promise<any[]> => {
const buffer = fs.readFileSync(path)
const data: any[] = []
const promise = new Promise<void>((resolve) => {
const parser = parse({ bom: true, columns: true })
// Collect data from the CSV
parser.on('readable', () => {
let record
while ((record = parser.read())) {
data.push(record)
}
})
// Resolve the promise on 'end'
parser.on('end', () => {
resolve()
})
// Handle errors (optional, but good practice)
parser.on('error', (error) => {
console.error('Error parsing CSV:', error)
resolve() // Ensures promise doesn't hang on error
})
// Pipe the buffer into the parser
parser.write(buffer)
parser.end()
})
// Await the promise
await promise
return data
}
export const readJSON = async (path: string): Promise<any[]> => {
const buffer = fs.readFileSync(path)
const str = buffer.toString()
try {
const json = await JSON.parse(str)
return json
} catch (error) {
console.error('Error reading JSON file:', error)
throw error
}
}

View File

@@ -0,0 +1,400 @@
import type { CollectionSlug, Payload } from 'payload'
import path from 'path'
import { fileURLToPath } from 'url'
import { devUser } from '../credentials.js'
import { initPayloadInt } from '../helpers/initPayloadInt.js'
import { readCSV, readJSON } from './helpers.js'
import { richTextData } from './seed/richTextData.js'
let payload: Payload
let user: any
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
describe('@payloadcms/plugin-import-export', () => {
beforeAll(async () => {
;({ payload } = (await initPayloadInt(dirname)) as { payload: Payload })
user = await payload.login({
collection: 'users',
data: {
email: devUser.email,
password: devUser.password,
},
})
})
afterAll(async () => {
if (typeof payload.db.destroy === 'function') {
await payload.db.destroy()
}
})
describe('exports', () => {
it('should create a file for collection csv from defined fields', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
sort: 'createdAt',
fields: ['id', 'title', 'group.value', 'group.array.field1', 'createdAt', 'updatedAt'],
format: 'csv',
where: {
title: { contains: 'Title ' },
},
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toContain('pages.csv')
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].id).toBeDefined()
expect(data[0].title).toStrictEqual('Title 0')
expect(data[0].group_value).toStrictEqual('group value')
expect(data[0].group_ignore).toBeUndefined()
expect(data[0].group_array_0_field1).toStrictEqual('test')
expect(data[0].createdAt).toBeDefined()
expect(data[0].updatedAt).toBeDefined()
})
it('should create a file for collection csv from one locale', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'localized'],
locale: 'en',
format: 'csv',
where: {
title: { contains: 'Localized ' },
},
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].id).toBeDefined()
expect(data[0].localized).toStrictEqual('en test')
})
it('should create a file for collection csv from multiple locales', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'localized'],
locale: 'all',
format: 'csv',
where: {
title: { contains: 'Localized ' },
},
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].id).toBeDefined()
expect(data[0].localized_en).toStrictEqual('en test')
expect(data[0].localized_es).toStrictEqual('es test')
})
it('should create a file for collection csv from array', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'array'],
format: 'csv',
where: {
title: { contains: 'Array ' },
},
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].array_0_field1).toStrictEqual('foo')
expect(data[0].array_0_field2).toStrictEqual('bar')
expect(data[0].array_1_field1).toStrictEqual('foo')
expect(data[0].array_1_field2).toStrictEqual('baz')
})
it('should create a file for collection csv from array.subfield', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'array.field1'],
format: 'csv',
where: {
title: { contains: 'Array Subfield ' },
},
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].array_0_field1).toStrictEqual('foo')
expect(data[0].array_0_field2).toBeUndefined()
expect(data[0].array_1_field1).toStrictEqual('foo')
expect(data[0].array_1_field2).toBeUndefined()
})
it('should create a file for collection csv from hasMany field', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'hasManyNumber'],
format: 'csv',
where: {
title: { contains: 'hasMany Number ' },
},
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].hasManyNumber_0).toStrictEqual('0')
expect(data[0].hasManyNumber_1).toStrictEqual('1')
expect(data[0].hasManyNumber_2).toStrictEqual('1')
expect(data[0].hasManyNumber_3).toStrictEqual('2')
expect(data[0].hasManyNumber_4).toStrictEqual('3')
})
it('should create a file for collection csv from blocks field', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'blocks'],
format: 'csv',
where: {
title: { contains: 'Blocks ' },
},
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].blocks_0_blockType).toStrictEqual('hero')
expect(data[0].blocks_1_blockType).toStrictEqual('content')
})
it('should create a JSON file for collection', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'title'],
format: 'json',
sort: 'title',
where: {
title: { contains: 'JSON ' },
},
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readJSON(expectedPath)
expect(data[0].title).toStrictEqual('JSON 0')
})
it('should create an export with every field when no fields are defined', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
format: 'json',
sort: 'title',
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readJSON(expectedPath)
expect(data[0].id).toBeDefined()
expect(data[0].title).toBeDefined()
expect(data[0].createdAt).toBeDefined()
expect(data[0].updatedAt).toBeDefined()
})
it('should create jobs task for exports', async () => {
const doc = await payload.create({
collection: 'exports-tasks' as CollectionSlug,
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'title'],
format: 'csv',
sort: 'title',
where: {
title: { contains: 'Jobs ' },
},
},
})
const { docs } = await payload.find({
collection: 'payload-jobs' as CollectionSlug,
})
const job = docs[0]
expect(job).toBeDefined()
const { input } = job
expect(input.id).toBeDefined()
expect(input.name).toBeDefined()
expect(input.format).toStrictEqual('csv')
expect(input.locale).toStrictEqual('all')
expect(input.fields).toStrictEqual(['id', 'title'])
expect(input.collectionSlug).toStrictEqual('pages')
expect(input.exportsCollection).toStrictEqual('exports-tasks')
expect(input.user).toBeDefined()
expect(input.userCollection).toBeDefined()
await payload.jobs.run()
const exportDoc = await payload.findByID({
collection: 'exports-tasks' as CollectionSlug,
id: doc.id,
})
expect(exportDoc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', exportDoc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].title).toStrictEqual('Jobs 0')
})
// disabled so we don't always run a massive test
it.skip('should create a file from a large set of collection documents', async () => {
const allPromises = []
let promises = []
for (let i = 0; i < 100000; i++) {
promises.push(
payload.create({
collectionSlug: 'pages',
data: {
title: `Array ${i}`,
blocks: [
{
blockType: 'hero',
title: 'test',
},
{
blockType: 'content',
richText: richTextData,
},
],
},
}),
)
if (promises.length >= 500) {
await Promise.all(promises)
promises = []
}
if (i % 1000 === 0) {
console.log('created', i)
}
}
await Promise.all(promises)
console.log('seeded')
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'blocks'],
format: 'csv',
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].blocks_0_blockType).toStrictEqual('hero')
expect(data[0].blocks_1_blockType).toStrictEqual('content')
})
})
})

View File

@@ -0,0 +1,670 @@
/* tslint:disable */
/* eslint-disable */
/**
* This file was automatically generated by Payload.
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
* and re-run `payload generate:types` to regenerate this file.
*/
/**
* Supported timezones in IANA format.
*
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "supportedTimezones".
*/
export type SupportedTimezones =
| 'Pacific/Midway'
| 'Pacific/Niue'
| 'Pacific/Honolulu'
| 'Pacific/Rarotonga'
| 'America/Anchorage'
| 'Pacific/Gambier'
| 'America/Los_Angeles'
| 'America/Tijuana'
| 'America/Denver'
| 'America/Phoenix'
| 'America/Chicago'
| 'America/Guatemala'
| 'America/New_York'
| 'America/Bogota'
| 'America/Caracas'
| 'America/Santiago'
| 'America/Buenos_Aires'
| 'America/Sao_Paulo'
| 'Atlantic/South_Georgia'
| 'Atlantic/Azores'
| 'Atlantic/Cape_Verde'
| 'Europe/London'
| 'Europe/Berlin'
| 'Africa/Lagos'
| 'Europe/Athens'
| 'Africa/Cairo'
| 'Europe/Moscow'
| 'Asia/Riyadh'
| 'Asia/Dubai'
| 'Asia/Baku'
| 'Asia/Karachi'
| 'Asia/Tashkent'
| 'Asia/Calcutta'
| 'Asia/Dhaka'
| 'Asia/Almaty'
| 'Asia/Jakarta'
| 'Asia/Bangkok'
| 'Asia/Shanghai'
| 'Asia/Singapore'
| 'Asia/Tokyo'
| 'Asia/Seoul'
| 'Australia/Sydney'
| 'Pacific/Guam'
| 'Pacific/Noumea'
| 'Pacific/Auckland'
| 'Pacific/Fiji';
export interface Config {
auth: {
users: UserAuthOperations;
};
blocks: {};
collections: {
users: User;
pages: Page;
exports: Export;
'exports-tasks': ExportsTask;
'payload-jobs': PayloadJob;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
collectionsJoins: {};
collectionsSelect: {
users: UsersSelect<false> | UsersSelect<true>;
pages: PagesSelect<false> | PagesSelect<true>;
exports: ExportsSelect<false> | ExportsSelect<true>;
'exports-tasks': ExportsTasksSelect<false> | ExportsTasksSelect<true>;
'payload-jobs': PayloadJobsSelect<false> | PayloadJobsSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
};
db: {
defaultIDType: string;
};
globals: {};
globalsSelect: {};
locale: 'en' | 'es' | 'de';
user: User & {
collection: 'users';
};
jobs: {
tasks: {
createCollectionExport: TaskCreateCollectionExport;
inline: {
input: unknown;
output: unknown;
};
};
workflows: unknown;
};
}
export interface UserAuthOperations {
forgotPassword: {
email: string;
password: string;
};
login: {
email: string;
password: string;
};
registerFirstUser: {
email: string;
password: string;
};
unlock: {
email: string;
password: string;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
*/
export interface User {
id: string;
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "pages".
*/
export interface Page {
id: string;
title: string;
localized?: string | null;
group?: {
value?: string | null;
ignore?: string | null;
array?:
| {
field1?: string | null;
field2?: string | null;
id?: string | null;
}[]
| null;
};
array?:
| {
field1?: string | null;
field2?: string | null;
id?: string | null;
}[]
| null;
blocks?:
| (
| {
title?: string | null;
id?: string | null;
blockName?: string | null;
blockType: 'hero';
}
| {
richText?: {
root: {
type: string;
children: {
type: string;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
id?: string | null;
blockName?: string | null;
blockType: 'content';
}
)[]
| null;
author?: (string | null) | User;
hasManyNumber?: number[] | null;
relationship?: (string | null) | User;
excerpt?: string | null;
updatedAt: string;
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "exports".
*/
export interface Export {
id: string;
name?: string | null;
format: 'csv' | 'json';
limit?: number | null;
sort?: string | null;
locale?: ('all' | 'en' | 'es' | 'de') | null;
drafts?: ('true' | 'false') | null;
selectionToUse?: ('currentSelection' | 'currentFilters' | 'all') | null;
fields?: string[] | null;
collectionSlug: string;
where?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
updatedAt: string;
createdAt: string;
url?: string | null;
thumbnailURL?: string | null;
filename?: string | null;
mimeType?: string | null;
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "exports-tasks".
*/
export interface ExportsTask {
id: string;
name?: string | null;
format: 'csv' | 'json';
limit?: number | null;
sort?: string | null;
locale?: ('all' | 'en' | 'es' | 'de') | null;
drafts?: ('true' | 'false') | null;
selectionToUse?: ('currentSelection' | 'currentFilters' | 'all') | null;
fields?: string[] | null;
collectionSlug: string;
where?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
updatedAt: string;
createdAt: string;
url?: string | null;
thumbnailURL?: string | null;
filename?: string | null;
mimeType?: string | null;
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-jobs".
*/
export interface PayloadJob {
id: string;
/**
* Input data provided to the job
*/
input?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
taskStatus?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
completedAt?: string | null;
totalTried?: number | null;
/**
* If hasError is true this job will not be retried
*/
hasError?: boolean | null;
/**
* If hasError is true, this is the error that caused it
*/
error?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
/**
* Task execution log
*/
log?:
| {
executedAt: string;
completedAt: string;
taskSlug: 'inline' | 'createCollectionExport';
taskID: string;
input?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
output?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
state: 'failed' | 'succeeded';
error?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
id?: string | null;
}[]
| null;
taskSlug?: ('inline' | 'createCollectionExport') | null;
queue?: string | null;
waitUntil?: string | null;
processing?: boolean | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents".
*/
export interface PayloadLockedDocument {
id: string;
document?:
| ({
relationTo: 'users';
value: string | User;
} | null)
| ({
relationTo: 'pages';
value: string | Page;
} | null)
| ({
relationTo: 'exports';
value: string | Export;
} | null)
| ({
relationTo: 'exports-tasks';
value: string | ExportsTask;
} | null)
| ({
relationTo: 'payload-jobs';
value: string | PayloadJob;
} | null);
globalSlug?: string | null;
user: {
relationTo: 'users';
value: string | User;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference {
id: string;
user: {
relationTo: 'users';
value: string | User;
};
key?: string | null;
value?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration {
id: string;
name?: string | null;
batch?: number | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users_select".
*/
export interface UsersSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
email?: T;
resetPasswordToken?: T;
resetPasswordExpiration?: T;
salt?: T;
hash?: T;
loginAttempts?: T;
lockUntil?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "pages_select".
*/
export interface PagesSelect<T extends boolean = true> {
title?: T;
localized?: T;
group?:
| T
| {
value?: T;
ignore?: T;
array?:
| T
| {
field1?: T;
field2?: T;
id?: T;
};
};
array?:
| T
| {
field1?: T;
field2?: T;
id?: T;
};
blocks?:
| T
| {
hero?:
| T
| {
title?: T;
id?: T;
blockName?: T;
};
content?:
| T
| {
richText?: T;
id?: T;
blockName?: T;
};
};
author?: T;
hasManyNumber?: T;
relationship?: T;
excerpt?: T;
updatedAt?: T;
createdAt?: T;
_status?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "exports_select".
*/
export interface ExportsSelect<T extends boolean = true> {
name?: T;
format?: T;
limit?: T;
sort?: T;
locale?: T;
drafts?: T;
selectionToUse?: T;
fields?: T;
collectionSlug?: T;
where?: T;
updatedAt?: T;
createdAt?: T;
url?: T;
thumbnailURL?: T;
filename?: T;
mimeType?: T;
filesize?: T;
width?: T;
height?: T;
focalX?: T;
focalY?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "exports-tasks_select".
*/
export interface ExportsTasksSelect<T extends boolean = true> {
name?: T;
format?: T;
limit?: T;
sort?: T;
locale?: T;
drafts?: T;
selectionToUse?: T;
fields?: T;
collectionSlug?: T;
where?: T;
updatedAt?: T;
createdAt?: T;
url?: T;
thumbnailURL?: T;
filename?: T;
mimeType?: T;
filesize?: T;
width?: T;
height?: T;
focalX?: T;
focalY?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-jobs_select".
*/
export interface PayloadJobsSelect<T extends boolean = true> {
input?: T;
taskStatus?: T;
completedAt?: T;
totalTried?: T;
hasError?: T;
error?: T;
log?:
| T
| {
executedAt?: T;
completedAt?: T;
taskSlug?: T;
taskID?: T;
input?: T;
output?: T;
state?: T;
error?: T;
id?: T;
};
taskSlug?: T;
queue?: T;
waitUntil?: T;
processing?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents_select".
*/
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
document?: T;
globalSlug?: T;
user?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences_select".
*/
export interface PayloadPreferencesSelect<T extends boolean = true> {
user?: T;
key?: T;
value?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations_select".
*/
export interface PayloadMigrationsSelect<T extends boolean = true> {
name?: T;
batch?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "TaskCreateCollectionExport".
*/
export interface TaskCreateCollectionExport {
input: {
name?: string | null;
format: 'csv' | 'json';
limit?: number | null;
sort?: string | null;
locale?: ('all' | 'en' | 'es' | 'de') | null;
drafts?: ('true' | 'false') | null;
selectionToUse?: ('currentSelection' | 'currentFilters' | 'all') | null;
fields?: string[] | null;
collectionSlug: string;
where?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
user?: string | null;
userCollection?: string | null;
exportsCollection?: string | null;
};
output: {
success?: boolean | null;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth".
*/
export interface Auth {
[k: string]: unknown;
}
declare module 'payload' {
// @ts-ignore
export interface GeneratedTypes extends Config {}
}

View File

@@ -0,0 +1,135 @@
import type { Payload } from 'payload'
import { devUser } from '../../credentials.js'
import { richTextData } from './richTextData.js'
export const seed = async (payload: Payload): Promise<boolean> => {
payload.logger.info('Seeding data...')
try {
await payload.create({
collection: 'users',
data: {
email: devUser.email,
password: devUser.password,
},
})
// create pages
for (let i = 0; i < 5; i++) {
await payload.create({
collection: 'pages',
data: {
title: `Title ${i}`,
group: {
array: [{ field1: 'test' }],
},
},
})
}
for (let i = 0; i < 5; i++) {
const doc = await payload.create({
collection: 'pages',
data: {
title: `Localized ${i}`,
localized: 'en test',
},
locale: 'en',
})
await payload.update({
collection: 'pages',
id: doc.id,
data: {
localized: 'es test',
},
locale: 'es',
})
}
for (let i = 0; i < 5; i++) {
await payload.create({
collection: 'pages',
data: {
title: `Array ${i}`,
array: [
{
field1: 'foo',
field2: 'bar',
},
{
field1: 'foo',
field2: 'baz',
},
],
},
})
}
for (let i = 0; i < 5; i++) {
await payload.create({
collection: 'pages',
data: {
title: `Array Subfield ${i}`,
array: [
{
field1: 'foo',
field2: 'bar',
},
{
field1: 'foo',
field2: 'baz',
},
],
},
})
}
for (let i = 0; i < 5; i++) {
await payload.create({
collection: 'pages',
data: {
title: `hasMany Number ${i}`,
hasManyNumber: [0, 1, 1, 2, 3, 5, 8, 13, 21],
},
})
}
for (let i = 0; i < 5; i++) {
await payload.create({
collection: 'pages',
data: {
title: `Blocks ${i}`,
blocks: [
{
blockType: 'hero',
title: 'test',
},
{
blockType: 'content',
richText: richTextData,
},
],
},
})
}
for (let i = 0; i < 5; i++) {
await payload.create({
collection: 'pages',
data: {
title: `JSON ${i}`,
},
})
}
for (let i = 0; i < 5; i++) {
await payload.create({
collection: 'pages',
data: {
title: `Jobs ${i}`,
},
})
}
return true
} catch (err) {
console.error(err)
return false
}
}

View File

@@ -0,0 +1,110 @@
export const richTextData = {
root: {
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'This is some content in a block',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'heading',
version: 1,
tag: 'h2',
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Paragraph of text',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
textFormat: 0,
textStyle: '',
},
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Testing ',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 1,
},
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Richtext',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'listitem',
version: 1,
value: 2,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'list',
version: 1,
listType: 'number',
start: 1,
tag: 'ol',
},
{
children: [],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
textFormat: 0,
textStyle: '',
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'root',
version: 1,
},
}

View File

@@ -0,0 +1 @@
export const pagesSlug = 'pages'

View File

@@ -0,0 +1,13 @@
{
// extend your base config to share compilerOptions, etc
//"extends": "./tsconfig.json",
"compilerOptions": {
// ensure that nobody can accidentally use this config for a build
"noEmit": true
},
"include": [
// whatever paths you intend to lint
"./**/*.ts",
"./**/*.tsx"
]
}

View File

@@ -0,0 +1,3 @@
{
"extends": "../tsconfig.json"
}

View File

@@ -23,6 +23,7 @@ export const tgzToPkgNameMap = {
'@payloadcms/payload-cloud': 'payloadcms-payload-cloud-*',
'@payloadcms/plugin-cloud-storage': 'payloadcms-plugin-cloud-storage-*',
'@payloadcms/plugin-form-builder': 'payloadcms-plugin-form-builder-*',
'@payloadcms/plugin-import-export': 'payloadcms-plugin-import-export-*',
'@payloadcms/plugin-multi-tenant': 'payloadcms-plugin-multi-tenant-*',
'@payloadcms/plugin-nested-docs': 'payloadcms-plugin-nested-docs-*',
'@payloadcms/plugin-redirects': 'payloadcms-plugin-redirects-*',